library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.lcd_graphics_controller_pkg.all;
use work.gfx_if_pkg.all;

entity lcd_graphics_controller_tb is
end entity;

architecture bench of lcd_graphics_controller_tb is

	component sram is 
		generic (
		OUTPUT_DIR   : string := "./"
		);
		port (
		-- SRAM interface
		dq     : inout std_logic_vector(15 downto 0);
		addr   : in	std_logic_vector(19 downto 0);
		lb_n   : in	std_logic;
		ub_n   : in	std_logic;
		ce_n   : in	std_logic;
		oe_n   : in	std_logic;
		we_n   : in	std_logic;

		-- on rising_edge: dump frame to file
		write_file : in std_logic;
		base_address : natural := 0;
		width        : natural := 400;
		height       : natural := 240
		);
	end component;

	-- sram signals
	signal dq   : std_logic_vector(15 downto 0);
	signal addr : std_logic_vector(19 downto 0);
	signal lb_n : std_logic;
	signal ub_n : std_logic;
	signal ce_n : std_logic;
	signal oe_n : std_logic;
	signal we_n : std_logic;

	signal write_file     : std_logic := '0';
	constant base_address : integer := 0;
	constant width        : integer := 400;
	constant height       : integer := 240;

	-- lcd_graphics_controller signals
	signal clk : std_logic := '-';
	signal res_n : std_logic := '-';
	signal display_clk : std_logic := '-';
	signal display_res_n : std_logic := '-';

	signal gfx_instr         : std_logic_vector(7 downto 0) := (others => '0');
	signal gfx_instr_wr      : std_logic := '0';
	signal gfx_data          : std_logic_vector(15 downto 0) := (others => '0');
	signal gfx_data_wr       : std_logic := '0';

	signal nclk  : std_logic;
	signal grest : std_logic;
	signal vd    : std_logic;
	signal hd    : std_logic;
	signal den   : std_logic;
	signal r     : std_logic_vector(7 downto 0);
	signal g     : std_logic_vector(7 downto 0);
	signal b     : std_logic_vector(7 downto 0);
	signal sclk  : std_logic;
	signal sda   : std_logic;
	signal scen  : std_logic;

	signal gfx_instr_full    : std_logic;
	signal gfx_data_full     : std_logic;
	signal gfx_frame_sync    : std_logic;

	signal stop_clock : boolean := false;
	constant CLK_PERIOD : time := 20 ns;
	constant DISPLAY_CLK_PERIOD : time := 125 ns;
	constant N : integer := 16;

	-- in clock cycles
	constant TIMEOUT_CYCLES : integer := 128;

begin

	sram_inst : sram
	generic map (
		OUTPUT_DIR => "./"
	)
	port map (
		dq   => dq,
		addr => addr,
		lb_n => lb_n,
		ub_n => ub_n,
		ce_n => ce_n,
		oe_n => oe_n,
		we_n => we_n,

		write_file   => write_file,
		base_address => base_address,
		width        => width,
		height       => height
	);

	lcd_graphics_controller_inst : lcd_graphics_controller
	port map (
		clk           => clk,
		res_n         => res_n,
		display_clk   => display_clk,
		display_res_n => display_res_n,
		
		gfx_instr    => gfx_instr,
		gfx_instr_wr => gfx_instr_wr,
		gfx_data     => gfx_data,
		gfx_data_wr  => gfx_data_wr,
		
		nclk      => nclk,
		grest     => grest,
		vd        => vd,
		hd        => hd,
		den       => den,
		r         => r,
		g         => g,
		b         => b,
		sclk      => sclk,
		sda       => sda,
		scen      => scen,
		-- use sram signals
		sram_addr => addr,
		sram_ub_n => ub_n,
		sram_lb_n => lb_n,
		sram_we_n => we_n,
		sram_ce_n => ce_n,
		sram_oe_n => oe_n,
		sram_dq   => dq,
		
		gfx_instr_full => gfx_instr_full,
		gfx_data_full  => gfx_data_full,
		gfx_frame_sync => gfx_frame_sync
	);

	-- NOTE: this implementation is not complete :(
	-- some error I could not fix in time causes the drawing to behave weirdly, but it sort of works
	generate_gfx : process
		variable timeout : integer := TIMEOUT_CYCLES;
	begin
		res_n <= '0';
		wait for 250 ns;
		wait until rising_edge(clk);
		res_n <= '1';
		wait until rising_edge(clk);

		if gfx_instr_full = '1' then
			wait until gfx_instr_full = '0';
		end if;
		gfx_instr_wr <= '1';
		gfx_instr <= GFX_INSTR_SET_PATTERN(1);
		gfx_data_wr <= '1';
		-- first pattern
		-- dx = dy = 1, bw = bh = 2
		--gfx_data <= "0001000100100010";
		-- dx = dy = 0, bw = bh = 1
		gfx_data <= "0000000000010001";
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		gfx_data_wr <= '1';
		-- second pattern
		-- dx = dy = 2, bw = bh = 1
		--gfx_data <= "0010001000010001";
		-- dx = dy = 1, bw = bh = 1
		gfx_data <= "0001000100010001";
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- rect 1
		if gfx_instr_full = '1' then
			wait until gfx_instr_full = '0';
		end if;
		-- border, no alpha, pattern 1
		gfx_instr_wr <= '1';
		gfx_instr <= GFX_INSTR_DRAW_RECT(true, false, 1);
		gfx_data_wr <= '1';
		-- x0
		gfx_data <= "0000000000000000";
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		gfx_data_wr <= '1';
		-- y0
		gfx_data <= "0000000000000000";
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		gfx_data_wr <= '1';
		-- width after x0
		gfx_data <= std_logic_vector(to_unsigned(N, 16));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		gfx_data_wr <= '1';
		-- height after y0
		gfx_data <= std_logic_vector(to_unsigned(N, 16));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- rect 2
		if gfx_instr_full = '1' then
			wait until gfx_instr_full = '0';
		end if;
		-- border, no alpha, pattern 1
		gfx_instr_wr <= '1';
		gfx_instr <= GFX_INSTR_DRAW_RECT(true, false, 1);
		gfx_data_wr <= '1';
		-- x1
		gfx_data <= std_logic_vector(to_unsigned(width - N - 1, 16));
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		gfx_data_wr <= '1';
		-- y1
		gfx_data <= std_logic_vector(to_unsigned(height - N - 1, 16));
		gfx_data <= "0000000000000000";
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		gfx_data_wr <= '1';
		-- width after x1
		gfx_data <= std_logic_vector(to_unsigned(N, 16));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		gfx_data_wr <= '1';
		-- height after y1
		gfx_data <= std_logic_vector(to_unsigned(N, 16));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);

		-- wait for finish
		-- TODO: find good way to wait for finish
		while timeout > 0 loop
			timeout := TIMEOUT_CYCLES when we_n = '1' else timeout - 1;
			wait until rising_edge(clk);
		end loop;
		
		write_file <= '1';
		stop_clock <= true;
		report "generate_gfx done";
		wait;
	end process;

	-- taken from top_tb
	generate_clk : process
	begin
		while not stop_clock loop
			clk <= '0', '1' after CLK_PERIOD / 2;
			wait for CLK_PERIOD;
		end loop;
		wait;
	end process;

	generate_display_clk : process
	begin
		while not stop_clock loop
			display_clk <= '0', '1' after DISPLAY_CLK_PERIOD / 2;
			wait for DISPLAY_CLK_PERIOD;
		end loop;
		wait;
	end process;
end architecture;
