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

entity dac is
	generic (
		CLK_FREQ        : integer := 50e6;
		VRAM_DATA_WIDTH : integer := 2
	);
	port (
		clk   : in std_logic;
		res_n : in std_logic;

		rd   : out std_logic;
		data : in std_logic_vector(VRAM_DATA_WIDTH - 1 downto 0);

		-- ADV7123 interface
		vga_r   : out std_logic_vector(7 downto 0);
		vga_g   : out std_logic_vector(7 downto 0);
		vga_b   : out std_logic_vector(7 downto 0);
		vga_clk : out std_logic;
		-- low active
		vga_sync_n  : out std_logic;
		-- low active
		vga_blank_n : out std_logic
	);
end entity;
architecture dac_arch of dac is
	-- all times in us
	constant TIME_SCANLINE_US         : real := real(64);
	constant TIME_BROAD_SYNC_START_US : real := 27.3;
	constant TIME_BROAD_SYNC_END_US   : real := TIME_SCANLINE_US / real(2) - TIME_BROAD_SYNC_START_US;
	constant TIME_SHORT_SYNC_START_US : real := 2.35;
	constant TIME_SHORT_SYNC_END_US   : real := TIME_SCANLINE_US / real(2) - TIME_SHORT_SYNC_START_US;
	constant TIME_H_SYNC_US           : real := 4.7;
	constant TIME_BACK_PORCH_US       : real := 5.7;
	constant TIME_FRONT_PORCH_US      : real := 1.65;
	constant TIME_DISPLAY_AREA_US     : real := TIME_SCANLINE_US - TIME_H_SYNC_US - TIME_BACK_PORCH_US - TIME_FRONT_PORCH_US;
	-- synchronization pulse rest durations
	-- NOTE: small error due to integer division expected!
	-- cycle count constants
	-- NOTE: these end up wrong :(
	--constant CYCLES_BROAD_SYNC_START : integer := integer(TIME_BROAD_SYNC_START_US * (real(CLK_FREQ) / real(10e6)));
	--constant CYCLES_BROAD_SYNC_END   : integer := integer(TIME_BROAD_SYNC_END_US * (real(CLK_FREQ) / real(10e6)));
	--constant CYCLES_SHORT_SYNC_START : integer := integer(TIME_SHORT_SYNC_START_US * (real(CLK_FREQ) / real(10e6)));
	--constant CYCLES_SHORT_SYNC_END   : integer := integer(TIME_SHORT_SYNC_END_US * (real(CLK_FREQ) / real(10e6)));
	--constant CYCLES_H_SYNC           : integer := integer(TIME_H_SYNC_US * (real(CLK_FREQ) / real(10e6)));
	--constant CYCLES_BACK_PORCH      : integer := integer(TIME_BACK_PORCH_US * (real(CLK_FREQ) / real(10e6)));
	--constant CYCLES_DISPLAY_AREA    : integer := integer(TIME_DISPLAY_AREA_US * (real(CLK_FREQ) / real(10e6)));
	--constant CYCLES_FRONT_PORCH     : integer := integer(TIME_FRONT_PORCH_US * (real(CLK_FREQ) / real(10e6)));
	-- "correct" ones added here
	constant CYCLES_BROAD_SYNC_START : integer := 1365 - 1;
	constant CYCLES_BROAD_SYNC_END   : integer := 235 - 1;
	constant CYCLES_SHORT_SYNC_START : integer := 118 - 1;
	constant CYCLES_SHORT_SYNC_END   : integer := 1482 - 1;
	constant CYCLES_H_SYNC           : integer := 235 - 1;
	constant CYCLES_BACK_PORCH       : integer := 384 - 1;
	constant CYCLES_DISPLAY_AREA     : integer := 2400 - 1;
	constant CYCLES_FRONT_PORCH      : integer := 181 - 1;
	-- denotes how long a read is valid
	constant CYCLES_PIXEL_VALID : integer := 6 - 1;
	-- line counts (starts counting from 0!)
	constant LINES_BROAD_SYNC : integer := 5 - 1;
	constant LINES_SHORT_SYNC : integer := 5 - 1;
	-- image centering requires special activation
	constant LINES_DISPLAY_INACTIVE_START : integer := 41 - 1;
	constant LINES_DISPLAY_ACTIVE         : integer := 240 - 1;
	constant LINES_DISPLAY_INACTIVE_END   : integer := 24 - 1;
	constant LINES_DISPLAY_TOTAL          : integer := LINES_DISPLAY_INACTIVE_START + 1 + LINES_DISPLAY_ACTIVE + 1 + LINES_DISPLAY_INACTIVE_END + 1 - 1;
	-- used for integer range delimitation
	-- NOTE: has to be changed manually when the line counts or cycle counts change!
	constant MAX_CYCLES : integer := CYCLES_DISPLAY_AREA;
	constant MAX_LINES  : integer := LINES_DISPLAY_TOTAL;

	type fsm_state_t is (
		BROAD_SYNC_START, BROAD_SYNC_END, SHORT_SYNC_1_START,
		SHORT_SYNC_1_END, H_SYNC, FRONT_PORCH, DISPLAY_AREA,
		BACK_PORCH, SHORT_SYNC_2_START, SHORT_SYNC_2_END
	);

	type fsm_data_t is record
		cycle_cnt : integer range 0 to MAX_CYCLES;
		line_cnt  : integer range 0 to MAX_LINES;
		pixel_cnt : integer range 0 to CYCLES_PIXEL_VALID;
	end record;

	-- fsm signals
	constant INITIAL_STATE    : fsm_state_t := BROAD_SYNC_START;
	constant INITIAL_FSM_DATA : fsm_data_t := (
		cycle_cnt => 0,
		line_cnt  => 0,
		pixel_cnt => CYCLES_PIXEL_VALID
	);

	signal state, state_next       : fsm_state_t;
	signal fsm_data, fsm_data_next : fsm_data_t;
begin
	-- structural
	-- concurrent
	-- drive with full CLK_FREQ clk
	vga_clk <= clk;
	-- red and blue unused
	vga_r <= (others => '0');
	vga_b <= (others => '0');

	-- sequential
	sync : process(clk, res_n)
	begin
		if res_n = '0' then
			state <= INITIAL_STATE;
			fsm_data <= INITIAL_FSM_DATA;
		elsif rising_edge(clk) then
			state <= state_next;
			fsm_data <= fsm_data_next;
		end if;
	end process;

	async : process(all)
	begin
		-- default assignments
		state_next <= state;
		fsm_data_next <= fsm_data;

		-- _n prefixed signals are low active!
		vga_sync_n <= '1';
		vga_blank_n <= '1';
		vga_g <= (others => '0');
		rd <= '0';

		case state is
			when BROAD_SYNC_START =>
				-- emit sync level
				vga_sync_n <= '0';
				if fsm_data.cycle_cnt = CYCLES_BROAD_SYNC_START then
					state_next <= BROAD_SYNC_END;
					fsm_data_next.cycle_cnt <= 0;
					vga_sync_n  <= '1';
					vga_blank_n <= '0';
				else
					fsm_data_next.cycle_cnt <= fsm_data.cycle_cnt + 1;
				end if;

			when BROAD_SYNC_END =>
				-- emit blank level
				vga_blank_n <= '0';
				if fsm_data.cycle_cnt = CYCLES_BROAD_SYNC_END and fsm_data.line_cnt = LINES_BROAD_SYNC then
					state_next <= SHORT_SYNC_1_START;
					fsm_data_next.cycle_cnt <= 0;
					fsm_data_next.line_cnt <= 0;
					vga_sync_n  <= '0';
					vga_blank_n <= '1';
				elsif fsm_data.cycle_cnt = CYCLES_BROAD_SYNC_END then
					state_next <= BROAD_SYNC_START;
					fsm_data_next.cycle_cnt <= 0;
					fsm_data_next.line_cnt <= fsm_data.line_cnt + 1;
					vga_sync_n  <= '0';
					vga_blank_n <= '1';
				else
					fsm_data_next.cycle_cnt <= fsm_data.cycle_cnt + 1;
				end if;

			when SHORT_SYNC_1_START =>
				-- emit sync level
				vga_sync_n <= '0';
				if fsm_data.cycle_cnt = CYCLES_SHORT_SYNC_START then
					state_next <= SHORT_SYNC_1_END;
					fsm_data_next.cycle_cnt <= 0;
					vga_sync_n  <= '1';
					vga_blank_n <= '0';
				else
					fsm_data_next.cycle_cnt <= fsm_data.cycle_cnt + 1;
				end if;

			when SHORT_SYNC_1_END =>
				-- emit blank level
				vga_blank_n <= '0';
				if fsm_data.cycle_cnt = CYCLES_SHORT_SYNC_END and fsm_data.line_cnt = LINES_SHORT_SYNC then
					state_next <= H_SYNC;
					fsm_data_next.cycle_cnt <= 0;
					fsm_data_next.line_cnt <= 0;
					vga_sync_n  <= '0';
					vga_blank_n <= '1';
				elsif fsm_data.cycle_cnt = CYCLES_SHORT_SYNC_END then
					state_next <= SHORT_SYNC_1_START;
					fsm_data_next.cycle_cnt <= 0;
					fsm_data_next.line_cnt <= fsm_data.line_cnt + 1;
					vga_sync_n  <= '0';
					vga_blank_n <= '1';
				else
					fsm_data_next.cycle_cnt <= fsm_data.cycle_cnt + 1;
				end if;

			when H_SYNC =>
				-- emit sync level
				vga_sync_n <= '0';
				if fsm_data.cycle_cnt = CYCLES_H_SYNC then
					state_next <= BACK_PORCH;
					fsm_data_next.cycle_cnt <= 0;
					vga_sync_n  <= '1';
					vga_blank_n <= '0';
				else
					fsm_data_next.cycle_cnt <= fsm_data.cycle_cnt + 1;
				end if;

			when BACK_PORCH =>
				-- emit blank level
				vga_blank_n <= '0';
				if fsm_data.cycle_cnt = CYCLES_BACK_PORCH then
					state_next <= DISPLAY_AREA;
					fsm_data_next.cycle_cnt <= 0;
					vga_sync_n  <= '1';
				else
					fsm_data_next.cycle_cnt <= fsm_data.cycle_cnt + 1;
				end if;

			when DISPLAY_AREA =>
				if fsm_data.cycle_cnt = CYCLES_DISPLAY_AREA then
					state_next <= FRONT_PORCH;
					fsm_data_next.cycle_cnt <= 0;
					-- next time we enter, read immediately instead
					-- of waiting 5 cycles
					fsm_data_next.pixel_cnt <= CYCLES_PIXEL_VALID;
				else
					fsm_data_next.cycle_cnt <= fsm_data.cycle_cnt + 1;
				end if;
				-- take care to set `rd` signal
				if (fsm_data.line_cnt > LINES_DISPLAY_INACTIVE_START - 1) and (fsm_data.line_cnt <= LINES_DISPLAY_INACTIVE_START + 1 + LINES_DISPLAY_ACTIVE - 1) then
					if fsm_data.pixel_cnt = CYCLES_PIXEL_VALID then
						rd <= '1';
						fsm_data_next.pixel_cnt <= 0;
					else
						fsm_data_next.pixel_cnt <= fsm_data.pixel_cnt + 1;
					end if;
				end if;
				-- check for vertical middle
				if (fsm_data.line_cnt > LINES_DISPLAY_INACTIVE_START) and (fsm_data.line_cnt <= LINES_DISPLAY_INACTIVE_START + 1 + LINES_DISPLAY_ACTIVE) then
					case data is
						when "11"   => vga_g <= x"FF";
						when "10"   => vga_g <= x"7F";
						when "01"   => vga_g <= x"3F";
						when others => vga_g <= x"00";
					end case;
				else
					vga_blank_n <= '0';
				end if;

			when FRONT_PORCH =>
				-- emit blank level
				vga_blank_n <= '0';
				if fsm_data.cycle_cnt = CYCLES_FRONT_PORCH and fsm_data.line_cnt = LINES_DISPLAY_TOTAL then
					state_next <= SHORT_SYNC_2_START;
					fsm_data_next.cycle_cnt <= 0;
					fsm_data_next.line_cnt <= 0;
					vga_sync_n  <= '0';
					vga_blank_n <= '1';
				elsif fsm_data.cycle_cnt = CYCLES_FRONT_PORCH then
					state_next <= H_SYNC;
					fsm_data_next.cycle_cnt <= 0;
					fsm_data_next.line_cnt <= fsm_data.line_cnt + 1;
					vga_sync_n  <= '0';
					vga_blank_n <= '1';
				else
					fsm_data_next.cycle_cnt <= fsm_data.cycle_cnt + 1;
				end if;

			when SHORT_SYNC_2_START =>
				-- emit sync level
				vga_sync_n <= '0';
				if fsm_data.cycle_cnt = CYCLES_SHORT_SYNC_START then
					state_next <= SHORT_SYNC_2_END;
					fsm_data_next.cycle_cnt <= 0;
					vga_sync_n  <= '1';
					vga_blank_n <= '0';
				else
					fsm_data_next.cycle_cnt <= fsm_data.cycle_cnt + 1;
				end if;

			when SHORT_SYNC_2_END =>
				-- emit blank level
				vga_blank_n <= '0';
				if fsm_data.cycle_cnt = CYCLES_SHORT_SYNC_END and fsm_data.line_cnt = LINES_SHORT_SYNC then
					state_next <= BROAD_SYNC_START;
					fsm_data_next.cycle_cnt <= 0;
					fsm_data_next.line_cnt <= 0;
					vga_sync_n  <= '0';
					vga_blank_n <= '1';
				elsif fsm_data.cycle_cnt = CYCLES_SHORT_SYNC_END then
					state_next <= SHORT_SYNC_2_START;
					fsm_data_next.cycle_cnt <= 0;
					fsm_data_next.line_cnt <= fsm_data.line_cnt + 1;
					vga_sync_n  <= '0';
					vga_blank_n <= '1';
				else
					fsm_data_next.cycle_cnt <= fsm_data.cycle_cnt + 1;
				end if;
		end case;
	end process;
end architecture;
