目录
VGA简介
图像显示设备在日常生活中随处可见,例如家庭电视机、计算机显示屏幕等,这些设 备之所以能够显示我们需要的数据图像信息,归功于视频传输接口。常见的视频传输接口 有三种:VGA 接口、DVI 接口和 HDMI 接口,目前的显示设备都配有这三种视频传输接 口。
三类视频接口的发展历程为VGA→DVI→HDMI。其中VGA接口出现最早,只能传输模拟图像信号; 随后出现的 DVI接口又分为三类:DVI-A、DVI-D、DVI-I,分别可传输纯模拟图像信号、纯数字图像信号和兼容模拟、数字图像信号;最后的 HDMI在传输数字图像信号的基础上又可以传输音频信号。
VGA ,英文全称"Video Graphics Array",译为视频图形阵列,是一种使用模拟信号 进行视频传输的标准协议,由 IBM公司于 1987年推出,因其分辨率高、显示速度快、颜 色丰富等优点,广泛应用于彩色显示器领域。由于 VGA 接口体积较大,与追求小巧便携 的笔记本电脑背道而驰,在笔记本电脑领域,VGA 接口已被逐渐淘汰,但对于体积较大的 台式机,这种情况并未发生,虽然 VGA 标准在当前个人电脑市场中已经过时,但因其在 显示标准中的重要性和良好的兼容性,VGA 仍然是最多制造商所共同支持的一个标准,个 人电脑在加载自己独特驱动程序之前,都必须支持VGA的标准。
早期的CRT显示器只能接收模拟信号,不能接收数字信号,计算机内部显卡将数字信号转换成模拟信号,通过VGA接口传给VGA显示器,虽然现如今许多种类的显示器可以 直接接收数字信号,但为了兼容显卡的VGA接口,大都支持VGA标准。
VGA接口及引脚定义
在最初的应用中, VGA 接口常用于计算机与 VGA 显示器之间的图像传输,在台式计算机、旧式笔记本电脑和 VGA 显示器上一般会有标准的 VGA 接口。

VGA 接口中以针式引出信号线的称为公头,以孔式引出信号线的称为母头。在计算机和 VGA 显示器上一般引出母头接口,使用两头均为公头的 VGA 连接线将计算机与 VGA显示器连接起来,两者图像传输时,使用的是 VGA 图像传输标准,该标准的具体内容在后面小节会详细说明。 VGA 公头、 母头接口和 VGA 连接线,见下图所示,左侧为母头,右侧为公头。

VGA 接口共有 15 个引脚,分为 3 排,每排各 5 个, 按照自上而下、从左向右的顺序排列。 其中第一排的引脚 1、 2、 3 和第三排的引脚 13、 14 最为重要。


VGA 使用工业界通用的 RGB 色彩模式作为色彩显示标准,这种色彩显示标准是根据三原色中红色、绿色、蓝色所占比例多少及三原色之间的相互叠加得到各式各样的颜色。引脚 1 红基色(RED)、引脚 2 绿基色(GREEN)、引脚 3 蓝基色(BLUE)就是 VGA 接口中负责传输三原色的传输通道。要注意的是,这 3 个引脚传输的是模拟信号。
引脚 13 行同步信号(HSYNC)、引脚 14 场同步信号(VSYNC),这两个信号,是在 VGA显示图像时,负责同步图像色彩信息的同步信号。
引脚 5、 9:这两个引脚分别是 VGA 接口的自测试和预留接口,不过不同生产厂家对这两个接口定义不同,在接线时,两引脚可悬空不接。
引脚 4、 11、 12、 15:这四个是 VGA 接口的地址码,可以悬空不接。
引脚 6、 7、 8、 10:这四个引脚接地,无需解释。
VGA显示原理
VGA 显示器显示图像,并不是直接让图像在显示器上显示出来, 而是采用扫描的方式,将构成图像的像素点,在行同步信号和场同步信号的同步下,按照从上到下、 由左到右的顺序扫描到显示屏上。 VGA 显示器扫描方式,具体见图。

结合 VGA 显示器扫描方式示意图,我们简要说明一下 VGA 显示器的扫描规律。
(1) 在行、场同步信号的同步作用下,扫描坐标定位到左上角第一个像素点坐标;
(2) 自左上角(第一行)第一个像素点坐标,逐个像素点向右扫描(图中第一个水平方向箭头);
(3) 扫描到第一行最后一个数据,一行图像扫描完成,进行图像消隐,扫描坐标自第一行行尾转移到第二行行首(图中第一条虚线);
(4) 重复若干次扫描至最后一行行尾,一帧图像扫描完成,进行图像消隐,扫描坐标跳转回到左上角第一行行首(图中对角线箭头),开始下一帧图像的扫描。
在扫描的过程中会对每一个像素点进行单独赋值,使每个像素点显示对应色彩信息,
当一帧图像扫描结束后, 开始下一帧图像的扫描,循环往复,当扫描速度足够快,加之人眼的视觉暂留特性,我们会看到一幅完整的图片,而不是一个个闪烁的像素点。这就是VGA 显示的原理。
vga时序标准
vga行时序

