Ein VGA Controller in VHDL mit Xilinx Spartan 3/6

In VGA Controller wird ein VGA controller für ein Altera board vorgestellt.

Problemstellung

Die Entwicklungsboards BASYS2 und NEXYS3 von Digilent haben einen VGA Anschluss. Auf der Suche nach Quellcode für die Ansteuerung findet man meistens nur eine Auflösung von 640x480 Pixeln. Diese Auflösung wird von heutigen Monitoren und Beamern schlecht oder gar nicht dagestellt. Es soll ein VGA controller in VHDL für die folgenden Auflösungen entwickelt werden.
Auflösung Bildfrequenz
800 x 600 60Hz
1024 x 768 60Hz
1280 x 1024 60Hz
1366 x 768 60Hz
1680 x 1050 60Hz
1920 x 1080 60Hz

Messung des VGA Signals

Es wird das VGA Signal eines Laptops gemessen und die Werte mit Tabellen aus dem Internet verglichen. Auf den Leitungen VSYNC, HSYNC und R,G,B wird ein serieller Datenstrom erzeugt. Jedes Bild beginnt mit einem VSYNC Puls mit der Länge VR (vertical refresh) und der Periodendauer VR+VF+VD+VB. (VF: vertical front porch, VD: vertical display, VB: vertical back porch). Diese Werte werden in der Anzahl der Zeilen angegeben. Nur während VD werden das Bilddaten ausgegeben. Jede Zeile des Bildes beginnt mit einem HSYNC Puls mit der Länge HR und der Periodendauer HR+HF+HD+HB. Während der Zeit VD werden die einzelnen Pixel mit der Pixelfrequenz ausgegeben. Die Länge von HR, HF, HD und HB werden in Pixeln angegeben. Das nächste Bild zeigt das Zeitverhalten der Signale für 2 Zeilen mit jeweils 6 Pixeln.



Das folgende Bild zeigt die Steckerbelegung mit den HSYNC und VSYNC Signalen, Red, green, blue und Masse (GND).

Um das VGA Signal zu untersuchen wurde eine Y-Weiche and den VGA Ausgang eines Laptops angeschlossen und ein externer Monitor verbunden. Das VSYNC, HSYNC und ein Farbsignal wurden mit dem Electronic Explorer Board mit der Oszilloskopfunktion aufgenommen und gleichzeitig auf dem steuernden Laptop dargestellt. Die Messanordnung sieht man in folgendem Bild.

Messergebnisse

Im folgenden Bild sieht man das HSYNC C2 in Blau, das VSYNC Signal in Gelb und ein Farbsignal in Rot. Bei jedem Zeilenanfang wird das HSYNC Signal kurze Zeit 0V (Retrace). Das VSYNC Signal ist 0V da man mitten im Bild ist. Auf dem Farbsignal kann man gut die Farbinformation einer Zeile des Bildes sehen. Am Anfang einer Zeile im nicht sichtbaren Bereich ist das Signal 0V (back porch). Dann wird erst der blaue Hintergrund als Signal dargestellt, dann das weiße Fenster mit einem höheren Pegel, bevor wieder der Hintergrund kommt (display). Auch am Ende der Zeile befindet sich ein nicht sichtbarer Bereich (front porch). Man kann das Fenster auf dem Bildschirm von rechts nach links bewegen und sieht wie sich das Farbsignal verschiebt. Das HSYNC und VSYNC Signal haben einen Pegel von 3.3V. Die Farbinformation hat einen Pegel zwischen 0V und 0.7V. Dieser Pegel wird durch Widerstände aus dem digitalen 3.3V Signal erzeugt.


Im folgenden Bild sieht man mehrere Bilder (Frames). Für jedes Bild wird ein VSYNC generiert. Das HSYNC Signal hat eine viel höhere Frequenz und man sieht die vielen Zeilenpulse eines Bildes. Die Farbinformation repräsentiert ein vollständiges Bild. Veschiebt man das Fenster mit der Oszilloskopdarstellung von oben nach unten, bewegt sich auch das Farbsignal.


Es wurden folgende Zeiten gemessen:

