----------------------------------------------------------------------------------
-- Company:        c't magazin
-- Engineer:       Carsten Meyer
-- 
-- Create Date:    14:11:15 06/25/2009 
-- Design Name: 
-- Module Name:    lcd_timing - Behavioral 
-- Project Name: 
-- Target Devices: 
-- Tool versions: 
-- Description:    Timing-Generator für unintelligentes LC-Display QVGA sw
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

---- Uncomment the following library declaration if instantiating
---- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity lcd_timing is
    Port ( SYSCLK50 : in  STD_LOGIC;	-- Systemtakt 50 MHz
           font_data : in  STD_LOGIC_VECTOR (7 downto 0);	-- Daten vom Font-ROM 12 Zeilen/Zeichen
           text_data : in  STD_LOGIC_VECTOR (7 downto 0);	-- ASCII-Wert vom Textspeicher (1 KByte)
           text_inverse : in  STD_LOGIC;
           text_bright : in  STD_LOGIC;
           text_dim : in  STD_LOGIC;
           text_blink : in  STD_LOGIC;
           cursx : in  STD_LOGIC_VECTOR (7 downto 0);
           cursy : in  STD_LOGIC_VECTOR (7 downto 0);
           pixel_bright : in  STD_LOGIC;	-- Pixel-Grafik
           pixel_dim : in  STD_LOGIC;	-- Pixel-Grafik, halbe Leuchtkraft
           lcd_clk : out  STD_LOGIC:='0';
           lcd_load : out  STD_LOGIC:='0';
           lcd_frame : out  STD_LOGIC:='0';
           lcd_on : out  STD_LOGIC:='0';
           blink_out : out  STD_LOGIC:='0';
           vs60Hz_out : out  STD_LOGIC:='0';
           lcd_addr : out  STD_LOGIC_VECTOR (14 downto 0);	-- absolute Nibble-Adresse für Vollgrafik
           text_addr : out  STD_LOGIC_VECTOR (9 downto 0);	-- 960 Zeichen möglich
           pixel_column : out  STD_LOGIC_VECTOR (8 downto 0);	-- Pixel-Spalte 0..319
           pixel_line : out  STD_LOGIC_VECTOR (7 downto 0);	-- Pixel-Zeile 0..239
           pixel_clk : out  STD_LOGIC;	-- Pixel-Takt
           font_addr : out  STD_LOGIC_VECTOR (11 downto 0);
           lcd_data : out  STD_LOGIC_VECTOR (3 downto 0)
		   );
end lcd_timing;

architecture Behavioral of lcd_timing is

constant screen_h: integer:= (320/4)-1;	-- Breite 320 Pixel, 4 Bit pro lcd_clk
--constant screen_h_reset: STD_LOGIC_VECTOR(6 DOWNTO 0) :="0000001";	-- =+1
constant col_h: integer:= 40-1;	-- Breite 40 Zeichen
constant screen_v: integer:= 240-1;	-- Höhe 240 Pixel

