library ieee;

use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

use work.gfx_if_pkg.all;
use work.math_pkg.all;

entity rasterizer_tb is
end entity;

architecture bench of rasterizer_tb is

	component rasterizer is
		generic (
			COLOR_WIDTH : integer := 2;

			WIDTH  : integer := 400;
			HEIGHT : integer := 240;

			FIFO_DATA_DEPTH  : integer := 8;
			FIFO_INSTR_DEPTH : integer := 8;

			FIFO_DATA_WIDTH  : integer := 16;
			FIFO_INSTR_WIDTH : integer := 8;

			VRAM_ADDR_WIDTH : integer := log2c(2 * 400 * 240);
			VRAM_DATA_WIDTH : integer := 2
		);
		port (
			clk : in std_logic;
			res_n : in std_logic;

			gfx_instr      : in std_logic_vector(FIFO_INSTR_WIDTH - 1 downto 0);
			gfx_instr_wr   : in std_logic;
			gfx_instr_full : out std_logic;
			gfx_data       : in std_logic_vector(FIFO_DATA_WIDTH - 1 downto 0);
			gfx_data_wr    : in std_logic;
			gfx_data_full  : out std_logic;
			gfx_frame_sync : out std_logic;

			-- frame synchronization signal
			frame_start    : in std_logic;
			vram_base_addr : out std_logic_vector(VRAM_ADDR_WIDTH - 1 downto 0);

			-- exposed dp ram storage
			-- (only write buffers! read buffers managed by frame_reader)
			vram_wr   : out std_logic;
			vram_addr : out std_logic_vector(VRAM_ADDR_WIDTH - 1 downto 0);
			vram_data : out std_logic_vector(VRAM_DATA_WIDTH - 1 downto 0)
		);
	end component;

	component dp_ram_1c1r1w is 
		generic (
			ADDR_WIDTH : integer;
			DATA_WIDTH : integer
		);
		port (
			clk : in std_logic;

			rd1      : in std_logic;
			rd1_addr : in std_logic_vector(ADDR_WIDTH - 1 downto 0);
			rd1_data : out std_logic_vector(DATA_WIDTH - 1 downto 0);

			wr2      : in std_logic;
			wr2_addr : in std_logic_vector(ADDR_WIDTH - 1 downto 0);
			wr2_data : in std_logic_vector(DATA_WIDTH - 1 downto 0)
		);
	end component;

	signal vram_wr   : std_logic := '0';
	signal vram_addr : std_logic_vector(log2c(2 * 400 * 240) - 1 downto 0) := (others => '0');
	signal vram_data : std_logic_vector(1 downto 0) := (others => '0');

	signal vram_base_addr : std_logic_vector(log2c(2 * 400 * 240) - 1 downto 0);

	constant width          : integer := 400;
	constant height         : integer := 240;

	-- lcd_graphics_controller signals
	signal clk : std_logic;
	signal res_n : std_logic := '1';

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

	signal gfx_frame_sync : std_logic;
	signal frame_start    : std_logic := '0';

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

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