Auflösung Bildfrequenz VSYNC length VSYNC period HSYNC length HSYNC period
800 x 600 60Hz 105.8 us 16.66 ms 3.2 us 26.45 us
1024 x 768 60Hz 124 us low 16.66 ms 2.1 us 20.7 us
1280 x 1024 60Hz 46.8 us 16.66 ms 1.035 us 15.6us
1366 x 768 60Hz 62.8 us 16.66 ms 1.64 us 20.93 us
1680 x 1050 60Hz 91.8 us 16.66 ms 1.23 us 15.27 us
1920 x 1080 60Hz 44.6 us 16.66 ms 1.19us 14.87 us

Entwicklungsboards


Es steht ein BASYS2 Board von Digilent mit einem Spartan3E 250k Gate FPGA von Xilinx (ca 90 Euro) zur Verfügung. Auf dem Board wird von einem Timer ein nicht stabiler Takt von 50MHz erzeugt (Pin B8). Es hat sich gezeigt, dass das Bild ausfranst und die Zeilen flackern. Deshalb wurde in den vorhandenen Sockel ein Quarzoszillator eingesetzt (Pin M6). Das Board stellt nur 3.3V zur Verfügung, so dass ein 3.3V SMD Quarzoszillator auf eine Platine gelötet wurde (Conrad, 155980 SMD-Quarz Oszillator XO53-Serie 50.000MHZ XO53050UITA ca 2.20 Euro) und in den Sockel eingesteckt wurde. Ein interner Clockmanager (DCM_SP), der mit dem IP Core Generator erzeugt wurde, erlaubt es den Takt anzupassen.

Das Nexys Board hat ein Spartan6 FPGA von Xilinx und einen Quarzstabilisierten 100MHz Takt. Der Coregenerator Wizard erzeugt eine PLL_BASE primitive für 1920x1080 172MHz und ein DCM_SP für 108 MHz.



Normalerweise leitet man den benötigten Systemtakt von der Pixelfrequenz ab. Werte für HD, HF, HB, HR, VD, VF, VB, VR findet man im http://www.tinyvga.com/vga-timing . In der folgenden Tabelle werden einige Auflösungen dargestellt.
In Klammern stehen die äquivalenten Werte für 50 MHz. Hier wird der feste Takt von 50MHz verwendet und entsprechend viele horizontale Pixel zusammengefasst.

Auflösung Bildfrequenz HD HF HB HR VD VF VB VR Pixelfrequenz
800 x 600 72 Hz 800 56 64 120 600 37 23 6 50MHz
1024 x 768 (681 x 768) 70Hz 1024 (681) 24 (16) 144 (96) 136(90) 768 3 29 6 75MHz (50MHz)
1280 x 1024 (640 x 1024) 60Hz 1280 (640) 48 (24) 248 (124) 112 (56) 1024 1 38 3 108MHz (50MHz)
1366 x 768 (683 x 768) 60Hz (70Hz) 1368 (683) 72 (36) 144 (72) 216 (108) 768 1 23 3 85.86MHz (50MHz)
1680 x 1050 (560 x 1050) 60Hz 1680 (560) 104 (35) 288 (96) 184 (61) 1050 1 33 3
1920 x 1080 (548 x 1080) 60Hz 1920 (548) 119 (34) 326 (93) 207 (59) 1080 1 32 3 172MHz (50MHz)

Der VGA Controller


Ein VGA Controller soll verschiedene Eingangstakte, Auflösungen und Testmuster unterstützen. Ein Hauptmodul erlaubt die Auswahl der Auflösung und der Testmuster. Ein VGA Modul erzeugt aus den gemessenen Timingparametern (HD, HF, HB, HR, VD, VF, VB, VR) ein VSYNC, HSYNC, Video_on und die Pixelposition pixel_x, pixel_y. Da es Zeiten gibt in denen kein Pixel dargestellt werden soll gibt es noch ein Video_on Signal.

Der VGA Controller soll mit einem Testmuster verifiziert werden. Dazu wird bei bestimmten x,y Zaehlerstand ein farbiges Pixel aus den die Rot, Grün und Blau Signalen erzeugt werden. Dabei stehen auf dem Entwicklungsboard 3 Bit für Rot und Grün zur Verfügung und 2 Bit für Blau. Damit stehen 256 Farben zur Verfügung. Diese werden mit Widerständen in ein analoges Signal (DA-Wandler) gewandelt.

