这一篇在干嘛?
显示器是很多人每天盯着却从没想过的东西:画面到底是怎么「出现」的?这一篇用 FPGA 从零生成 VGA 时序——行同步、场同步、消隐、RGB 三基色全部自己算,最后在一台真实显示器上显示出彩色棋盘格。做完你就彻底理解「像素时钟 + 扫描计数器」这套显示世界的底层语言。
实验目标与原理
实验目的原文:学习 VGA 图像显示控制电路设计。
显示器是怎么画出画面的
计算机显示器的显示有许多标准,常见的有 VGA、SVGA 等。一般这些显示控制都用专用的显示控制器(如 6845)。在这里不妨尝试用 FPGA 来实现 VGA 图像显示控制器,用以显示一些图形、文字或图象,这在产品开发设计中有许多实际应用。
常见的彩色显示器一般由 CRT(阴极射线管)构成,彩色是由 R、G、B(红:Red,绿:Green,蓝:Blue)三基色组成,用逐行扫描的方式解决图象显示。阴极射线枪发出电子束打在涂有荧光粉的荧光屏上,产生 R、G、B 三基色,合成一个彩色像素。扫描是从屏幕的左上方开始的,从左到右,从上到下,进行扫描。每扫完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT 对电子束进行消隐,每行结束时,用行同步信号进行行同步;扫描完所有行,用场同步信号进行场同步,并使扫描回到屏幕的左上方,同时进行场消隐,预备下一场的扫描。
理解要点:画面不是「一次性出现」的,而是电子束(或液晶的逐点寻址)一行一行、一个点一个点画出来的。控制器必须和显示器的扫描节奏严格对齐——这就是行同步(HS)和场同步(VS)信号的职责。
640×480@60Hz 工业标准
对于普通的 VGA 显示器,其引出线共含 5 个信号,即:R、G、B 是三基色信号;HS 是行同步信号;VS 是场同步信号。对于 VGA 显示器的 5 个信号的时序驱动要注意严格遵循「VGA 工业标准」,即 640×480@60Hz 模式。VGA 工业标准要求的频率:
- 时钟频率(clock frequency):25.175 MHz(像素输出的频率)
- 行频(Line frequency):31469 Hz
- 场频(Field frequency):59.94 Hz(每秒图像刷新频率)

图 1.1 VGA 行扫描、场扫描时序示意图
时序参数由两张表严格规定。
表 1-1 行扫描时序要求(单位:像素,即输出一个像素 Pixel 的时间间隔):
| 行同步头 | 行图像 | 行周期 | |||||
| 对应位置 | Tf | Ta | Tb | Tc | Td | Te | Tg |
| 时间(Pixels) | 8 | 96 | 40 | 8 | 640 | 8 | 800 |
表 1-2 场扫描时序要求(单位:行,即输出一行 Line 的时间间隔):
| 行同步头 | 行图像 | 行周期 | |||||
| 对应位置 | Tf | Ta | Tb | Tc | Td | Te | Tg |
| 时间(Lines) | 2 | 2 | 25 | 8 | 480 | 8 | 525 |
HST1T2
VST3←T4→
图 1.2 HS 和 VS 的时序图
T1 为行同步消隐(约为 6μs);T2 为行显示时间(约为 26μs);T3 为场同步消隐(两行周期);T4 为场显示时间(480 行周期)。
验算一遍这三个频率
行周期 800 像素 × 525 行 = 420000 像素/帧;像素时钟 25.175MHz ÷ 420000 ≈ 59.94Hz——正好是场频。这三组数字不是独立凑出来的,而是互相锁死的:改任何一个,另两个都得跟着重算。设计显示控制器前先做这道算术,能避免绝大多数「显示器点不亮」的问题。
极性与消隐
VGA 工业标准显示模式要求:行同步、场同步都为负极性,即同步头脉冲要求是负脉冲。设计 VGA 图像显示控制要注意两个问题:一个是时序驱动,这是完成设计的关键,时序稍有偏差,显示必然不正常;另一个是 VGA 信号的电平驱动(注意 VGA 信号的驱动电平是模拟信号)。对于一些 VGA 显示器,HS 和 VS 的极性可正可负,显示器内可自动转换为正极性逻辑。在此以正极性为例,说明本示例中的 CRT 工作过程:R、G、B 为正极性信号,即高电平有效。
当 VS=0、HS=0 时,CRT 显示的内容为亮,此过程即正向扫描过程,约需 26µs。当一行扫描完毕,行同步 HS=1,约需 6µs;其间,CRT 扫描产生消隐,电子束回到 CRT 左边下一行的起始位置(X=0,Y=1);当扫描完 480 行后,CRT 的场同步 VS=1,产生场同步使扫描线回到 CRT 的第一行第一列(X=0,Y=0)处(约为两个行周期)。

