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

entity video_generator is
port(
	clock 	: in std_logic;
	reset 	: in std_logic;
	
	col_to_read : out unsigned (8 downto 0);
	color       : in std_logic_vector(17 downto 0);

	start_screen : out std_logic;
	start_row    : out std_logic;

	-- Signals for the vga chip
	vga_clock,
	vga_h_sync,
	vga_v_sync,
	vga_blank,
	vga_sync : out std_logic;
	vga_r,
	vga_g,
	vga_b : out unsigned(9 downto 0)
);
end video_generator;

architecture rtl of video_generator is

	-- Video parameters
	constant HTOTAL       : integer := 1600;
	constant HSYNC        : integer := 192;
	constant HBACK_PORCH  : integer := 96;
	constant HACTIVE      : integer := 1280;
	constant HFRONT_PORCH : integer := 32;
  
	constant VTOTAL       : integer := 525;
	constant VSYNC        : integer := 2;
	constant VBACK_PORCH  : integer := 33;
	constant VACTIVE      : integer := 480;
	constant VFRONT_PORCH : integer := 10;
	
	-- Stuff we found out (old, was at 25mhz):
	-- In simulation:
	-- HCount 146 is the first pixel in a row
	-- HCount 786 is the pixel after the active area
	-- We start outputting video at Vcount 35
	-- We stop at Vcount 515
	-- When we look at this on the monitor, the first column is duplicated 
	-- once and last column is duplicated twice.
	
	-- Signals for the video controller
	signal Hcount : unsigned(10 downto 0);  -- Horizontal position 
	signal Vcount : unsigned(9 downto 0);  -- Vertical position 
	signal EndOfLine, EndOfField : std_logic;

	signal vga_hblank, vga_hsync,
    vga_vblank, vga_vsync : std_logic;  -- Sync. signals
	signal clock_25 : std_logic := '0';
	signal col_to_read_sig : unsigned (8 downto 0);

begin
	process (clock)
	begin
		if rising_edge(clock) then
			clock_25 <= not clock_25;
		end if;
	end process;
	
	-- Horizontal and vertical counters
	
	HCounter : process (clock)
	begin
		if rising_edge(clock) then      
			if reset = '1' then
				Hcount <= (others => '0');
			elsif EndOfLine = '1' then
				Hcount <= (others => '0');
			else
				Hcount <= Hcount + 1;
			end if;
		end if;
	end process HCounter;

	EndOfLine <= '1' when Hcount = HTOTAL - 1 else '0';

	VCounter : process (clock)
	begin
		if rising_edge(clock) then      
			if reset = '1' then
				Vcount <= (others => '0');
			elsif EndOfLine = '1' then
				if EndOfField = '1' then
					Vcount <= (others => '0');
				else
					Vcount <= Vcount + 1;
				end if;
			end if;
		end if;
	end process VCounter;

	EndOfField <= '1' when Vcount = VTOTAL - 1 else '0';

	-- State machines to generate HSYNC, VSYNC, HBLANK, and VBLANK

	HSyncGen : process (clock)
	begin
		if rising_edge(clock) then     
			if reset = '1' or EndOfLine = '1' then
				vga_hsync <= '1';
			elsif Hcount = HSYNC - 1 then
				vga_hsync <= '0';
			end if;
		end if;
	end process HSyncGen;
	  
	HBlankGen : process (clock)
	begin
		if rising_edge(clock) then
			if reset = '1' then
				vga_hblank <= '1';
			elsif Hcount = HSYNC + HBACK_PORCH then
				vga_hblank <= '0';
			elsif Hcount = HSYNC + HBACK_PORCH + HACTIVE then
				vga_hblank <= '1';
			end if;      
		end if;
	end process HBlankGen;

	VSyncGen : process (clock)
	begin
		if rising_edge(clock) then
			if reset = '1' then
				vga_vsync <= '1';
			elsif EndOfLine ='1' then
				if EndOfField = '1' then
					vga_vsync <= '1';
				elsif Vcount = VSYNC - 1 then
					vga_vsync <= '0';
				end if;
			end if;      
		end if;
	end process VSyncGen;

	VBlankGen : process (clock)
	begin
		if rising_edge(clock) then    
			if reset = '1' then
				vga_vblank <= '1';
			elsif EndOfLine = '1' then
				if Vcount = VSYNC + VBACK_PORCH - 1 then
					vga_vblank <= '0';
				elsif Vcount = VSYNC + VBACK_PORCH + VACTIVE - 1 then
					vga_vblank <= '1';
				end if;
			end if;
		end if;
	end process VBlankGen;
	
	-- Start screen and start row control signals

	start_signals : process (clock)
	begin
		if rising_edge(clock) then
			if reset = '1' then
				start_screen <= '0';
				start_row <= '0';
			-- We always want to assert these signals just as soon as we're 
			-- done with the active area of the previous row.
			elsif Hcount = HSYNC + HBACK_PORCH + HACTIVE + 1 then -- TODO we can probably remove the "+ 1" here, we're playing it safe for now
				if Vcount = VSYNC + VBACK_PORCH - 1 then
					start_screen <= '1';
				else
					start_screen <= '0';
				end if;
				-- only start a new row of ray tracing every other screen row 
				-- to expand 240 rows of ray traced data 480 rows on screen.
				if Vcount(0) = '0'
				   and Vcount >= VSYNC + VBACK_PORCH 
				   and Vcount < VSYNC + VBACK_PORCH + VACTIVE then
					start_row <= '1';
				else
					start_row <= '0';
				end if;
			else
				start_screen <= '0';
				start_row <= '0';
			end if;
		end if;
	end process start_signals;
	
	-- Column that we want to read from the line buffer
	column : process (clock)
	begin
		if rising_edge(clock) then
			if reset = '1' then
				col_to_read_sig <= (others => '0');
			elsif Hcount = HSYNC + HBACK_PORCH then
				col_to_read_sig <= (others => '0');
			-- only increase the col every other pixel to expand 320 pixels in 
			-- memory to 640 pixels on screen.
			-- divide by another factor of two since hcount is now running at 
			-- twice the vga clock speed.
			elsif Hcount(1 downto 0) = "00" then
				col_to_read_sig <= col_to_read_sig + "000000001";
			end if;      
		end if;
	end process column;
	
	col_to_read <= col_to_read_sig;

	-- Final video out
	-- TODO do we need to ensure we always send out white so it gets the right 
	-- levels or something?
	VideoOut : process (clock, reset)
	begin
		if reset = '1' then
			vga_r <= "0000000000";
			vga_g <= "0000000000";
			vga_b <= "0000000000";
		elsif rising_edge(clock) then
			if vga_hblank = '1' or vga_vblank = '1' then
				vga_r <= "0000000000";
				vga_g <= "0000000000";
				vga_b <= "0000000000";
			else
				vga_r <= unsigned(color(17 downto 12)) & "0000";
				vga_g <= unsigned(color(11 downto 6)) & "0000";
				vga_b <= unsigned(color(5 downto 0)) & "0000";
			end if;
		end if;
	end process VideoOut;
	
	vga_clock <= clock_25;
	vga_h_sync <= not vga_hsync;
	vga_v_sync <= not vga_vsync;
	vga_sync <= '0';
	vga_blank <= not (vga_hsync or vga_vsync);

end rtl;