图中 Video 代表传输的图像数据,HSync 为行同步信号。HSync 的上升沿到下一个上升沿构成一个完整的行扫描周期。
每个行扫描周期包含 6 个部分(单位均为像素时钟周期):
- Sync(同步)
- Back Porch(后沿)
- Left Border(左边框)
- "Addressable" Video(有效图像)
- Right Border(右边框)
- Front Porch(前沿)
在行扫描周期内,Video 数据仅在"Addressable" Video 阶段有效,其他阶段均无效。HSync 信号仅在 Sync 阶段保持高电平,其余阶段均为低电平。这种高低电平交替的模式在每个行扫描周期重复进行。
vga场时序

图中 Video 表示传输的图像数据,VSync 是场同步信号。VSync 的上升沿到下一个上升沿构成一个完整的场扫描周期。
每个场扫描周期包含 6 个部分(以行为单位):
- Sync(同步)
- Back Porch(后沿)
- Top Border(上边框)
- "Addressable" Video(有效图像)
- Bottom Border(底边框)
- Front Porch(前沿)
在 HSync(行同步信号)和 VSync 的共同控制下,Video 图像数据完成一帧显示。其中,仅"Addressable" Video 阶段的图像数据有效,其余阶段无效。VSync 信号在 Sync 阶段保持高电平,其余阶段为低电平。完成一个场扫描周期后,系统即开始下一帧图像的扫描。
将行同步时序图与场同步时序图结合起来就构成了 VGA 时序图,如下图:

图中的红色区域表示在一个完整的行扫描周期中,Video 图像信息只在此区域有效, 黄色区域表示在一个完整的场扫描周期中,Video 图像信息只在此区域有效,两者相交的 橙色区域,就是VGA图像的最终显示区域。
VGA显示模式及相关参数
行同步时序可分为 6 个阶段,对于这 6 个阶段的参数是有严格定义的,参数配置不正确, VGA 不能正常显示。 VGA 显示器可支持多种分辨率,不同分辨率对应各阶段的参数是不同的,常用 VGA 分辨率时序参数,具体见图。

下面我们以经典 VGA 显示模式 640x480@60 为例,为讲解一下 VGA 显示的相关参数
- 显示模式: 640x480@60
640x480 是指 VGA 的分辨率, 640 是指有效显示图像每一行有 640 个像素点, 480 是指每一帧图像有 480 行, 640 * 480 = 307200 ≈ 300000,每一帧图片包含约 30 万个像素点,之前某品牌手机广告上所说的 30 万像素指的就是这个; @60 是指 VGA 显示图像的刷新频率, 60 就是指 VGA 显示器每秒刷新图像 60 次,即每秒钟需要显示 60 帧图像。 - 时钟(MHz): 25.175MHz,是 VGA 显示的工作时钟,像素点扫描频率。
- 行同步信号时序(像素 )、 场同步信号时序(行数 )
下面我们以显示模式 640x480@60、 640x480@75 为例,学习一下时钟频率的计算方法。
行扫描周期 * 场扫描周期 * 刷新频率 = 时钟频率
所以行同步信号和场同步信号高电平宽度与显示模式有关。
640x480@60:
|----------------------------------|------------|
| 行扫描周期: 800(像素),场扫描周期: 525(行扫描周期) | 刷新频率: 60Hz |
| 800 * 525 * 60 = 25,200,000 ≈ 25.175MHz (误差忽略不计) ||
640x480@75:
|----------------|-------------------|------------|
| 行扫描周期: 840(像素) | 场扫描周期: 500(行扫描周期) | 刷新频率: 75Hz |
| 840 * 500 * 75 = 31,500,000 = 31.5MHz |||
在计算时钟频率时,要谨记一点,要使用行扫描周期和场扫描周期的参数进行计算,不能使用有效图像的参数进行计算,虽然在有效图像外的其他阶段图像信息均无效, 但图像无效阶段的扫描也花费了扫描时间。
VGA电路开发板原理图分析

由于FPGA芯片只能处理数字信号,而VGA接口传输的是模拟信号,因此图中的vga_r、vga_g、vga_b引脚必须输出模拟信号。这需要通过数模转换将FPGA输出的数字信号转换为模拟信号。原理图中采用权电阻网络实现DAC功能,当然也可以使用专用DAC芯片。二者的主要区别在于权电阻网络的转换精度低于专用DAC芯片。通过观看这个原理图的权电阻网络,我们可以很清晰的看见每个颜色的接口的电阻之间的规律,我们以vga_b为例子进行讲解,假设
|--------------|-----|
| fpga高电平1 | 16V |
| vga_d4接电阻 | 1Ω |
| vga_d3接电阻 | 2Ω |
| vga_d2接电阻 | 4Ω |
| vga_d1接电阻 | 8Ω |
| vga_d0接电阻 | 16Ω |
| vga_b接口处的电阻值 | 1Ω |