Clock Manager


Im Xilinx FPGAs steht ein Clock Manager zur Verfügung, der den einen internen Takt erzeugen kann. Unter IP-Cores, FPGA Features and Design, Clocking kann man den externen Takt in eine geeignete Taktfrequenz mit dem Digital Frequency Synthesizer übersetzen.
Es gilt folgende Formel: CLKFX = (CLKFX_MULTIPLY_value/CLKFX_DIVIDE_value) * FrequencyCLKIN Es wird z.B. folgender VHDL code erzeugt, wenn man die Aktion view functional model durchführt.

entity CLK_Manager is
port
 (-- Clock in ports
  CLK_IN1           : in     std_logic;
  -- Clock out ports
  CLK_OUT1          : out    std_logic;
  -- Status and control signals
  RESET             : in     std_logic;
  LOCKED            : out    std_logic
 );
end CLK_Manager;

architecture xilinx of CLK_Manager is
  attribute CORE_GENERATION_INFO : string;
  -- One line which can not be broken
  attribute CORE_GENERATION_INFO of xilinx : architecture is "CLK_Manager,clk_wiz_v1_8,{component_name=CLK_Manager,use_phase_alignment=true,use_min_o_jitter=false,use_max_i_jitter=false,use_dyn_phase_shift=false,use_inclk_switchover=false,use_dyn_reconfig=false,feedback_source=FDBK_AUTO,primtype_sel=DCM_SP,num_out_clk=1,clkin1_period=10.0,clkin2_period=10.0,use_power_down=false,use_reset=true,use_locked=true,use_inclk_stopped=false,use_status=false,use_freeze=false,use_clk_valid=false,feedback_type=SINGLE,clock_mgr_type=AUTO,manual_override=false}";
	  -- Input clock buffering / unused connectors
  signal clkin1            : std_logic;
  -- Output clock buffering
  signal clkfb             : std_logic;
  signal clk0              : std_logic;
  signal clkdv             : std_logic;
  signal clkfbout          : std_logic;
  signal locked_internal   : std_logic;
  signal status_internal   : std_logic_vector(7 downto 0);
begin


  -- Input buffering
  --------------------------------------
  clkin1_buf : IBUFG
  port map
   (O => clkin1,
    I => CLK_IN1);


  -- Clocking primitive
  --------------------------------------
  -- Instantiation of the DCM primitive
  --    * Unused inputs are tied off
  --    * Unused outputs are labeled unused
  dcm_sp_inst: DCM_SP
  generic map
   (CLKDV_DIVIDE          => 2.000,
    CLKFX_DIVIDE          => 1,
    CLKFX_MULTIPLY        => 4,
    CLKIN_DIVIDE_BY_2     => FALSE,
    CLKIN_PERIOD          => 10.0,
    CLKOUT_PHASE_SHIFT    => "NONE",
    CLK_FEEDBACK          => "1X",
    DESKEW_ADJUST         => "SYSTEM_SYNCHRONOUS",
    PHASE_SHIFT           => 0,
    STARTUP_WAIT          => FALSE)
  port map
   -- Input clock
   (CLKIN                 => clkin1,
    CLKFB                 => clkfb,
    -- Output clocks
    CLK0                  => clk0,
    CLK90                 => open,
    CLK180                => open,
    CLK270                => open,
    CLK2X                 => open,
    CLK2X180              => open,
    CLKFX                 => open,
    CLKFX180              => open,
    CLKDV                 => clkdv,
   -- Ports for dynamic phase shift
    PSCLK                 => '0',
    PSEN                  => '0',
    PSINCDEC              => '0',
    PSDONE                => open,
   -- Other control and status signals
    LOCKED                => locked_internal,
    STATUS                => status_internal,
    RST                   => RESET,
   -- Unused pin, tie low
    DSSEN                 => '0');

  LOCKED                <= locked_internal;


  -- Output buffering
  -------------------------------------
  clkf_buf : BUFG
  port map
   (O => clkfb,
    I => clk0);


  clkout1_buf : BUFG
  port map
   (O   => CLK_OUT1,
    I   => clkdv);

