library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.numeric_std.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;


-- top level entity myfirst_niosii

entity myfirst_niosii is port (
	clk: in std_logic;																																		  -- 50 MHz
	rst: in std_logic;																																			-- rst switch
	pio_led: out std_logic_vector(31 downto 0);																							-- LEDs of dev board
	buttons: in std_logic_vector(7 downto 0);																								-- push buttons
	matrix_rows: out std_logic_vector(11 downto 0);																					-- LED row driver, active high
	matrix_cols: out std_logic_vector(7 downto 0);																					-- LED colunm driver, active high
	lcd_16207_ext_RS             : out   std_logic;                                         -- RS
	lcd_16207_ext_RW             : out   std_logic;                                         -- RW
	lcd_16207_ext_data           : inout std_logic_vector(7 downto 0)  := (others => 'X');  -- data
	lcd_16207_ext_E              : out   std_logic 
);

	-- the state of the LEDs is stored in a 8 x 12 bit matrix
	-- the outer index represents the coulumn ID and the std_logic index represents the row ID
	type matrix_t is array(integer range 0 to 7) of std_logic_vector(11 downto 0);

end myfirst_niosii;

architecture behav of myfirst_niosii is

	-- noisII CPU IP wrapper
	component nios2_uc is port (
		clk_clk                 	   : in    std_logic                     := 'X';  				   -- clk
		reset_reset_n           	   : in    std_logic                     := 'X';  				   -- reset_n
		lcd_16207_ext_RS             : out   std_logic;                                        -- RS
		lcd_16207_ext_RW             : out   std_logic;                                        -- RW
		lcd_16207_ext_data           : inout std_logic_vector(7 downto 0)  := (others => 'X'); -- data
		lcd_16207_ext_E              : out   std_logic;                                        -- E
		pio_led_ext_conn_export 	   : out   std_logic_vector(31 downto 0);         				   -- dev board LEDs
		pio_button_ext_conn_export   : in    std_logic_vector(7 downto 0)  := (others => 'X'); -- dev board buttons

		pio_matrix_ext_conn_export   : out   std_logic_vector(19 downto 0)                     -- matrix instruction
		-- matrix instruction contains three parts:
		-- 20   16   12   8    4    0
		--   XXXX CCCC RRRR RRRR RRRR
		-- 
		-- X: 19 downto 16:  unused
		-- C: 15 downto 12:  column number
		-- R: 11 downto  0:  row data
		--
		-- column number:
		--  0000: don't set anything
		--  0001: set column 0 to row data
		--  0010: set column 1 to row data 
		--  0011: set column 2 to row data 
		--  ...
	);
	end component nios2_uc;

	-- signals for debouncing
	-- previous button state
	signal button_states: std_logic_vector(7 downto 0);

	-- button polling clock: 50Mhz / 2**20 = 47.7 Hz
	signal button_timer: integer range 0 to 2**20-1 := 0;

  -- matrix multiplexing clock: 
	-- 50Mhz / 2**20 = 1526 Hz = 1 / 0.66 ms per column
	-- 1526 Hz / 8 = 190 Hz matrix refresh rate
	signal matrix_timer: integer range 0 to 2**15-1 := 0;

	-- column index signal
	signal matrix_col_index: integer range 0 to 7 := 0;
	-- matrix signal
	signal matrix_s: matrix_t;
	-- matrix instruction signal
	signal pio_matrix_s: std_logic_vector(19 downto 0);

begin
	 
	u0: component nios2_uc
	port map (
		clk_clk                 => clk, 
		pio_led_ext_conn_export => pio_led, 
		reset_reset_n           => rst,
		pio_matrix_ext_conn_export => pio_matrix_s,
		pio_button_ext_conn_export => button_states
	);


	-- copies the row data according to instructions from CPU
	matrix_set: process(clk, rst)
		variable col_id : integer range 0 to 8;
	begin
		if rst = '0' then
			matrix_s <= (
				"111110011111",
				"000100000101",
				"010000000111",
				"111110000000",
				"000000011111",
				"111110010001",
				"101010011111",
				"111010000000"
			);
		elsif rising_edge(clk) then
			col_id := to_integer(unsigned(pio_matrix_s(15 downto 12)));
			if col_id > 0 then
				matrix_s(col_id-1) <= pio_matrix_s(11 downto 0);
			end if;
		end if;
	end process;

	-- copies rows from matrix_s to the outputs
	matrix_multiplex: process(clk, rst)
	begin
		if rst = '0' then
			matrix_rows <= "111111111111";
			matrix_cols <= "11111111";
			matrix_timer <= 0;
			matrix_col_index <= 0;
		elsif rising_edge(clk) then

			-- set outputs to zero at timer overflow
			-- avoids ghosting of display
			if matrix_timer = 2**15-1 then
				matrix_timer <= 0;
				if matrix_col_index = 7 then
					matrix_col_index <= 0;
				else
					matrix_col_index <= matrix_col_index + 1;
				end if;
				
				matrix_cols <= (others => '0');
				matrix_rows <= (others => '0');

			-- write actual data only 50MHz / 2**11 = 1 / 0.04 ms after setting outputs to zero
			elsif matrix_timer = 2**11-1 then
				matrix_cols(matrix_col_index) <= '1';
				matrix_rows <= matrix_s(matrix_col_index);
				matrix_timer <= matrix_timer + 1;

			-- else do nothing
			else
				matrix_timer <= matrix_timer + 1;
			end if;
		end if;
	end process;

	-- this process polls the buttons at 47.7 Hz
	-- edge detection is done in software
	button_debounce: process(clk, rst)	
	begin
		if rst = '0' then
			button_timer <= 0;
		elsif rising_edge(clk) then
			if button_timer = 2**20-1 then
				button_timer <= 0;

				-- update button states
				for id in 0 to 7 loop
					button_states(id) <= buttons(id);
				end loop;
			else
				button_timer <= button_timer + 1;
			end if;
		end if;
	end process;

end behav;