根据上面的假设,我们可以写出几组真值表。
|--------|--------|--------|--------|--------|--------------------------------|
| vga_d4 | vga_d3 | vga_d2 | vga_d1 | vga_d0 | vga_b |
| 1 | 0 | 0 | 0 | 0 | (16V/1Ω)*1Ω=16V |
| 1 | 1 | 1 | 0 | 0 | (16V/1Ω+16V/2Ω+16V/4Ω)*1Ω=28V |
这就是权电阻网络实现的DAC,通过电阻去控制电流,进而控制电压(幅值),从而实现数模转换。
我们可以看出高位对vga_b的电压幅值影响最大,最低位对vga_b的电压幅值影响最小。
这里使用的RGB565图像模式,位宽为16bit,高 5位表示红色,低5 位表示蓝色,中间 6 位表示绿色。根据位宽不同,RGB 图形格式还包括 RGB232、 RGB888等,数据位宽越大,表示颜色种类越多,显示图像越细腻。
我们的电脑大多使用的是rgb888模式。将RGB888转换为RGB565的方法很简单:只需从RGB888的三个颜色通道中提取高位数据,并将对应幅值赋予RGB565即可完成模式转换。
使用vga接口显示不同颜色
实现目标1:VGA单颜色显示
实现目标2:VGA显示:横向条纹、纵向条纹、斜线分块显示、显示三角形、圆形、椭圆形等
绘制模块框图及波形图