图 1.3 VGA 接口电路图,左接口从上往下看
8 色编码与显示模式
为了节省存储空间,本示例中仅采用 3 位数字信号表达 R、G、B(纯数字方式):三基色信号,因此仅可显示 8 种颜色。表 1-3 是此 8 色对应的编码电平:
| 颜色 | 黑 | 蓝 | 红 | 品 | 绿 | 青 | 黄 | 白 |
| R | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
| G | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
| B | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
表 1-4 彩条信号发生器 3 种显示模式:
| 1 | 横彩条 | 1:白黄青绿品红蓝黑 | 2:黑蓝红品绿青黄白 | |
| 2 | 竖彩条 | 1:白黄青绿品红蓝黑 | 2:黑蓝红品绿青黄白 | |
| 3 | 棋盘格 | 1:棋盘格显示模式1 | 2:棋盘格显示模式2 |
本实验的工程代码选择的是棋盘格模式,颜色由坐标直接算出来,不需要帧缓存。
Verilog 实现
原文代码一字不改:
//硬件实验使用程序,加入了彩条显示,删去了读取 rom 信息
module vgaV
(
input clk,
input reset_n,
output VGA_HSYNC,
output VGA_VSYNC,
output reg [2:0] VGA_D
);
reg [9:0] hcnt, vcnt;
reg r, g, b;
reg hs, vs;
wire [2:0] rgb;wire [10:0] x= hcnt;
wire [10:0] y= vcnt;
assign VGA_VSYNC = vs;
assign VGA_HSYNC = hs;
wire display_enable;
reg clk25M;
assign display_enable = (x<640 && y<480);
assign rgb = x[8:6] + y[8:6];
always @(posedge clk or negedge reset_n)
if (!reset_n)
clk25M <= 0;
else
clk25M <= ~clk25M;
always @(posedge clk25M or negedge reset_n) //水平扫描计数器
if(!reset_n)
hcnt <= 0;
else
begin
if (hcnt < 800)
hcnt <= hcnt + 1'b1;
else
hcnt <= 0;
end
always @(posedge clk25M or negedge reset_n) //垂直扫描计数器
if(!reset_n)
vcnt <= 0;
else
begin
if (hcnt == 640 + 8)
begin
if (vcnt < 525)
vcnt <= vcnt +1'b1;
else
vcnt <= 0;
end
always @(posedge clk25M or negedge reset_n) //场同步信号发生
if(!reset_n)
hs <= 1;
else
begin
if((hcnt >=640+8+8) & (hcnt < 640 +8 +8 +96))
hs <= 1'b0;else
hs <= 1'b1;
end
always @(vcnt or reset_n) //行同步信号发
if(!reset_n)
vs <= 1;
else
begin
if( (vcnt >= 480 + 8 + 2) &&(vcnt < 480 + 8 + 2 + 2) )
vs <= 1'b0;
else
vs <= 1'b1;
end
always @(posedge clk25M or negedge reset_n)
if(!reset_n)
begin
VGA_D <= 0;
end
else
begin
if (hcnt < 640 & vcnt < 480 && display_enable) //扫描终止
begin
VGA_D[0] <= rgb[0];
VGA_D[1] <= rgb[1];
VGA_D[2] <= rgb[2];
end
else
begin
VGA_D <= 0;
end
end
endmodule逐块拆解:
- clk25M 分频:
clk25M <= ~clk25M把 50MHz 二分频,得到约 25MHz 的像素时钟,对准 25.175MHz 标准值(误差 0.7%,显示器可接受)。 - hcnt 水平计数器:0
800 循环,一个计数值对应一个像素时间。对照表 1-1:显示区 0639、后沿 640679、同步头 680775、前沿 776~799。 - vcnt 垂直计数器:注意它在
hcnt == 640+8时才加一——每过完一个完整行周期,行数才前进一行,计到 525 归零,对应表 1-2。 - hs/vs 生成:
hs在 hcnt 的 640+8+8 到 640+8+8+96 区间拉低,正是 96 像素宽的行同步头;vs在 vcnt 的 480+8+2 到 480+8+2+2 区间拉低,正好 2 行宽的场同步头,均为负极性。 - 颜色计算:
assign rgb = x[8:6] + y[8:6];是点睛之笔。x[8:6] 把 640 个水平像素切成 8 个 64 像素宽的竖条,y[8:6] 把 480 行切成 8 个 60 行高的横条,两者相加,就得到随坐标周期变化的 3 位颜色——棋盘格不需要任何存储器,纯组合逻辑「算」出来。 - 消隐输出:只在
display_enable(x<640 且 y<480)期间把 rgb 送出,其余时间 VGA_D 输出 0,等价于消隐。
时序差一个像素都不行
VGA 时序是「硬约束」:同步头宽度、极性、消隐区位置与表 1-1/1-2 偏差过大,显示器就会黑屏或画面滚动。调试时如果屏幕不亮,先别怀疑颜色逻辑,按顺序检查:像素时钟对不对 → hcnt/vcnt 计数范围对不对 → hs/vs 的区间对不对。绝大多数问题出在计数器边界值上。
实验仿真
原文给出的仿真观测信号表:
| Msgs | |
| /vgaV_vlg_tst/cik | 1 |
| /vgaV_vlg_tst/reset_n | 1 |
| /vgaV_vlg_tst/rgbin | 100 |
| /vgaV_vlg_tst/DOUT | 001000010001 |
| /vgaV_vlg_tst/hcnt | 145 |
| /vgaV_vlg_tst/vcnt | 8 |
| /vgaV_vlg_tst/hs | St1 |
| /vgaV_vlg_tst/vs | St1 |
| /vgaV_vlg_tst/r | St1 |
| /vgaV_vlg_tst/g | St0 |
| /vgaV_vlg_tst/b | St0 |
仿真时用波形观察 hs 的 96 像素负脉冲、vs 的 2 行负脉冲,以及 hcnt 从 799 跳回 0、vcnt 在行周期结束处加一的细节,确认时序后再上板。
引脚分配
原文引脚约束一字不改:
Set_pin_assignment { VGA_D[0] } { LOCATION = D5; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { VGA_D[1] } { LOCATION = N6; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { VGA_D[2] } { LOCATION = E13; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { VGA_HSYNC } { LOCATION = E7; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { VGA_VSYNC } { LOCATION = F7; IOSTANDARD = LVCMOS33; DRIVESTRENGTH = 8; PULLTYPE = NONE; }
set_pin_assignment { clk } { LOCATION = R7; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }
set_pin_assignment { reset_n } { LOCATION = A2; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }整理成速查表:
| 信号 | 方向 | 引脚 | 说明 |
|---|---|---|---|
| clk | 输入 | R7 | 50MHz 系统时钟 |
| reset_n | 输入 | A2 | 复位(低有效) |
| VGA_D[2:0] | 输出 | E13/N6/D5 | RGB 三基色数字信号 |
| VGA_HSYNC | 输出 | E7 | 行同步 |
| VGA_VSYNC | 输出 | F7 | 场同步 |
上板流程
- 按图 1.3 的接口电路,将 VGA 线一端插在开发板 VGA 口,另一端连接至显示器。
- 新建工程、加入 vgaV 模块、综合布局布线。
- 按上表录入引脚约束,生成比特流并下载。
- 程序下载完毕后,按动板子上的 KEY1 复位键。
现象与总结
原文描述的测试现象:
按动板子上的 KEY1 复位键后应该能够显示彩色棋盘格。实验到此结束。


回顾一下这个实验教给我们的显示设计套路,它适用于所有逐点扫描的显示设备:
- 先定像素时钟——由分辨率、刷新率反推(25.175MHz)。
- 两个嵌套计数器——水平数像素(0
799),垂直数行(0524),垂直计数以完整行周期为步进。 - 查表定同步区间——hs/vs 的脉冲宽度与位置严格对齐标准时序表,负极性。
- 显示区内外区别对待——区内输出颜色,区外消隐为 0。
- 颜色用坐标算——不需要帧缓存就能产生测试图案,是验证时序最省资源的手段。
理解了这个框架,再看 HDMI、LCD 控制器甚至 GPU 的光栅化流程,骨架都是同一套:时序发生器 + 每像素颜色计算。VGA 是它们最赤裸、最适合初学的版本。
自测
自测
- 640×480@60Hz 模式下,像素时钟、行频、场频各是多少?用行周期总像素数验算一下场频。
- 表 1-1 中行同步头 Ta 宽多少像素?代码里 hs 的拉低区间是如何对应这个值的?
- 代码
assign rgb = x[8:6] + y[8:6];为什么能产生棋盘格?如果把 8:6 改成 8:7,图案会变成什么样?- vcnt 为什么要在 hcnt==648 时才加一,而不是每个像素时钟都加?
- VGA 标准要求行、场同步是什么极性?代码中体现在哪里?