end xilinx;
  

VHDL Code für einen VGA Controller

Als erstes der Code für den Controller.


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

entity vga_gen is
    generic(
    -- VGA 1920x1080 sync parameters 50MHz Clock 3.5 pixels at once -> 548 x 1080 bad ok
     HD: integer := 548; -- horizontal display area 640;  800
     HF: integer := 34;  -- h. front porch          (8) 16;     40
     HB: integer := 93;  -- h. back porch           (56) 48;    88 
     HR: integer := 59;  -- h. retrace              96;    128
	 -- VD+VF+VB+VR-1 -> 1087
     VD: integer := 1080; -- vertical display area   480;   600
     VF: integer := 1;  -- v. front porch          (2) 11;    4
     VB: integer := 32;  -- v. back porch           (41) 31;    23
     VR: integer := 3   -- v. retrace               (2) 2;    1
	 );
    port(
        clk, not_reset: in std_logic;
        hsync, vsync: out std_logic;
        video_on, p_tick: out std_logic;
        pixel_x, pixel_y: out std_logic_vector (11 downto 0)
    );
end vga_gen;

architecture Behavioral of vga_gen is
    -- 50 MHz Clock input
    -- sync counters
    signal v_count: std_logic_vector(11 downto 0);
    signal h_count: std_logic_vector(11 downto 0);
begin
    process(clk, not_reset)
    begin
        if not_reset = '0' then
            v_count <= (others => '0');
            h_count <= (others => '0');
        elsif clk'event and clk = '0' then
            if (h_count < (HD + HF + HB + HR)) then
                h_count <= h_count+1;
            else
                h_count <= (others => '0');
            end if;
            if (h_count = (HD + HF -1)) then
                if (v_count < (VD + VF + VB + VR)) then
                    v_count <= v_count +1;
                else
                    v_count <= (others => '0');
                end if;
            end if;
        end if;
    end process;

    -- horizontal and vertical sync
    hsync <= '0' when (h_count >= (HD + HF)) and
                      (h_count <= (HD + HF + HR - 1)) else
             '1';
    vsync <= '1' when (v_count > (VD + VF)) and
                      (v_count <= (VD + VF + VR)) else
             '0';
    -- video on/off
    video_on <= '1' when (h_count < HD) and (v_count < VD) else '0';

    -- output signal
    pixel_x <= h_count;
    pixel_y <= v_count;
    p_tick <= clk;
end Behavioral;
  

Programm mit Testmuster



Als Testmuster bietet sich ein Diagonalmuster an. Man kann die Auflösung an derAnzahl der Rauten abzählen. Weiterhin kann man am Rand überprüfen, dass das gesamte Muster nicht abgeschnitten wird



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

entity vga_Main is
    port(
        clk, not_reset: in std_logic;
        sw:std_logic_vector(3 downto 2);
        hsync, vsync: out std_logic;
        vgaRed: out std_logic_vector(2 downto 0);
        vgaGreen: out std_logic_vector(2 downto 0);
        vgaBlue: out std_logic_vector(2 downto 1);
        led: out std_logic_vector(7 downto 0)
        );
end vga_Main;

