这一篇在干嘛?

显示器是很多人每天盯着却从没想过的东西:画面到底是怎么「出现」的?这一篇用 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 的时间间隔):

行同步头行图像行周期
对应位置TfTaTbTcTdTeTg
时间(Pixels)8964086408800

表 1-2 场扫描时序要求(单位:行,即输出一行 Line 的时间间隔):

行同步头行图像行周期
对应位置TfTaTbTcTdTeTg
时间(Lines)222584808525

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 色对应的编码电平:

颜色绿
R00001111
G00110011
B01010101

表 1-4 彩条信号发生器 3 种显示模式:

1横彩条1:白黄青绿品红蓝黑2:黑蓝红品绿青黄白
2竖彩条1:白黄青绿品红蓝黑2:黑蓝红品绿青黄白
3棋盘格1:棋盘格显示模式12:棋盘格显示模式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 水平计数器:0800 循环,一个计数值对应一个像素时间。对照表 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/cik1
/vgaV_vlg_tst/reset_n1
/vgaV_vlg_tst/rgbin100
/vgaV_vlg_tst/DOUT001000010001
/vgaV_vlg_tst/hcnt145
/vgaV_vlg_tst/vcnt8
/vgaV_vlg_tst/hsSt1
/vgaV_vlg_tst/vsSt1
/vgaV_vlg_tst/rSt1
/vgaV_vlg_tst/gSt0
/vgaV_vlg_tst/bSt0

仿真时用波形观察 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输入R750MHz 系统时钟
reset_n输入A2复位(低有效)
VGA_D[2:0]输出E13/N6/D5RGB 三基色数字信号
VGA_HSYNC输出E7行同步
VGA_VSYNC输出F7场同步

上板流程

  1. 按图 1.3 的接口电路,将 VGA 线一端插在开发板 VGA 口,另一端连接至显示器。
  2. 新建工程、加入 vgaV 模块、综合布局布线。
  3. 按上表录入引脚约束,生成比特流并下载。
  4. 程序下载完毕后,按动板子上的 KEY1 复位键。

现象与总结

原文描述的测试现象:

按动板子上的 KEY1 复位键后应该能够显示彩色棋盘格。实验到此结束。

回顾一下这个实验教给我们的显示设计套路,它适用于所有逐点扫描的显示设备:

  1. 先定像素时钟——由分辨率、刷新率反推(25.175MHz)。
  2. 两个嵌套计数器——水平数像素(0799),垂直数行(0524),垂直计数以完整行周期为步进。
  3. 查表定同步区间——hs/vs 的脉冲宽度与位置严格对齐标准时序表,负极性。
  4. 显示区内外区别对待——区内输出颜色,区外消隐为 0。
  5. 颜色用坐标算——不需要帧缓存就能产生测试图案,是验证时序最省资源的手段。

理解了这个框架,再看 HDMI、LCD 控制器甚至 GPU 的光栅化流程,骨架都是同一套:时序发生器 + 每像素颜色计算。VGA 是它们最赤裸、最适合初学的版本。

自测