--------------------------------------------------------------------------------
-- Company: 
-- Engineer:
--
-- Create Date:    11:25:45 10/15/05
-- Design Name:    
-- Module Name:    char_gen - Behavioral
-- Project Name:   
-- Target Device:  
-- Tool versions:  
-- Description:
--
-- Dependencies:
-- 
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
-- 
--------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

use work.utils_pkg.all;
use work.vga_types.all;

entity char_gen is
	Generic
	(
		tsvga					: vga_timespec_t := ts_vga_800_600_72;

		-- User supplied parameters
		CHARACTER_SCALE			: natural := 1;
		CHAR_ROM_SIZE_X			: natural := 8;		-- Pixels
		CHAR_ROM_SIZE_Y			: natural := 16;	-- Pixels
		CHAR_ROM_DEPTH			: natural := 256;	-- Chars
		MAX_CHAR_PER_LINE		: natural := 32;	-- Chars
		MAX_LINE_PER_FRAME		: natural := 16;	-- Chars
		CHAR_SPACING_X			: natural := 0; 	-- Pixels
		CHAR_SPACING_Y			: natural := 0;		-- Pixels
		SCREEN_OFFSET_X			: natural := 16;	-- Pixels
		SCREEN_OFFSET_Y			: natural := 16		-- Pixels
	);
	Port
	(
		-- System signals
		rst					: in std_logic;
		vga_clk				: in std_logic;
		sys_clk				: in std_logic;
		ce					: in std_logic;
		frame_pulse			: in std_logic;

		
		-- Host Signals
		clr_screen			: in std_logic;
		clr_line			: in std_logic;
		din					: in unsigned(7 downto 0);
		dout				: out unsigned(7 downto 0);
		ascii_we			: in std_logic;
		posy_we				: in std_logic;
		posx_we				: in std_logic;
		ready				: out std_logic;
		
		-- Control signals
		scan_pos_x_in		: in natural range 0 to tsvga.ts_h.ncyc_scan-1;
		scan_pos_y_in		: in natural range 0 to tsvga.ts_v.ncyc_scan-1;
		
		-- Status signals
		csr_pos_x_out		: out natural range 0 to MAX_CHAR_PER_LINE-1;
		csr_pos_y_out		: out natural range 0 to MAX_LINE_PER_FRAME-1;

		-- Output 
		char_draw_en		: out std_logic
		
	);
end char_gen;