编写模块代码
模块vga_ctrl
module vga_ctrl(
input wire clk ,
input wire rst_n ,
output wire[15:0] rgb ,
output wire hsync ,
output wire vsync
);
//========== 时序参数(640x480@60Hz)==========
//行同步信号时序(像素)
parameter H_SYNC = 16'd96 ,
H_BACK = 16'd40 ,
H_LEFT = 16'd8 ,
H_VAILD = 16'd640 ,
H_RIGHT = 16'd8 ,
H_FRONT = 16'd8 ,
H_TOTAL = 16'd800 ;
//场同步信号时序(行数)
parameter V_SYNC = 16'd2 ,
V_BACK = 16'd25 ,
V_TOP = 16'd8 ,
V_VAILD = 16'd480 ,
V_BOTTOM= 16'd8 ,
V_FRONT = 16'd2 ,
V_TOTAL = 16'd525 ;
//========== 颜色定义 ==========
parameter RED = 16'hF800, //红色
ORANGE = 16'hFC00, //橙色
YELLOW = 16'hFFE0, //黄色
GREEN = 16'h07E0, //绿色
CYAN = 16'h07FF, //青色
BLUE = 16'h001F, //蓝色
PURPPLE = 16'hF81F, //紫色
BLACK = 16'h0000, //黑色
WHITE = 16'hFFFF, //白色
GRAY = 16'hD69A; //灰色
//========== 显示模式选择 ==========
// 0:单色(黄色) 1:横向条纹 2:纵向条纹 3:斜线分块正向
// 4:斜线分块反向 5:三角形 6:圆形 7:椭圆形
parameter [2:0] PATTERN = 3'd5; // 修改此值切换模式
reg [15:0] cnt_h;
reg [15:0] cnt_v;
wire rgb_en;
//cnt_h
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_h <= 16'd0;
else if(cnt_h == H_TOTAL - 1'b1)
cnt_h <= 16'd0;
else
cnt_h <= cnt_h + 1'b1;
end
//cnt_v
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_v <= 16'd0;
else if(cnt_h == H_TOTAL - 1'b1 && cnt_v == V_TOTAL - 1'b1)
cnt_v <= 16'd0;
else if(cnt_h == H_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= cnt_v;
end
//hsync
assign hsync = (cnt_h < H_SYNC) ? 1'b1 : 1'b0 ;
//vsync
assign vsync = (cnt_v < V_SYNC) ? 1'b1 : 1'b0 ;
//rgb_en有效显示区域使能
assign rgb_en = (cnt_h >= H_SYNC + H_BACK + H_LEFT
&&cnt_h < H_SYNC + H_BACK + H_LEFT + H_VAILD
&&cnt_v >= V_SYNC + V_BACK + V_TOP
&&cnt_v < V_SYNC + V_BACK + V_TOP + V_VAILD)
? 1'b1 : 1'b0 ;
//建立坐标系
wire [31:0] x;
wire [31:0] y;
reg [15:0] color_tmp;//根据坐标和模式产生的颜色
//x
assign x = (rgb_en) ? (cnt_h - H_SYNC - H_BACK - H_LEFT) : 16'hffff;
//y
assign y = (rgb_en) ? (cnt_v - V_SYNC - V_BACK - V_TOP ) : 16'hffff;
//color_tmp
always @(*)
begin
color_tmp = BLACK;// 默认颜色
case(PATTERN)
3'd0 : color_tmp = YELLOW;//单色(黄色)
3'd1 : if(y < V_VAILD / 4)//横向条纹与y有关
color_tmp = RED;
else if(y >= V_VAILD / 4 * 1 && y < V_VAILD / 4 * 2)
color_tmp = ORANGE;
else if(y >= V_VAILD / 4 * 2 && y < V_VAILD / 4 * 3)
color_tmp = BLUE;
else
color_tmp = GREEN;
3'd2 : if(x < H_VAILD / 4)//纵向条纹与x有关
color_tmp = RED;
else if(x >= H_VAILD / 4 * 1 && x < H_VAILD / 4 * 2)
color_tmp = ORANGE;
else if(x >= H_VAILD / 4 * 2 && x < H_VAILD / 4 * 3)
color_tmp = BLUE;
else
color_tmp = GREEN;
3'd3 : if(y * H_VAILD < x * V_VAILD)//斜线分块正向
color_tmp = RED;//上半边
else
color_tmp = BLUE;
3'd4 : if(y * H_VAILD < V_VAILD * H_VAILD - x * V_VAILD)//斜线分块反向
color_tmp = RED;//上半边
else
color_tmp = BLUE;
3'd5 : if(y < 360 && y * 2 + 360 * 2 > x * 3 && y * 2 + x * 3 > 600 * 2)//三角形
color_tmp = YELLOW;
else
color_tmp = PURPPLE;
3'd6 : if(x * x + 320 * 320 + y * y + 240 * 240 < 2 * 320 * x + 2 * 240 * y + 120 * 120)//圆形(x - 320) * (x - 320) + (y - 240) * (y - 240) < 120*120
color_tmp = ORANGE;
else
color_tmp = GREEN;
3'd7 : if((x - 320) * (x - 320) * 50 * 50 + (y - 240) * (y - 240) * 100 * 100 < 100 * 100 * 50 * 50)//椭圆形
color_tmp = RED;
else
color_tmp = BLUE;
default:color_tmp = YELLOW;
endcase
end
//最终RGB输出
assign rgb = (!rgb_en) ? BLACK : color_tmp;
endmodule
顶层模块vga_one_color
module vga_one_color(
input wire clk ,
input wire rst_n ,
output wire[15:0] rgb ,
output wire hsync ,
output wire vsync
);
wire clk_25Mhz;
wire locked ;
pll_25Mhz pll_25Mhz_inst (
.areset ( ~rst_n ),
.inclk0 ( clk ),
.c0 ( clk_25Mhz ),
.locked ( locked )
);
vga_ctrl vga_ctrl_inst(
.clk (clk_25Mhz ) ,
.rst_n (locked ) ,
.rgb (rgb ) ,
.hsync (hsync ) ,
.vsync (vsync )
);
endmodule
编写仿真代码
`timescale 1ns/1ps
module vga_one_color_tb();
reg clk ;
reg rst_n ;
initial
begin
clk = 1'b0;
rst_n = 1'b0;
#123
rst_n = 1'b1;
end
always #10 clk = ~clk;
vga_one_color vga_one_color_inst(
.clk (clk ) ,
.rst_n (rst_n) ,
.rgb () ,
.hsync () ,
.vsync ()
);
endmodule
仿真验证

hsync没有问题

vsync没有问题

两个计数器没有问题



显示区域没有问题,x,y坐标也没有问题,仿真验证通过。
上板验证








练习
VGA显示,使用其他分辨率显示(只需要按照显示模式,修改一些参数配置即可)
总结
**vga显示原理:**VGA采用逐行扫描方式显示图像。在行同步信号和场同步信号的精确控制下,电子束(或视频信号)从屏幕左上角开始,按照从左到右、从上到下的顺序依次扫描每一个像素点。扫描过程中,每个像素点被单独赋予对应的RGB色彩值。一行扫描结束后,行消隐期使电子束快速跳转到下一行行首;一帧扫描完成后,场消隐期让电子束从右下角跳回左上角,开始下一帧刷新。如此循环往复,当刷新率足够高(通常≥60Hz)时,利用人眼的视觉暂留效应,观察者便看到连续稳定的完整画面。
**解释数模转换方法(介绍权电阻网络):**由于FPGA输出的是数字信号(0和1),而VGA接口需要模拟电压来表现颜色深浅,因此必须进行数模转换。权电阻网络是一种简单经济的实现方案。以蓝色通道为例,数字位从高位到低位分别通过阻值按二进制倍数递增的电阻(如1Ω、2Ω、4Ω、8Ω、16Ω)接入负载。高位接小电阻,权重大,对输出电流贡献大;低位接大电阻,权重小。当数字位为高电平(如16V)时,对应支路产生电流,所有有效支路的电流在负载电阻上叠加,形成最终的模拟电压。也就是说使用电阻,控制各支路的电流,进而去控制vga三原色接口的模拟电压。
回答不同分辨率下使用的时钟怎么计算: 分辨率不同,则对应的有效显示区域不同,则行扫描周期和场扫描周期都不同,所以发送一帧数据所需要时钟周期数为行扫描周期 * 场扫描周期。然后根据刷新频率,我们可以知道1s发送多少帧。这样我们就可以得到1s内的时钟周期数也就是vga的时钟频率为:行扫描周期 * 场扫描周期 * 刷新频率。
使用vga接口显示静态图片
实现目标:使用matlab将.jpg\.png图片转换成.mif文件,然后将.mif文件存储到rom-ip核里,最后通过vga接口显示到显示器上。下面使用的分辨率为800x600@60。
绘制模块框图及波形图


生成mif文件
使用下面的matlab代码生成mif文件。
Matlab
%a 是一个三维矩阵 (行,列,3)
%imread 函数默认将图片解码为 RGB888 格式,颜色通道顺序为R-G-B。
a = imread('tupian1.png'); % 读入图片 jpg png
y1=zeros(1,16384);% 存储红色分量,开辟一个16384的一维的全为0的空间,加速代码运行
y2=zeros(1,16384);% 存储绿色分量
y3=zeros(1,16384);% 存储蓝色分量
y4=zeros(1,16384);
y=zeros(1,16384); % 存储最终的 RGB565 像素值
%下面的设置使用EP4CE10F17C8芯片的60%多的存储类资源
%m=130;%行像素 图片宽度(列数)
%n=116;%场像素 图片高度(行数)
m=130;
n=73;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%111111
fild=fopen('tupian.mif','wt');
fprintf(fild,'%s\n','WIDTH=16;');% 数据位宽为 16 bit(RGB565恰好占16位)
fprintf(fild,'%s\n\n','DEPTH=16384;'); % 深度 16384
fprintf(fild,'%s\n','ADDRESS_RADIX=UNS;'); % 地址采用无符号十进制
fprintf(fild,'%s\n\n','DATA_RADIX=UNS;'); % 数据采用无符号十进制
fprintf(fild,'%s\n','CONTENT BEGIN');
for i=1:n % 遍历行(高度)
for j=1:m % 遍历列(宽度)
z= (i-1)*m+j ;% 二维坐标转一维索引(从1开始)
z0=round(z-1);% 生成从0开始的地址(MIF要求地址从0开始)
y1(z)= a(i,j,1) ;%提取红色通道 8bit
y2(z)= a(i,j,2) ;%提取绿色通道 8bit
y3(z)= a(i,j,3) ;%提取蓝色通道 8bit
%RGB565 fix向0取整数 RGB888 转 RGB565 的位截断与拼合
y(z)= fix(y1(z)/2^3)*2^11 + fix(y2(z)/2^2)*2^5 + fix(y3(z)/2^3) ;%彩色
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%彩色转黑白 彩色图转为灰度图(使用心理视觉权重:红 0.4,绿 0.5,蓝 0.1)。
%y4(z)= 0.4*y1(z)+0.5*y2(z)+0.1*y3(z) ;%RGB转灰度值
%%将计算出的灰度值 y4 再次按 RGB565 的位宽拆分成 R、G、B 相等(即 R=G=B=灰度值截断),显示出来就是黑白图像。
%y(z)= fix(y4(z)/8)*2^11 + fix(y4(z)/4)*2^5 + fix(y4(z)/8) ;%黑白
fprintf(fild,'\t%d\t',z0); % 写入地址(十进制)
fprintf(fild,'%s\t',':'); % 写入冒号分隔符
fprintf(fild,'%d\t',y(z)); % 写入 RGB565 数值
fprintf(fild,'%s\n',';'); % 写入分号结束符
end
end
fprintf(fild,'%s\n','END;');
配置IP核
配置pll_40Mhz、rom_16x16384
编写模块代码
模块vga_ctrl
Matlab
module vga_ctrl(
input wire clk ,
input wire rst_n ,
output wire[15:0] rgb ,
output wire hsync ,
output wire vsync
);
//========== 时序参数(800x600@60Hz)==========
//行同步信号时序(像素)
parameter H_SYNC = 16'd128 ,
H_BACK = 16'd88 ,
H_LEFT = 16'd0 ,
H_VAILD = 16'd800 ,
H_RIGHT = 16'd0 ,
H_FRONT = 16'd40 ,
H_TOTAL = 16'd1056;
//场同步信号时序(行数)
parameter V_SYNC = 16'd4 ,
V_BACK = 16'd23 ,
V_TOP = 16'd0 ,
V_VAILD = 16'd600 ,
V_BOTTOM= 16'd0 ,
V_FRONT = 16'd1 ,
V_TOTAL = 16'd628 ;
//========== 颜色定义 ==========
parameter RED = 16'hF800, //红色
ORANGE = 16'hFC00, //橙色
YELLOW = 16'hFFE0, //黄色
GREEN = 16'h07E0, //绿色
CYAN = 16'h07FF, //青色
BLUE = 16'h001F, //蓝色
PURPPLE = 16'hF81F, //紫色
BLACK = 16'h0000, //黑色
WHITE = 16'hFFFF, //白色
GRAY = 16'hD69A; //灰色
////////////////////////////////////////////////图片宽度和高度
parameter PIC_W = 16'd130, //必须为偶数
PIC_H = 16'd72 ; //必须为偶数
reg [15:0] cnt_h;
reg [15:0] cnt_v;
wire rgb_en;
//cnt_h
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_h <= 16'd0;
else if(cnt_h == H_TOTAL - 1'b1)
cnt_h <= 16'd0;
else
cnt_h <= cnt_h + 1'b1;
end
//cnt_v
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_v <= 16'd0;
else if(cnt_h == H_TOTAL - 1'b1 && cnt_v == V_TOTAL - 1'b1)
cnt_v <= 16'd0;
else if(cnt_h == H_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= cnt_v;
end
//hsync
assign hsync = (cnt_h < H_SYNC) ? 1'b1 : 1'b0 ;
//vsync
assign vsync = (cnt_v < V_SYNC) ? 1'b1 : 1'b0 ;
//rgb_en有效显示区域使能
assign rgb_en = (cnt_h >= H_SYNC + H_BACK + H_LEFT
&&cnt_h < H_SYNC + H_BACK + H_LEFT + H_VAILD
&&cnt_v >= V_SYNC + V_BACK + V_TOP
&&cnt_v < V_SYNC + V_BACK + V_TOP + V_VAILD)
? 1'b1 : 1'b0 ;
//////////////////////////////建立坐标系
wire [15:0] dx;
wire [15:0] dy;
//dx
assign dx = (rgb_en) ? (cnt_h - H_SYNC - H_BACK - H_LEFT) : 16'hffff;
//dy
assign dy = (rgb_en) ? (cnt_v - V_SYNC - V_BACK - V_TOP ) : 16'hffff;
///////////////////////////从rom核里提取数据
wire rden;
reg [13:0] addr;
wire[15:0] q ;
//rden,每一行提取2个时钟周期读取
assign rden = (dx >= H_VAILD / 2 - PIC_W / 2 - 2'd2
&&dx < H_VAILD / 2 + PIC_W / 2 - 2'd2
&&dy >= V_VAILD / 2 - PIC_H / 2
&&dy < V_VAILD / 2 + PIC_H / 2)
? 1'b1 : 1'b0 ;
//addr
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
addr <= 14'd0;
else if(rden)
addr <= addr + 1'b1;
else if(addr == PIC_W * PIC_H)
addr <= 14'd0;
else
addr <= addr;
end
rom_16x16384 rom_16x16384_inst (
.address( addr ),
.clock ( clk ),
.rden ( rden ),
.q ( q )
);
//////////////////////////////最终RGB输出
assign rgb = (dx >= H_VAILD / 2 - PIC_W / 2
&&dx < H_VAILD / 2 + PIC_W / 2
&&dy >= V_VAILD / 2 - PIC_H / 2
&&dy < V_VAILD / 2 + PIC_H / 2) ? q : BLACK ;
endmodule
顶层模块vga_pic
Matlab
module vga_pic(
input wire clk ,
input wire rst_n ,
output wire[15:0] rgb ,
output wire hsync ,
output wire vsync
);
wire clk_40Mhz;
wire locked ;
pll_40Mhz pll_40Mhz_inst (
.areset ( ~rst_n ),
.inclk0 ( clk ),
.c0 ( clk_40Mhz ),
.locked ( locked )
);
vga_ctrl vga_ctrl_inst(
.clk (clk_40Mhz ) ,
.rst_n (locked ) ,
.rgb (rgb ) ,
.hsync (hsync ) ,
.vsync (vsync )
);
endmodule
编写仿真代码
Matlab
`timescale 1ns/1ps
module vga_pic_tb();
reg clk ;
reg rst_n ;
initial
begin
clk = 1'b0;
rst_n = 1'b0;
#123
rst_n = 1'b1;
end
always #10 clk = ~clk;
vga_pic vga_pic_inst(
.clk (clk ) ,
.rst_n (rst_n) ,
.rgb () ,
.hsync () ,
.vsync ()
);
endmodule
仿真验证
这里的仿真直接参考vga动态显示图片的仿真图查看。
上板验证

使用vga接口显示动态图片
实现目标:使用上面的图片从显示器的左上角处显示,然后向右下角移动,步进为1,到达边界时反弹,实现效果如下图:

这里不再绘制模块框图及波形图了。直接根据上面的代码进行修改。
编写模块代码
模块vga_ctrl
Matlab
module vga_ctrl(
input wire clk ,
input wire rst_n ,
output wire[15:0] rgb ,
output wire hsync ,
output wire vsync
);
/* //========== 时序参数(640x480@60Hz)==========
//行同步信号时序(像素)
parameter H_SYNC = 16'd96 ,
H_BACK = 16'd40 ,
H_LEFT = 16'd8 ,
H_VAILD = 16'd640 ,
H_RIGHT = 16'd8 ,
H_FRONT = 16'd8 ,
H_TOTAL = 16'd800 ;
//场同步信号时序(行数)
parameter V_SYNC = 16'd2 ,
V_BACK = 16'd25 ,
V_TOP = 16'd8 ,
V_VAILD = 16'd480 ,
V_BOTTOM= 16'd8 ,
V_FRONT = 16'd2 ,
V_TOTAL = 16'd525 ; */
//========== 时序参数(800x600@60Hz)==========
//行同步信号时序(像素)
parameter H_SYNC = 16'd128 ,
H_BACK = 16'd88 ,
H_LEFT = 16'd0 ,
H_VAILD = 16'd800 ,//dx坐标范围:0-799
H_RIGHT = 16'd0 ,
H_FRONT = 16'd40 ,
H_TOTAL = 16'd1056;
//场同步信号时序(行数)
parameter V_SYNC = 16'd4 ,
V_BACK = 16'd23 ,
V_TOP = 16'd0 ,
V_VAILD = 16'd600 ,//dy坐标范围:0-699
V_BOTTOM= 16'd0 ,
V_FRONT = 16'd1 ,
V_TOTAL = 16'd628 ;
//========== 颜色定义 ==========
parameter RED = 16'hF800, //红色
ORANGE = 16'hFC00, //橙色
YELLOW = 16'hFFE0, //黄色
GREEN = 16'h07E0, //绿色
CYAN = 16'h07FF, //青色
BLUE = 16'h001F, //蓝色
PURPPLE = 16'hF81F, //紫色
BLACK = 16'h0000, //黑色
WHITE = 16'hFFFF, //白色
GRAY = 16'hD69A; //灰色
//========== 图片宽度和高度 ==================
parameter PIC_W = 16'd130, //必须为偶数
PIC_H = 16'd72 ; //必须为偶数
reg [15:0] cnt_h;
reg [15:0] cnt_v;
wire rgb_en;
//cnt_h
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_h <= 16'd0;
else if(cnt_h == H_TOTAL - 1'b1)
cnt_h <= 16'd0;
else
cnt_h <= cnt_h + 1'b1;
end
//cnt_v
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_v <= 16'd0;
else if(cnt_h == H_TOTAL - 1'b1 && cnt_v == V_TOTAL - 1'b1)
cnt_v <= 16'd0;
else if(cnt_h == H_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= cnt_v;
end
//hsync
assign hsync = (cnt_h < H_SYNC) ? 1'b1 : 1'b0 ;
//vsync
assign vsync = (cnt_v < V_SYNC) ? 1'b1 : 1'b0 ;
//rgb_en有效显示区域使能
assign rgb_en = (cnt_h >= H_SYNC + H_BACK + H_LEFT - 2'd2
&&cnt_h < H_SYNC + H_BACK + H_LEFT + H_VAILD - 2'd2
&&cnt_v >= V_SYNC + V_BACK + V_TOP
&&cnt_v < V_SYNC + V_BACK + V_TOP + V_VAILD)
? 1'b1 : 1'b0 ;
//============== 建立二维坐标系 ================
wire [15:0] dx;
wire [15:0] dy;
//dx
assign dx = (rgb_en) ? (cnt_h - H_SYNC - H_BACK - H_LEFT + 2'd2) : 16'hffff;
//dy
assign dy = (rgb_en) ? (cnt_v - V_SYNC - V_BACK - V_TOP ) : 16'hffff;
//=============== 从rom核里提取数据 =====================
parameter OFFSET = 1'b1;//偏移量
reg [15:0] offset_x;//x轴的显示起始位置
reg [15:0] offset_y;//y轴的显示起始位置
reg dir_x; // 方向标志 1: 向右移动,0: 向左移动
reg dir_y; // 方向标志 1: 向下移动,0: 向上移动
wire frame_end;//扫描完一帧的结束标志信号
wire rden;
reg [13:0] addr;
wire[15:0] q ;
//offset_x
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
offset_x <= 16'd0;
else if(frame_end && dir_x && offset_x + PIC_W >= H_VAILD)
offset_x <= H_VAILD - PIC_W;//图片向右移动,图片右边缘超出或等于有效图像显示区的右边界
else if(frame_end && dir_x && offset_x + PIC_W < H_VAILD)
offset_x <= offset_x + OFFSET;
else if(frame_end && !dir_x && offset_x <= 16'd0)
offset_x <= 16'd0;//图片向左移动,图片左边缘超出或等于有效图像显示区的左边界
else if(frame_end && !dir_x && offset_x > 16'd0)
offset_x <= offset_x - OFFSET;
else
offset_x <= offset_x;
end
//offset_y
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
offset_y <= 16'd0;
else if(frame_end && dir_y && offset_y + PIC_H >= V_VAILD)
offset_y <= V_VAILD - PIC_H;//图片向下移动,图片下边缘超出或等于有效图像显示区的下边界
else if(frame_end && dir_y && offset_y + PIC_H < V_VAILD)
offset_y <= offset_y + OFFSET;
else if(frame_end && !dir_y && offset_y <= 16'd0)
offset_y <= 16'd0;//图片向上移动,图片上边缘超出或等于有效图像显示区的上边界
else if(frame_end && !dir_y && offset_y > 16'd0)
offset_y <= offset_y - OFFSET;
else
offset_y <= offset_y;
end
//dir_x
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
dir_x <= 1'b1;// 初始向右
else if(frame_end && dir_x && offset_x + PIC_W >= H_VAILD)//到右边界
dir_x <= 1'b0;// 改为向左
else if(frame_end && !dir_x && offset_x <= 16'd0)//到左边界
dir_x <= 1'b1;// 改为向右
else
dir_x <= dir_x;
end
//dir_y
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
dir_y <= 1'b1;// 初始向下
else if(frame_end && dir_y && offset_y + PIC_H >= V_VAILD)//到下边界
dir_y <= 1'b0;
else if(frame_end && !dir_y && offset_y <= 16'd0)//到上边界
dir_y <= 1'b1;
else
dir_y <= dir_y;
end
//frame_end
assign frame_end = (cnt_h == H_TOTAL - 1'b1 && cnt_v == V_TOTAL - 1'b1);
//rden与q之间存在2拍延迟,每一行提前2个时钟周期读取(由于dx没有负值,将dx坐标轴提前2拍)
assign rden = (dx >= offset_x
&&dx < offset_x + PIC_W
&&dy >= offset_y
&&dy < offset_y + PIC_H)
? 1'b1 : 1'b0 ;
//addr
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
addr <= 14'd0;
else if(rden)
addr <= addr + 1'b1;
else if(frame_end)//addr == PIC_W * PIC_H
addr <= 14'd0;
else
addr <= addr;
end
rom_16x16384 rom_16x16384_inst (
.address( addr ),
.clock ( clk ),
.rden ( rden ),
.q ( q )
);
////////////////////////////////////////////最终RGB输出
assign rgb = (dx >= offset_x + 2'd2 //dx坐标轴提前2拍,rgb要和q保持对齐
&&dx < offset_x + PIC_W + 2'd2
&&dy >= offset_y
&&dy < offset_y + PIC_H) ? q : BLACK ;
endmodule
顶层模块vga_dynamic_pic
Matlab
module vga_dynamic_pic(
input wire clk ,
input wire rst_n ,
output wire[15:0] rgb ,
output wire hsync ,
output wire vsync
);
wire clk_40Mhz;
wire locked ;
pll_40Mhz pll_40Mhz_inst (
.areset ( ~rst_n ),
.inclk0 ( clk ),
.c0 ( clk_40Mhz ),
.locked ( locked )
);
vga_ctrl vga_ctrl_inst(
.clk (clk_40Mhz ) ,
.rst_n (locked ) ,
.rgb (rgb ) ,
.hsync (hsync ) ,
.vsync (vsync )
);
endmodule
编写仿真代码
Matlab
`timescale 1ns/1ps
module vga_dynamic_pic_tb();
reg clk ;
reg rst_n ;
initial
begin
clk = 1'b0;
rst_n = 1'b0;
#123
rst_n = 1'b1;
end
always #10 clk = ~clk;
vga_dynamic_pic vga_dynamic_pic_inst(
.clk (clk ) ,
.rst_n (rst_n) ,
.rgb () ,
.hsync () ,
.vsync ()
);
endmodule
仿真验证

第一帧数据rden在坐标(0,0)拉高的,q和rgb是与有效区域(cnt_h:216-1015)(cnt_v:27-626)对齐的,第一帧数据的第一行的开始位置没有问题,addr也没有问题。

第一帧数据的第一行的结束位置没有问题,宽度为130,addr也没有问题。

第一帧数据的高度为72没有问题,第一帧数据验证通过。

扫描一帧结束标志信号没有问题,显示坐标更新没有问题,addr复位没有问题。

第二帧数据rden在坐标(1,1)处拉高,相对的q和rgb也是步进为1进行对齐的,说明第二帧数据开始也没有问题。


仿真验证通过。