begin

	vram : dp_ram_1c1r1w
	generic map (
		ADDR_WIDTH => log2c(2 * 400 * 240),
		DATA_WIDTH => 2
	)
	port map (
		clk => clk,

		rd1_addr => (others => '0'),
		rd1_data => open,
		rd1      => '0',

		wr2_addr => vram_addr,
		wr2_data => vram_data,
		wr2      => vram_wr
	);

	rasterizer_inst : rasterizer
	generic map (
		COLOR_WIDTH => 2,
		WIDTH => WIDTH,
		HEIGHT => HEIGHT,
		FIFO_DATA_DEPTH => 8,
		FIFO_INSTR_DEPTH => 8,
		FIFO_DATA_WIDTH => 16,
		FIFO_INSTR_WIDTH => 8,
		VRAM_ADDR_WIDTH => log2c(2 * 400 * 240),
		VRAM_DATA_WIDTH => 2
	)
	port map (
		clk           => clk,
		res_n         => res_n,
		
		gfx_instr      => gfx_instr,
		gfx_instr_wr   => gfx_instr_wr,
		gfx_instr_full => gfx_instr_full,
		gfx_data       => gfx_data,
		gfx_data_wr    => gfx_data_wr,
		gfx_data_full  => gfx_data_full,
		gfx_frame_sync => gfx_frame_sync,

		vram_wr   => vram_wr,
		vram_addr => vram_addr,
		vram_data => vram_data,

		frame_start => frame_start,
		vram_base_addr => vram_base_addr
	);

	generate_gfx : process
		variable done : boolean := false;
		-- for use with gfx_(instr|data)_full
		procedure wait_until_not_full(signal is_full : std_logic) is
		begin
			if is_full = '1' then
				wait until is_full = '0';
				return;
			end if;
		end procedure;
		procedure write_mem(
			signal is_full : std_logic;
			signal wr : out std_logic;
			signal mem : out std_logic_vector;
			to_write : std_logic_vector) is
		begin
			wait_until_not_full(is_full);
			wr <= '1';
			mem <= to_write;
		end procedure;
	begin
		res_n <= '0';
		wait for 100 ns;
		wait until rising_edge(clk);
		res_n <= '1';
		wait until rising_edge(clk);

		-- test INSTR_SET_CFG
		report "started GFX_INSTR_SET_CFG(true)";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_SET_CFG(true));
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		wait until rising_edge(clk);

		-- test INSTR_FRAME_SYNC
		report "started GFX_INSTR_FRAME_SYNC";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_FRAME_SYNC);
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		wait until rising_edge(clk);

		-- signal pseudo frame start
		wait for 100 ns;
		wait until rising_edge(clk);
		frame_start <= '1';
		wait until rising_edge(clk);
		frame_start <= '0';
		wait until rising_edge(clk);
		-- disable double buffering again
		report "started GFX_INSTR_SET_CFG(false)";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_SET_CFG(false));
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		wait until rising_edge(clk);

		-- test GFX_INSTR_SET_PATTERN(1)
		report "started GFX_INSTR_SET_PATTERN(1)";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_SET_PATTERN(1));
		-- first pattern
		-- dx = dy = 1, bw = bh = 2
		--gfx_data <= "0001000100100010";
		-- dx = dy = 0, bw = bh = 1
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, "0000000000010001");
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- second pattern
		-- dx = dy = 2, bw = bh = 1
		--gfx_data <= "0010001000010001";
		-- dx = dy = 1, bw = bh = 1
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, "0001000100010001");
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		report "started GFX_INSTR_DRAW_RECT(true, false, 1)";
		-- rect 1
		-- border, no alpha, pattern 1
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_DRAW_RECT(true, false, 1));
		-- x0
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, "0000000000000000");
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- y0
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, "0000000000000000");
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- width after x0
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- height after y0
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		report "started GFX_INSTR_DRAW_RECT(true, false, 1)";
		-- rect 2
		-- border, no alpha, pattern 1
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_DRAW_RECT(true, false, 1));
		-- x1
		write_mem(gfx_data_full, gfx_data_wr, 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);
		-- y1
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(width - N - 1, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- width after x1
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- height after y1
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);

		-- test INSTR_NOP
		report "started GFX_INSTR_NOP";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_NOP);
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		wait until rising_edge(clk);

		-- test INSTR_SET_PIXEL
		report "started GFX_INSTR_SET_PIXEL";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_SET_PIXEL);
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		wait until rising_edge(clk);
		-- x
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N/2, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- y
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N/2, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);

		-- test INSTR_CLEAR
		report "started GFX_INSTR_CLEAR";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_CLEAR);
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		wait until rising_edge(clk);

		-- test INSTR_DRAW_LINE
		report "started GFX_INSTR_DRAW_LINE";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_DRAW_LINE);
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		wait until rising_edge(clk);
		-- test if waiting for instruction works
		wait for 100 ns;
		-- supply data
		-- x0
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(0, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- y0
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(0, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- x1
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- y1
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);

		-- test INSTR_SET_COLOR
		report "started GFX_INSTR_SET_COLOR(true)";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_SET_COLOR(true));
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		wait until rising_edge(clk);
		-- color
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, x"f0f0");
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);

		report "started GFX_INSTR_SET_COLOR(false)";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_SET_COLOR(false));
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		wait until rising_edge(clk);
		-- color
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, x"0f0f");
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);

		-- test INSTR_DRAW_CIRCLE
		report "started GFX_INSTR_DRAW_CIRCLE";
		write_mem(gfx_instr_full, gfx_instr_wr, gfx_instr, GFX_INSTR_DRAW_CIRCLE);
		wait until rising_edge(clk);
		gfx_instr_wr <= '0';
		wait until rising_edge(clk);
		-- supply data
		-- x
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N/2, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- y
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N/2, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);
		-- r
		write_mem(gfx_data_full, gfx_data_wr, gfx_data, std_logic_vector(to_unsigned(N/4, 16)));
		wait until rising_edge(clk);
		gfx_data_wr <= '0';
		wait until rising_edge(clk);

		wait for 5e3 * CLK_PERIOD;
		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;

end architecture;