architecture Behavioral of char_gen is

	constant A_RAM_ADDR_WIDTH 		: integer := 12;
	constant A_RAM_DATA_WIDTH 		: integer := 8;

	type offset_rom_t is array (0 to MAX_LINE_PER_FRAME-1) of unsigned(A_RAM_ADDR_WIDTH-1 downto 0);

	

	function gen_offset_rom(num_char_per_line, num_lines : natural) return offset_rom_t is
	variable result : offset_rom_t;

	variable offset : unsigned(A_RAM_ADDR_WIDTH-1 downto 0) := (others => '0');
	begin
		for i in 0 to num_lines-1 loop
			result(i) := offset;
			offset := offset + to_unsigned(num_char_per_line, A_RAM_ADDR_WIDTH-1);
		end loop;
		return result;
	end gen_offset_rom;
	
	constant offset_rom : offset_rom_t := gen_offset_rom(MAX_CHAR_PER_LINE, MAX_LINE_PER_FRAME);
		
	-- Calculated parameters
	type cg_state_t is (cg_init, cg_ready, cg_clr_line, cg_clr_screen);
	signal s, sn : cg_state_t;
	
	constant CELL_SIZE_X			: natural := (CHAR_ROM_SIZE_X+CHAR_SPACING_X)*CHARACTER_SCALE;
	constant CELL_SIZE_Y			: natural := (CHAR_ROM_SIZE_Y+CHAR_SPACING_Y)*CHARACTER_SCALE;
	constant CHAR_SIZE_X			: natural := CHAR_ROM_SIZE_X*CHARACTER_SCALE;
	constant CHAR_SIZE_Y			: natural := CHAR_ROM_SIZE_Y*CHARACTER_SCALE;
	
	signal line_offset				: natural range 0 to MAX_LINE_PER_FRAME-1;
	signal cell_x_cnt				: natural range 0 to MAX_CHAR_PER_LINE-1;
	signal cell_y_cnt				: natural range 0 to MAX_LINE_PER_FRAME-1;
	signal char_x_cnt				: natural range 0 to CELL_SIZE_X-1;
	signal char_y_cnt				: natural range 0 to CELL_SIZE_Y-1;
	signal char_rom_row				: unsigned(NextExpBaseTwo(CHAR_SIZE_Y)-1 downto 0);
	signal char_rom_addr			: unsigned(NextExpBaseTwo(CHAR_ROM_DEPTH*CHAR_ROM_SIZE_Y)-1 downto 0);
	signal char_rom_data			: unsigned(CHAR_ROM_SIZE_X-1 downto 0);
	signal char_reg					: unsigned((CHARACTER_SCALE*CHAR_ROM_SIZE_X)-1 downto 0);
	
	signal a_addr_rd				: unsigned(A_RAM_ADDR_WIDTH-1 downto 0);
	signal a_addr_wr				: unsigned(A_RAM_ADDR_WIDTH-1 downto 0);
	signal a_data_rd				: unsigned(A_RAM_DATA_WIDTH-1 downto 0);
	signal a_data_wr				: unsigned(A_RAM_DATA_WIDTH-1 downto 0);
	signal a_ram_we					: std_logic;
	signal a_cnt					: natural range 0 to 2**A_RAM_ADDR_WIDTH-1;
	signal a_cnt_rst				: std_logic;
	signal a_cnt_en					: std_logic;
	signal csr_posx_reg				: natural range 0 to MAX_CHAR_PER_LINE-1;
	signal csr_posy_reg				: natural range 0 to MAX_LINE_PER_FRAME-1;
	signal a_data_reg				: unsigned(A_RAM_DATA_WIDTH-1 downto 0);
	signal a_data_we				: std_logic;
	signal csr_posx_overflow		: std_logic;
	signal csr_posx_cnt_din			: natural range 0 to MAX_CHAR_PER_LINE-1;
	signal csr_posx_cnt_set			: std_logic;
	signal csr_posx_cnt_en			: std_logic;
	signal csr_posy_overflow		: std_logic;
	signal csr_posy_cnt_din			: natural range 0 to MAX_LINE_PER_FRAME-1;
	signal csr_posy_cnt_set			: std_logic;
	signal csr_posy_cnt_en			: std_logic;

	signal line_addr				: unsigned(A_RAM_ADDR_WIDTH-1 downto 0);
	signal char_addr				: unsigned(A_RAM_ADDR_WIDTH-1 downto 0);
	
	-- Visible Cursor
	constant CURSOR_BLINK_INTERVAL	: natural := tsvga.nfps/2;

	type cursor_rom_t is array (0 to 2*CHAR_ROM_SIZE_Y-1) of unsigned (CHAR_ROM_SIZE_X-1 downto 0);
	constant cursor_rom				: cursor_rom_t :=
	(
		-- Cursor off
		"00000000",
		"00000000",
		"00000000",
		"00000000",
		"00000000",
		"00000000",
		"00000000",
		"00000000",
		"00000000",
		"00000000",
		"00000000",
		"00000000",
		"00000000",
		"11111111",
		"11111111",
		"11111111",
		
		-- Cursor on
		"00000000",
		"00000000",
		"11111111",
		"11111111",
		"11111111",
		"11111111",
		"11111111",
		"11111111",
		"11111111",
		"11111111",
		"11111111",
		"11111111",
		"11111111",
		"00000000",
		"00000000",
		"00000000"

	);
	signal cursor_rom_addr			: unsigned(NextExpBaseTwo(2*CHAR_ROM_SIZE_Y)-1 downto 0);
	signal cursor_rom_data			: unsigned(CHAR_ROM_SIZE_X-1 downto 0);
	signal cursor_visible			: std_logic;
	signal cursor_cnt				: natural range 0 to CURSOR_BLINK_INTERVAL-1;
	signal csr_posx_synced			: natural range 0 to MAX_CHAR_PER_LINE-1;
	signal csr_posy_synced			: natural range 0 to MAX_LINE_PER_FRAME-1;
		
	-- Scrolling
	signal scroll_offset			: natural range 0 to MAX_LINE_PER_FRAME-1;
	signal scroll_active			: std_logic;


