VGA controller with VHDL

osuarez picture osuarez · Oct 15, 2014 · Viewed 12.1k times · Source

I'm trying to learn VHDL programming with some books and an Altera DE1 development kit from Terasic.

The issue here is that I am trying to program a VGA controller for work with a resolution of 640x480 (although my screen is an TFT LCD with 1280x1024).

I am having some problems with the code.

I am using a FSM to make the vertical and horizontal signals, another block to drive the RGB inputs and also a 27 MHz clock from the DE1.

I think there has to be something wrong with the code because the image I print on the screen is not the correct size (640x480) but bigger (approx. 1174x980).

By now I am trying to put one only color in the screen to simplify it until I can discover the mistake.

My project has 3 files, 1 for the block RGB, 1 for the FSM and another one to instantiate both of them.

I would appreciate some kind of help to solve this problem because I have tried hard to figure it out but I can't see where are the mistakes.

Thanks a lot!

Omar

VGA Controller file

library ieee;
use ieee.std_logic_1164.all;

entity VGA_controller is
     port(clk, reset : in std_logic;
        Hsync,Vsync : out std_logic;
        R,G,B : out std_logic_vector(3 downto 0));
end entity;

architecture arch of VGA_controller is

component FSM_sync is
    port(clk,reset : in std_logic;
        Hsync,Vsync,VIDON : out std_logic;
        Vcount,Hcount : out integer range 0 to 799);
end component;

component VGA_display is
    port(hcount,vcount : in integer range 0 to 799;
        r,g,b : out std_logic_vector(3 downto 0);
        video_on : in std_logic);
end component;

signal video : std_logic;
signal signal1 : integer range 0 to 799;
signal signal2 : integer range 0 to 799;

begin

maquinaestado_sync : FSM_sync port map (clk=>clk, reset=>reset, Hsync=>Hsync, Vsync=>Vsync, Vcount=>signal1, Hcount=>signal2, VIDON=>video);

salida_pantalla : VGA_display port map (r=>R, g=>G, b=>B, video_on=>video, vcount=>signal1, hcount=>signal2 );

end arch;

FSM sync file

library ieee;
use ieee.std_logic_1164.all;


entity FSM_sync is
    port(clk,reset : in std_logic;
        Hsync,Vsync,VIDON : out std_logic;
        Vcount,Hcount : out integer range 0 to 799);
end entity;


architecture arch of FSM_sync is
--constantes para definir los ciclos de reloj de cada señal del HSYNC
constant counterMAX : integer := 640;
constant counterSP : integer := 96;
constant counterBP : integer := 48;
constant counterHV : integer := 640;
constant counterFP : integer := 16;
--constantes para definir los ciclos de reloj de cada señal del VSYNC
constant counterMAX_V : integer := 384000;  -- calculamos estos valores multiplicando el numero de filas por los pixeles en cada fila horizontal (800)
constant counterSP_V : integer := 1600;     -- de manera que cada estado de la sincronizacion vertical dure todo el recorrido de los pixeles de cada fila
constant counterBP_V : integer := 26400;
constant counterVV : integer := 384000;
constant counterFP_V : integer := 8000;
--constantes para el numero de pixeles maximo que debemos controlar en horizontal y en vertical
constant number_pixelsMAX_H : integer := 800;
constant number_pixelsMAX_V : integer := 525;

type state is (SP_1,BP,HV,FP,reseteo);  --4 estados para cada maquina de estado de sincronizacion (vertical y horizontal)
signal present_state_H,next_state_H,present_state_V,next_state_V : state;

signal timer : integer range 0 to counterMAX ; -- señal para pasar el valor counterXX al proceso secuencial para compararlo con un contador y establecer el
                                                -- tiempo de duracion de cada estado
signal timer2 : integer range 0  to counterMAX_V ;  --lo mismo que la señal anterior pero para el sincronizacion vertical

signal video_1,video_2 : std_logic; 

signal hcount_reg,vcount_reg : integer range 0 to 799;

begin

--==============================================
--FSM para la sincronizacion del barrido HORIZONTAL
--===============================================

