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 |