architecture Behavioral of vga_Main is
 -- sw(7) not_reset should be '1' for normal operation
 -- sw(3,2) "11" 1024x768 otherwise 800x600
	COMPONENT CLK_Manager
	PORT(
		CLK_IN1 : IN std_logic;
		RESET : IN std_logic;          
		CLK_OUT1 : OUT std_logic;
		LOCKED : OUT std_logic
		);
	END COMPONENT;
    COMPONENT vga_gen
    generic(
	    HD: integer := 548; -- horizontal display area 640;  800
        HF: integer := 34;  -- h. front porch          (8) 16;     40
        HB: integer := 93;  -- h. back porch           (56) 48;    88 
        HR: integer := 59;  -- h. retrace              96;    128
	    -- VD+VF+VB+VR-1 -> 1087
        VD: integer := 1080; -- vertical display area   480;   600
        VF: integer := 1;  -- v. front porch          (2) 11;    4
        VB: integer := 32;  -- v. back porch           (41) 31;    23
        VR: integer := 3   -- v. retrace               (2) 2;    1
	 );
    port(
        clk, not_reset: in std_logic;
        hsync, vsync: out std_logic;
        video_on, p_tick: out std_logic;
        pixel_x, pixel_y: out std_logic_vector (11 downto 0)
    );
    END COMPONENT;

    signal rgb_reg, rgb_next: std_logic_vector(2 downto 0);
    signal video_on,video_on0,video_on2: std_logic;
    signal px_x, px_y,px_x0, px_y0,px_x2, px_y2: std_logic_vector(11 downto 0);
    signal hsync0,vsync0,hsync2,vsync2: std_logic;
    signal clkx:std_logic;
    signal en,reset, p_tick,p_tick0, p_tick2: std_logic;
    signal sbtn: std_logic_vector(3 downto 0);
    signal graph_rgb: std_logic_vector(7 downto 0);

begin
	 reset<=not(not_reset);
    
	 led(7) <= not_reset;
	 led(6) <= px_y(6);
	 led(5) <= px_y(5);
	 led(4) <= reset;
	 led(3) <= p_tick;
	 led(2) <= video_on;
	 led(1) <= px_x(8);
	 led(0) <= px_x(8);
	 
	vgaRed <=graph_rgb(7 downto 5);
	vgaGreen <=graph_rgb(4 downto 2);
	vgaBlue <=graph_rgb(1 downto 0);
        
	vga0:
        vga_gen
        generic map(
		    HD => 681, HF => 16, HB=> 96, HR=> 90,  -- (1024) 681x 768 70Hz
		    VD=> 768, VF=> 3, VB=> 29, VR=> 6   
		  )
        port map(
            clk => clkx, not_reset => not_reset,
            hsync => hsync0, vsync => vsync0,
            video_on => video_on0, p_tick => p_tick0,
            pixel_x => px_x0, pixel_y => px_y0
        );
	vga2:
        vga_gen
        generic map(
		    HD => 800, HF => 56, HB=> 64, HR=> 120,  -- 800x600
		    VD=> 600, VF=> 37, VB=> 23, VR=> 6   
		)
        port map(
            clk => clkx, not_reset => not_reset,
            hsync => hsync2, vsync => vsync2,
            video_on => video_on2, p_tick => p_tick2,
            pixel_x => px_x2, pixel_y => px_y2
        );

   ----------------------------------------------
   -- Clock to pixel frequency with Clocking Wizard
   ----------------------------------------------
	Inst_CLK_y: CLK_Manager PORT MAP(
		CLK_IN1 => clk,
		CLK_OUT1 => clkx,
		RESET => reset,
		LOCKED => locked 
	);
	-- Alternative 1) Fixed external clock
	-- clkx <= clk;
	-- Alternative 2) Half external clock
    -- process(clk, not_reset) --- NEXYS3 100MHz transfers clk to 50MHz clkx
    -- begin
	--   if not_reset = '0' then
	--      clkx <='0';
	-- 	 elsif clk'event and clk='1' then -- generate clkx 50 MHz from clk 100MHz NEXYS3 board
	--	    clkx <= not(clkx);
    --   end if;
    -- end process;
	
   ----------------------------------------------
   -- signal multiplexing circuit
   ----------------------------------------------
  	hsync <= hsync0 when sw(3 downto 2)="11" else
           hsync2;
  	vsync <= vsync0 when sw(3 downto 2)="11" else
           vsync2;				
  	video_on <= video_on0 when sw(3 downto 2)="11" else
           video_on2;
  	p_tick <= p_tick0 when sw(3 downto 2)="11" else
           p_tick2;
  	px_x <= px_x0 when sw(3 downto 2)="11" else
           px_x2;
  	px_x <= px_x0 when sw(3 downto 2)="11" else
           px_x2;
	px_y <= px_y0 when sw(3 downto 2)="11" else
           px_y2;
   ----------------------------------------------
   -- rgb multiplexing circuit
   ----------------------------------------------
   process(video_on,px_x,px_y)
   begin
      if video_on='0' then
          graph_rgb <= "00000000"; --blank
      else
         if (px_x(4 downto 0) = px_y(4 downto 0)) or (px_x(4 downto 0) = not(px_y(4 downto 0)) ) then
            graph_rgb <= "00000011"; -- blue lines
         else
            graph_rgb <= "11111111";-- white background
         end if;
      end if;
   end process;