lower_part_1 : process (clk,reset)
                variable counter : integer range 0 to counterMAX - 1;   --variable para crear un contador de pulsos del clk
                variable counter2 : integer range 0 to number_pixelsMAX_H - 1;          --contador para los pixeles horizontales    
                variable counter3 : integer range 0 to number_pixelsMAX_V - 1;          --contador para los pixeles verticales
             begin                                                  --se cargan con 800 por que 800 son los pixeles que hay que leer en horizontal 
                if (reset = '1') then                               --esto implica contadores de al menos 10 bits para llegar a ese numero.
                                                                    --y para que los dos contadores sean del mismo numero de bits se cargan los dos igual   
                    counter := 0;                                   --realmente en vertical solo debemos contar hasta 521
                    counter2 := 0;          
                    counter3 := 0;
                    present_state_H <= reseteo;


                elsif (clk'event and clk = '1') then
                    counter := counter + 1;

                    if (counter2 < number_pixelsMAX_H-1) then
                        counter2 := counter2 + 1;
                    else
                        counter2 := 0;
                        if (counter3 < number_pixelsMAX_V-1) then
                            counter3 := counter3 + 1;
                        else
                            counter3 := 0;
                        end if;
                    end if;

                    hcount_reg <= counter2;
                    vcount_reg <= counter3;

                    if (counter = timer) then
                        present_state_H <= next_state_H;
                        counter := 0;
                    end if;
                end if;
                end process lower_part_1;


upper_part_1 : process (next_state_H)
             begin

                Hsync <= '1';
                next_state_H <= HV;

                case present_state_H is
                    when SP_1 =>
                        Hsync <= '0';
                        next_state_H <= BP;
                        timer <= counterSP;

                        video_1 <= '0';

                    when BP =>
                        Hsync <= '1';
                        next_state_H <= HV;
                        timer <= counterBP;

                        video_1 <= '0';

                    when HV =>
                        Hsync <= '1';
                        next_state_H <= FP;
                        timer <= counterHV;

                        video_1 <= '1';


                    when FP =>
                        Hsync <= '1';
                        next_state_H <= SP_1;
                        timer <= counterFP;

                        video_1 <= '0';

                    when reseteo =>
                        Hsync <= '1';
                        next_state_H <=HV;



                    end case;
                end process upper_part_1;

--==============================================
--FSM para la sincronizacion del barrido VERTICAL
--===============================================               


lower_part_2 : process (clk,reset)
                variable counter2 : integer range 0 to counterMAX_V;    --variable para crear un contador de pulsos del clk
             begin
                if (reset = '1') then
                    counter2 := 0;
                    present_state_V <= reseteo;



                elsif (clk'event and clk = '1') then
                    counter2 := counter2 + 1;



                    if (counter2 = timer2) then
                        present_state_V <= next_state_V;
                        counter2 := 0;
                    end if;
                end if;
                end process lower_part_2;


upper_part_2 : process (next_state_V)
             begin

                Vsync <= '1';
                next_state_V <= HV;

                case present_state_V is
                    when SP_1 =>
                        Vsync <= '0';
                        next_state_V <= BP;
                        timer2 <= counterSP_V;
                        video_2 <= '0';
                    when BP =>
                        Vsync <= '1';
                        next_state_V <= HV;
                        timer2 <= counterBP_V;

                        video_2 <= '0';

                    when HV =>
                        Vsync <= '1';
                        next_state_V <= FP;
                        timer2 <= counterVV;

                        video_2 <= '1';

                    when FP =>
                        Vsync <= '1';
                        next_state_V <= SP_1;
                        timer2 <= counterFP_V;

                        video_2 <= '0';

                    when reseteo =>
                        Vsync <= '1';
                        next_state_V <=HV;


                    end case;
                end process upper_part_2;


VIDON <= video_1 AND video_2;
Vcount <= vcount_reg;
Hcount <= hcount_reg;

        end arch;

VGD display file

library ieee;
use ieee.std_logic_1164.all;


entity VGA_display is
    port(hcount,vcount : in integer range 0 to 799;
        r,g,b : out std_logic_vector(3 downto 0);
        video_on : in std_logic);
end entity;



architecture arch of VGA_display is

begin

process (video_on)
begin
    if video_on = '1' then  --solo activamos los pixeles cuando vidon esté a uno, es decir, esten en la fase HV y VV las sincronizaciones
            r <= "1111";
            g <= "0000"; 
            b <= "0000";

    else
        r <= (others => '0');
        g <= (others => '0');
        b <= (others => '0');
    end if;

end process;
end arch;

Answer

user1155120 picture user1155120 · Oct 16, 2014

I think your monitor is syncing to your output and simply displaying the video full screen, much as your TV might with an auxiliary input.

I worked out your H and V rates in terms of your reported 27 MHz (~ 37 ns) and see if I could find a 1280 x 1024 LCD monitor that would accept them.

Your horizontal counter is counter2 which counts from 0 to number_pixelsMAX_H - 1(799), and your vertical counter is counter3 which counts from 0 to number_pixelsMAX_V - 1 (524).

That gives you a horizontal rate of 800 x 37 ns = 29.600 us and a vertical rate of 15.54 ms. 33.8 KHz H, V 64 Hz.

Without knowing the specific monitor you're using I looked around for any spec for one and found an SXGA AMOLED display spec that defines the sync rate range.

On page 14 there's a table showing the H and V ranges:

Table 6-4 AC Characteristics

It shows that this particular LCD panel could display your frame rate. How the pixel clock for it would be recovered from sync signals is dependent on a monitor build using this particular display.

All versions of the DE1 board appear to use the same ADV7123 to convert your SVGA signal to analog transmitted across a 15 pin VGA connector.

I think it highly likely your SXGA rate monitor capable of displaying SVGA.

It looks like an Acer 17 inch V173L will sync to it for instance having 3 VGA modes (60 Hz, 72 Hz, and 75 Hz) and a Mac mode for 640 x 480 (66 Hz).

The ability to synchronize to multiple scan rates is very common in monitors these days.

My six year old Dell 2208WP is telling me I'm inputting a DVI-D signal of 1680x1050 at 60 Hz. I can tell my Mac to switch resolution and will faithfully follow, adhering to presets conveyed by the DVI interface, in this case limited to 1024x640 (which resized all my open windows, flattening them).

Depending on your monitor you should get multi-syncing across a wider range with an analog input (which the DE1 provides).

The mechanism that makes it work for an analog video input is the pixel clock to the LCD panel is related to the un-blanked portion of a horizontal line, delivering a number of pixel clocks during that interval that matches the panel resolution, re-converting analog video to digital at a rate that fills all (or nearly all) the screen pixels.