signal clk_count : STD_LOGIC_VECTOR(3 DOWNTO 0) :="0000";
signal frame_count : STD_LOGIC_VECTOR(4 DOWNTO 0) :="00000";
signal h_count, v_count : STD_LOGIC_VECTOR(7 DOWNTO 0) :="00000000";
signal pixelblock_count : STD_LOGIC_VECTOR(15 DOWNTO 0) :=x"0000";
signal pixel_column_count : STD_LOGIC_VECTOR(6 DOWNTO 0) :="0000000";	-- wird um zwei Bits aus clk_count ergänzt
signal pixel_line_count : STD_LOGIC_VECTOR(7 DOWNTO 0) :="00000000";
signal frame_toggle : STD_LOGIC:='0';
signal frame_intern : STD_LOGIC:='0';
signal blink_toggle : STD_LOGIC:='0';
signal cursor_line_ena : STD_LOGIC:='0';
signal cursor_x_ena : STD_LOGIC:='0';
signal cursor_y_ena : STD_LOGIC:='0';
signal dark_duty : STD_LOGIC:='0';	-- Duty Cycles in Frames für Pixel- und Text-Helligkeit 33% und 66%
signal light_duty : STD_LOGIC:='0'; 
signal column_count: STD_LOGIC_VECTOR(5 DOWNTO 0) :="000000";
signal font_line_count: STD_LOGIC_VECTOR(3 DOWNTO 0) :="0000";	-- aktuelle Fontzeile 0..11 (max. 16)
signal text_line_count: STD_LOGIC_VECTOR(4 DOWNTO 0) :="00000";	-- aktuelle Textzeile 0..19 (max. 32)
signal cursor: STD_LOGIC_VECTOR(3 DOWNTO 0) :="0000";	-- Cursor Enable (blink_toggle)
signal text_flicker: STD_LOGIC_VECTOR(3 DOWNTO 0) :="1111";	-- durch Flickern Graustufen simulieren
signal graph_pixel : STD_LOGIC:='0';	-- aktuelles Grafik-Pixel mit Helligkeits-Flicker
signal lcd_data_temp: STD_LOGIC_VECTOR(3 DOWNTO 0) :="0000";	-- Textdaten, wird ggf. invertiert
signal lcd_data_graph, lcd_data_graph_temp: STD_LOGIC_VECTOR(3 DOWNTO 0) :="0000";	-- Grafikdaten
signal pixel_number: STD_LOGIC_VECTOR(1 DOWNTO 0) :="00";	-- 0 (links im Nibble) bis 3 (rechts im Nibble)

begin

-- für Grafik-Einheit
lcd_addr<=pixelblock_count(14 downto 0);	-- Absolut-Adresse Nibbles
pixel_number<=(not clk_count(3)) & clk_count(2);
pixel_column <= pixel_column_count & pixel_number;
pixel_clk <= clk_count(1);
pixel_line <= pixel_line_count;
blink_out <= blink_toggle;
vs60Hz_out <= frame_count(0);

-- Font-Adresse zusammenstellen aus ASCII-Wert mal 12 (text_data*12) und Font-Zeile
font_addr <= ('0' & text_data & "000") + (text_data & "00") + font_line_count;	-- text_data*12=text_data*8+text_data*4
-- Text-Adresse zusammenstellen aus Textzeile mal 40 (text_line_count*40) und Text-Spalte
text_addr <= ("00" & text_line_count & "00000") + (text_line_count & "000") + column_count;

-- Graustufe(n):

text_flicker <= "1111" when ((text_bright='1' and text_dim='1')		-- ganz hell
			or (text_bright='1' and text_dim='0' and light_duty='1')	-- helles Grau
			or (text_bright='0' and text_dim='1' and dark_duty='1'))	-- dunkles Grau
			else "0000";
-- Aktuelles Grafik-Pixel vom RAM (2 Bits) mit Flicker versehen
graph_pixel <= '1' when ((pixel_bright='1' and pixel_dim='1')		-- ganz hell
			or (pixel_bright='1' and pixel_dim='0' and light_duty='1')	-- helles Grau
			or (pixel_bright='0' and pixel_dim='1' and dark_duty='1'))	-- dunkles Grau
			else '0';

-- Multiplexer für Pixel-Daten, zwei Nibbles:
lcd_data_temp <= (font_data(3 downto 0) and text_flicker) xor cursor when pixelblock_count(0) = '1' 
	else (font_data(7 downto 4) and text_flicker) xor cursor;
lcd_data <= lcd_data_temp or lcd_data_graph when text_inverse='0' 
	else (lcd_data_temp xor "1111") or lcd_data_graph when  text_inverse='1';