begin

	a_addr_rd 		<= to_unsigned(cell_x_cnt, a_addr_rd'length) + offset_rom(line_offset);
	line_addr		<= offset_rom(csr_posy_reg);
	char_addr		<= to_unsigned(csr_posx_reg, char_addr'length);
	char_rom_addr	<= a_data_rd & char_rom_row(NextExpBaseTwo(CHAR_SIZE_Y)-1 downto (CHARACTER_SCALE-1));
	cursor_rom_addr	<= cursor_visible & char_rom_row(NextExpBaseTwo(CHAR_SIZE_Y)-1 downto (CHARACTER_SCALE-1));
	csr_pos_x_out	<= csr_posx_reg;
	csr_pos_y_out	<= csr_posy_reg;
	
	inst_char_rom : entity work.char_rom
	PORT MAP
	(
	    CLK         => vga_clk,
	    ADDR        => char_rom_addr,
	    DATA        => char_rom_data
    );

	inst_ascii_ram: entity work.dpram_1w1r2c_ro
	GENERIC MAP (
		addr_width	=> A_RAM_ADDR_WIDTH,
		data_width	=> A_RAM_DATA_WIDTH
	)
	PORT MAP(
		clk_a		=> sys_clk,
		clk_b		=> vga_clk,
		we_a		=> a_ram_we,
		re_b		=> '1',
		addr_a		=> a_addr_wr,
		addr_b		=> a_addr_rd,
		din_a		=> a_data_wr,
		dout_b		=> a_data_rd
	);

	---------------------------------------------------------------------------------------------
	--- ASCII RAM control
	---------------------------------------------------------------------------------------------
	asci_ram_fsm: 
	process (s, a_cnt, clr_line, clr_screen, a_data_we, a_data_reg, char_addr, line_addr)
	begin
		sn			<= s;
		a_cnt_rst	<= '0';
		a_cnt_en	<= '0';
		a_ram_we 	<= '0';
		a_data_wr 	<= a_data_reg;
		a_addr_wr	<= char_addr + line_addr;
		ready 		<= '0';
		
		case s is
		
		when cg_init =>
			sn <= cg_clr_screen;
			a_cnt_rst <= '1';
		when cg_ready =>
			ready		<= '1';
			a_ram_we	<= a_data_we;
			if clr_screen = '1' then
				a_cnt_rst <= '1';
				sn <= cg_clr_screen;
			elsif clr_line = '1' then
				a_cnt_rst <= '1';
				sn <= cg_clr_line;
			end if;	
		when cg_clr_line =>
			a_ram_we <= '1';
			a_cnt_en <= '1';
			a_data_wr <= to_unsigned(0, A_RAM_DATA_WIDTH);
			a_addr_wr <= to_unsigned(a_cnt + to_integer(line_addr), A_RAM_ADDR_WIDTH);
			if a_cnt = MAX_CHAR_PER_LINE-1 then
				sn <= cg_ready;
			end if;	
		when cg_clr_screen =>
			a_ram_we <= '1';
			a_cnt_en <= '1';
			a_data_wr <= to_unsigned(0, A_RAM_DATA_WIDTH);
			a_addr_wr <= to_unsigned(a_cnt, A_RAM_ADDR_WIDTH);
			if a_cnt = (2**A_RAM_ADDR_WIDTH-1) then
				sn <= cg_ready;
			end if;	
				
		when others =>
			sn <= cg_ready;
		
		end case;
	end process;
		
	asci_ram_fsm_next: process (sys_clk)
	begin
		if rising_edge(sys_clk) then
			if rst = '1' then
				s <= cg_init;
			else	
				s <= sn;
			end if;	
		end if;	
	end process;

	---------------------------------------------------------------------------------------------
	asci_ram_cnt: process (sys_clk)
	begin
		if rising_edge(sys_clk) then
			if a_cnt_rst = '1' then
				a_cnt <= 0;
			elsif a_cnt_en = '1' then
				if a_cnt < (2**A_RAM_ADDR_WIDTH-1) then
					a_cnt <= a_cnt + 1;
				end if;	
			end if;	
		end if;	
	end process;

	---------------------------------------------------------------------------------------------
	asci_addr_reg: process (sys_clk)
	begin
		if rising_edge(sys_clk) then
			a_data_we <= '0';
			csr_posx_cnt_en <= '0';
			csr_posx_cnt_set <= '0';
			csr_posy_cnt_en <= '0';
			csr_posy_cnt_set <= '0';
			if rst = '1' then
				a_data_reg <= (others => '0');
			elsif posy_we = '1' then
				csr_posy_cnt_din <= to_integer(din);
				csr_posy_cnt_set <= '1';
			elsif posx_we = '1' then
				csr_posx_cnt_din <= to_integer(din);
				csr_posx_cnt_set <= '1';
			elsif ascii_we = '1' then
				dout <= din;	-- written char is simply echoed back to dout
				if din < X"20" then
					if din = X"0D" then
						csr_posx_cnt_din <= 0;
						csr_posx_cnt_set <= '1';
					elsif din = X"0A" then	
						csr_posy_cnt_en <= '1';
					end if;	
				else
					a_data_reg <= din - X"20";
					a_data_we <= '1';
					csr_posx_cnt_en <= '1';
				end if;	
			end if;	
		end if;	
	end process;

	asci_csr_posx_counter:
	process (sys_clk)
	begin
		if rising_edge(sys_clk) then
			csr_posx_overflow <= '0';
			if rst = '1' then
				csr_posx_reg <= 0;
			elsif csr_posx_cnt_set = '1' then
				csr_posx_reg <= csr_posx_cnt_din;
			elsif csr_posx_cnt_en = '1' then	
				if csr_posx_reg < MAX_CHAR_PER_LINE-1 then
					csr_posx_reg <= csr_posx_reg + 1;
				else	
					csr_posx_reg <= 0;
					csr_posx_overflow <= '1';
				end if;	
			end if;
		end if;
	end process;
	
	asci_csr_posy_counter:
	process (sys_clk)
	begin
		if rising_edge(sys_clk) then
			csr_posy_overflow <= '0';
			if rst = '1' then
				csr_posy_reg <= 0;
			elsif csr_posy_cnt_set = '1' then
				csr_posy_reg <= csr_posy_cnt_din;
			elsif csr_posy_cnt_en = '1' or csr_posx_overflow = '1' then	
				if csr_posy_reg < MAX_LINE_PER_FRAME-1 then
					csr_posy_reg <= csr_posy_reg + 1;
				else	
					csr_posy_reg <= 0;
					csr_posy_overflow <= '1';
				end if;	
			end if;
		end if;
	end process;
	
	scroll_gen: process (sys_clk)
	begin
		if rising_edge(sys_clk) then
			if rst = '1' or clr_screen = '1' then
				scroll_offset <= 0;
				scroll_active <= '0';
			elsif csr_posy_cnt_set = '1' then
				scroll_active <= '0';
			elsif csr_posy_overflow = '1' then
				scroll_active <= '1';
				scroll_offset <= 1;
			end if;
			if (csr_posy_cnt_en = '1' or csr_posx_overflow = '1') and scroll_active = '1' then
				scroll_offset <= scroll_offset + 1;
			end if;	
		end if;	
	end process;

	---------------------------------------------------------------------------------------------
	--- Cursor generator
	---------------------------------------------------------------------------------------------
	proc_sync_csr_posx:
	process(vga_clk)
	variable p1, p2	: natural range 0 to MAX_CHAR_PER_LINE-1;
	begin
		if rising_edge(vga_clk) then
			csr_posx_synced <= p2;
			p2 := p1;
			p1 := csr_posx_reg;
		end if;
	end process;

	proc_sync_csr_posy:
	process(vga_clk)
	variable p1, p2	: natural range 0 to MAX_LINE_PER_FRAME-1;
	begin
		if rising_edge(vga_clk) then
			csr_posy_synced <= p2;
			p2 := p1;
			p1 := csr_posy_reg;
		end if;
	end process;

	csr_blink_cnt: process (rst, vga_clk)
	begin
		if rst = '1' then
			cursor_cnt <= 0;
			cursor_visible <= '0';
		elsif rising_edge(vga_clk) then
			if frame_pulse = '1' then
				if cursor_cnt < CURSOR_BLINK_INTERVAL-1 then
					cursor_cnt <= cursor_cnt + 1;
				else
					cursor_cnt <= 0;
					cursor_visible <= not cursor_visible;
				end if;
			end if;	
		end if;	
	end process;

	cursor_rom_read: process (vga_clk)
	begin
		if rising_edge(vga_clk) then
			cursor_rom_data <= (others => '0');
			if (cell_x_cnt = csr_posx_synced) and (line_offset = csr_posy_synced) then
				cursor_rom_data <= cursor_rom(to_integer(cursor_rom_addr));
			end if;	
		end if;
	end process;
		
	---------------------------------------------------------------------------------------------
	--- Character generator
	---------------------------------------------------------------------------------------------
	char_rom_ctrl: process (vga_clk)
	begin
		if rising_edge(vga_clk) then
			char_rom_row <= to_unsigned(char_y_cnt, char_rom_row'length);
		end if;	
	end process;

	---------------------------------------------------------------------------------------------
	char_reg_ctrl: process (vga_clk)
	begin
		if rising_edge(vga_clk) and ce = '1' then
			if char_x_cnt = 2 then	-- +2 delay for ASCII-RAM access and CHAR-ROM access
				for i in 0 to CHAR_ROM_SIZE_X-1 loop
					for j in 0 to CHARACTER_SCALE-1 loop
						char_reg(i*CHARACTER_SCALE + j) <= char_rom_data(i) xor cursor_rom_data(i);
					end loop;
				end loop;	
			else
				char_reg <= char_reg(char_reg'left-1 downto 0) & '0';
			end if;	
		end if;
	end process;
	
	---------------------------------------------------------------------------------------------
	cg_ctrl: process (rst, vga_clk)
	variable enable_x : std_logic;
	variable enable_y : std_logic;
	variable char_en : std_logic;
	variable char_en2 : std_logic;
	begin
		
		if (rst = '1') then
			enable_x		:= '0';
			enable_y		:= '0';
			line_offset		<= 0;
			cell_x_cnt		<= 0;
			cell_y_cnt		<= 0;
			char_x_cnt		<= 0;
			char_y_cnt		<= 0;
			char_en			:= '0';
		elsif rising_edge(vga_clk) and ce = '1' then
			char_draw_en	<= char_en2 and char_reg(char_reg'left);
			char_en2		:= char_en;
			char_en			:= '0';
			if scan_pos_y_in = SCREEN_OFFSET_Y then
				enable_y 	:= '1';
				cell_y_cnt	<= 0;
				char_x_cnt	<= 0;
				line_offset	<= scroll_offset;
			end if;	
			if scan_pos_x_in = SCREEN_OFFSET_X and enable_y = '1' then
				enable_x 	:= '1';
				cell_x_cnt	<= 0;
				char_x_cnt	<= 0;
			elsif enable_x = '1' and enable_y = '1' then
				if char_x_cnt < CELL_SIZE_X-1 then
					char_x_cnt <= char_x_cnt + 1;
				else	
					char_x_cnt		<= 0;
					if cell_x_cnt < MAX_CHAR_PER_LINE-1 then
						cell_x_cnt <= cell_x_cnt + 1;
					else	
						enable_x	:= '0';
						if char_y_cnt < CELL_SIZE_Y-1 then
							char_y_cnt <= char_y_cnt + 1;
						else
							char_y_cnt		<= 0;
							if cell_y_cnt < MAX_LINE_PER_FRAME-1 then
								cell_y_cnt <= cell_y_cnt + 1;
							else
								enable_y	:= '0';
							end if;	
							if line_offset < MAX_LINE_PER_FRAME-1 then
								line_offset <= line_offset + 1;
							else
								line_offset	<= 0;
							end if;	
						end if;	
					end if;	
				end if;	
				if (char_x_cnt < CHAR_SIZE_X) and (char_y_cnt < CHAR_SIZE_Y) then
					char_en :=  '1';
				end if;
			end if;	
		end if;
	end process;

	---------------------------------------------------------------------------------------------
end Behavioral;