end Behavioral;
  

Simulation

Für den Test erzeugt man mit ISE ein VHDL Testmodul.

Die Schalter sw werden zu Anfang automatisch auf "00" gesetzt. Für 50MHz Takt wird die Clockperiode gesetzt:

    -- Clock period definitions
   constant clk_period : time := 20 ns;
  
Am Anfang wird das not_reset Signal aktiviert und dann wird 20ms, mehr als ein volles Bild simuliert und dann die Bildschirmauflösung umgeschaltet. Damit kann man dann VSYNC, HSYNC und das RGB Signal beobachten.

   -- Stimulus process
   stim_proc: process
   begin		
      -- hold reset state for 100 ns.
      wait for 100 ns;	
      not_reset <='0';
      wait for 100 ns;	
      not_reset <='1';
		
      wait for 20 ms;
      sw<="11";
      wait for 100 ns;	
      not_reset <='0';
      wait for 100 ns;	
      not_reset <='1';
      wait for 20 ms;

      wait;
   end process;
  
In folgendem Bild wird der simulierte Signalverlauf dargestellt.



Man sieht wie sich VSYNC und die Farbsignale bei Umschalten der Grafikauflösung verändern. Bei einer Bildwiederholfrequenz von 60Hz kommt das VSYNC Signal alle 16.66ms. Vergößert man das Simulationsbild um das VSYNC Signal bekommt man folgendes Bild.



Das VSYNC Signal hat 6 Zeilen high Pegel und vorher und nachher werden einige Zeilen ohne Farbsignale ausgegeben.

Files and Implementation


NEXYS3 xc6slx16-3csg324 Projekt und bit Datei

Inbetriebsnahme und Fehlersuche (Debugging)


Nach dem Konfigurieren des FPGAs sollte man sw(7) (Reset) auf '1' und sw(3,2) auf "11" stellen.
Wird kein Bild dargestellt, sollte man mit einem Oszilloskop HSYNC und VSYNC messen.
Die korrekte Pinbelegung kann man im 'Summary:' 'Pinout' überprüfen.

Literatur


[1] VGA Controller (VHDL), http://eewiki.net/pages/viewpage.action?pageId=15925278

[2] Projekt VGA Core in VHDL, http://www.mikrocontroller.net/articles/Projekt_VGA_Core_in_VHDL

[3] Open Cores Yet Another VGA [4] VGA Timings, http://info.electronicwerkstatt.de/bereiche/monitortechnik/vga/Standard-Timing/index.html

[5] http://en.wikipedia.org/wiki/Video_Graphics_Array

[6] http://www.hs-augsburg.de/~beckmanf/dokuwiki/doku.php?id=dtpr_versuch_6

[7] VESA VGA standard DMTv1r11, Google search

[8] Spartan-3 Libraries Guide for HDL Designs (UG607)

[9] Spartan-3 Generation FPGA User Guide, Extended Spartan-3A, Spartan-3E, and Spartan-3 FPGA Families, UG331 (v1.8) June 13, 2011

Work plan


Date Topic Duration Start End
13.06.2014 Messung Grafikmodi 4h 13.6.2014 13.6.2014
19.06.2014 VHDL Syntax Highlighter 4h 19.6.2014 19.6.2014
20.06.2014 Dokumentation Grafikmodi 4h 20.6.2014 20.6.2014
20.06.2014 VHDL Code Main project 1h 20.6.2014 20.6.2014
20.6.2014 VHDL Code Simulation 1h 20.6.2014 20.6.2014
28.06.2014 Clockmanager Xilinx 4h 28.06.2014 28.06.2014
19.06.2017 Nexys3 Aktualisierung, Dateibereitstellung, Debug 4h 28.06.2014 28.06.2014