-- Cursor:
cursor_line_ena <= '1' when ((font_line_count > 7) and blink_toggle = '1') else '0';
cursor_x_ena <= '1' when (cursx(5 downto 0) = column_count) and cursor_line_ena = '1' else '0';
cursor_y_ena <= '1' when (cursy(4 downto 0) = text_line_count) and cursor_line_ena = '1' else '0';
cursor <= "1111" when cursor_x_ena='1' and cursor_y_ena='1' and cursor_line_ena='1' else "0000";

process(clk_count(1)) begin	-- Pixel-Multiplexer/Latch für Grafik
if rising_edge(clk_count(1)) then
	case pixel_number is
		when "00" =>
			lcd_data_graph_temp(3) <= graph_pixel;
		when "01" =>
			lcd_data_graph_temp(2) <= graph_pixel;
		when "10" =>
			lcd_data_graph_temp(1) <= graph_pixel;
		when "11" =>
			lcd_data_graph_temp(0) <= graph_pixel;
		when others =>
		end case;
end if;
end process;

process(clk_count(3)) begin
-- timing and addresses for 320x240 display, 4 Bits pro lcd_clk
if rising_edge(clk_count(3)) then
	pixelblock_count <= pixelblock_count + 1;	-- nach 4 Pixeln erhöhen
	pixel_column_count <= pixel_column_count + 1;	-- nach 4 Pixeln erhöhen
	
-- Achtung: pixel_column ist durch Nibble-Load um 4 Pixel nach vorn verschoben!
	if pixel_column_count = screen_h then
		pixel_column_count <= "0000000";
	end if;
	lcd_data_graph <= lcd_data_graph_temp;
	if column_count = col_h and pixelblock_count(0)='0' then	-- 39 dez., also Ende der Zeile
		pixel_line_count <= pixel_line_count + 1;	-- nächste Pixelzeile 4 Pixel früher wg. Latch
	end if;
	
	if pixelblock_count(0)='1' then	-- Spaltenzähler letzter Stand
		if column_count = col_h then	-- 39 dez., also Ende der Zeile
			column_count <= "000000";
			pixel_column_count <= "0000001";	-- nach Zeilenende resetten
			if font_line_count = x"B" then	-- 12 Zeilen pro Zeichen
				font_line_count <= x"0";
				text_line_count <= text_line_count+1;
			else
				font_line_count <= font_line_count+1;
			end if;
		else
			column_count <= column_count+1;
		end if;
	end if;
	
	if h_count = screen_h then	-- 79=320/4-1
		h_count <= "00000000";
		if v_count = screen_v then	-- 240-1, Ende des Frames, alles resetten
			v_count <= x"00";
			h_count <= "00000000";
			pixelblock_count <= x"0000";
			font_line_count<=x"0";
			column_count<="000000";
			text_line_count <= "00000";
			pixel_line_count <= x"FF";
			frame_toggle <= not frame_toggle;
		else
			v_count <= v_count + 1;
		end if;
	else
		h_count <= h_count + 1;
	end if;
end if;
end process;

process(SYSCLK50) begin

if rising_edge(SYSCLK50) then
	clk_count <= clk_count+1;
	if clk_count="1111" then
		if h_count=screen_h then
			lcd_load<='1';
			if v_count=screen_v then
				lcd_frame<='1';
				frame_intern<='1';
			end if;
		end if;
	elsif clk_count="0111" then
		lcd_load<='0';
		lcd_clk<='1';
	elsif clk_count="1100" then
		lcd_clk<='0';
		lcd_frame<='0';
		frame_intern<='0';
	end if;
end if;
end process;

process(frame_intern) begin
-- Graustufen-Duty erzeugen
if rising_edge(frame_intern) then
	frame_count <= frame_count + 1;
	light_duty<= frame_count(1);
	if frame_count(1 downto 0) = "00" then
		dark_duty<= '0';	
	end if;
	if frame_count(1 downto 0) = "11" then
		dark_duty<= '1';
	end if;
	if frame_count = "11111" then
		lcd_on <= '1';	-- bleibt '1' bis Power Off
		blink_toggle <= not blink_toggle;
	end if;
end if;
end process;

end Behavioral;

