由EGo1开发板进行验证
VGA简介
VGA,Video Graphics Array,即视频图形阵列,是一种使用模拟信号进行视频传输的标准协议
VGA接口及引脚定义
引脚 | 定义 | 引脚 | 定义 |
---|---|---|---|
1 | 红基色(RED) | 9 | 保留 |
2 | 绿基色(GREEN) | 10 | 数字地(GND) |
3 | 蓝基色(BLUE) | 11 | 地址码0(ID BIT0) |
4 | 地址码2(ID BIT2) | 12 | 地址码1(ID BIT1) |
5 | 自测试 | 13 | 行同步(HYNC) |
6 | 红色地(RGND) | 14 | 场同步(VSYNC) |
7 | 绿色地(GGND)) | 15 | 地址码3(ID BIT3) |
8 | 蓝色地(BGND) |
VGA显示原理
VGA显示器采用图像扫描的方式进行图像显示,将构成图像的像素点,在行同步信号和场同步信号的同步下,按照从上到下、从左到右的顺序扫描到显示屏上
VGA时序标准
一个行扫描周期分为6个部分,基本单位是一个像素点,每一个周期传递一个像素信息
一个场扫描的基本单位是一行,一行表示一个完成的行扫描周期
只要当Hsync和Vsync都处于图像有效阶段的时候,图像信息才有效,其他阶段信息无效,进行图像的同步和消隐
VGA显示模式及相关参数
640 * 480 @60
640:在一个完整的行扫描周期中,有效显示图像每一行有640个像素点
480:一帧完整的图像有480行
640*480 ≈ 300000个像素点,每个时钟进行一次像素点的扫描
60:帧率,每秒刷新图像60次,每秒显示60帧
行扫描周期 × 场扫描周期 × 帧率 = 每秒扫描像素点个数 = 时钟频率
ex. 840 × 500 × 75 = 31.5MHz
模块设计
实现640 * 480 @60规格
最终显示的结果为10个不同颜色的等宽彩条
- clk_gen
调用PPL锁相环,25MHz时钟生成模块 - vga_pic
根据传入的有效图像坐标信息,产生要显示的图像数据 - vga_ctrl
vga驱动模块,在25MHz工作时钟下,产生横纵坐标信号(pix_x, pix_y),(0, 0)~(639, 479),传送给vga_pic模块,并且生成行场同步信号hsync和sync,再将vga_pic模块传过来的像素点信息pix_data输出到rgb端口
vga_ctrl模块以及测试
module vga_ctrl (
input wire vga_clk,
input wire sys_rst_n,
input wire [15: 0] pix_data,
output wire [ 9: 0] pix_x,
output wire [ 9: 0] pix_y,
output wire hsync,
output wire vsync,
output wire [15: 0] rgb
);
parameter H_SYNC = 10'd96 ,
H_BACK = 10'd40 ,
H_LEFT = 10'd8 ,
H_VALID = 10'd640 ,
H_RIGHT = 10'd8 ,
H_FRONT = 10'd8 ,
H_TOTAL = 10'd800 ;
parameter V_SYNC = 10'd2 ,
V_BACK = 10'd25 ,
V_TOP = 10'd8 ,
V_VALID = 10'd480 ,
V_BOTTOM= 10'd8 ,
V_FRONT = 10'd2 ,
V_TOTAL = 10'd525 ;
reg [ 9 :0] cnt_h;
reg [ 9: 0] cnt_v;
wire rgb_valid; // 图像有效信号 显示640*480
wire pix_data_req; // 数据请求信号
// 需要超前数据有效信号一个时钟周期,解决时序逻辑的滞后问题
// 行计数器
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cnt_h <= 10'd0;
else if (cnt_h == H_TOTAL - 1'b1)
cnt_h <= 10'd0;
else
cnt_h <= cnt_h + 1'b1;
// 场计数器
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cnt_v <= 10'd0;
else if ((cnt_v == V_TOTAL - 1'b1) && (cnt_h == H_TOTAL - 1'b1))
cnt_v <= 10'd0;
else if (cnt_h == H_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
// 图像有效信号
assign rgb_valid = ((cnt_h >= H_SYNC + H_BACK + H_LEFT)
&& (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID)
&& (cnt_v >= V_SYNC + V_BACK + V_TOP)
&& (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID) )
? 1'b1 : 1'b0;
// 数据请求信号
// 行计数器往前推一个,代表超前一个时钟周期
// 场计数器往前退一个,代表超前一行,不可取
assign pix_data_req = ((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'b1)
&& (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID - 1'b1)
&& (cnt_v >= V_SYNC + V_BACK + V_TOP)
&& (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID) )
? 1'b1 : 1'b0;
// 行坐标信号
assign pix_x = (pix_data_req == 1'b1) ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'b1)) : 10'h3ff;
// 纵坐标信号
assign pix_y = (pix_data_req == 1'b1) ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 10'h3ff;
// 行同步信号
assign hsync = (cnt_h <= H_SYNC - 1'b1) ? 1'b1 : 1'b0;
// 场同步信号
assign vsync = (cnt_v <= V_SYNC - 1'b1) ? 1'b1 : 1'b0;
//
assign rgb = (rgb_valid == 1'b1) ? pix_data : 16'h0000;
endmodule
module tb_vga_ctrl ( );
reg sys_clk ;
reg sys_rst_n ;
wire vga_clk ;
wire locked ;
wire rst_n ;
reg [15: 0] pix_data;
wire [ 9: 0] pix_x ;
wire [ 9: 0] pix_y ;
wire hsync ;
wire vsync ;
wire [15: 0] rgb ;
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#20
sys_rst_n = 1'b1;
end
always #5 sys_clk = ~sys_clk; // 100MHz
assign rst_n = sys_rst_n && locked;
clk_gen instance_name
(
// Clock out ports
.clk_out1 (vga_clk ), // output clk_out1
// Status and control signals
.reset (~sys_rst_n ), // input reset
.locked (locked ), // output locked
// Clock in ports
.clk_in1 (sys_clk )); // input clk_in1
vga_ctrl vga_ctrl_inst (
.vga_clk (vga_clk ), // 25MHz
.sys_rst_n (rst_n ),
.pix_data (pix_data ),
.pix_x (pix_x ),
.pix_y (pix_y ),
.hsync (hsync ),
.vsync (vsync ),
.rgb (rgb )
);
// 模拟生成图像数据
// 这里使用时序逻辑
// 出来的波形rgb会比rgb_valid滞后一个时钟周期
// 产生的图像数据就会滞后这个坐标信号一个周期
// 为了补偿滞后的一个时钟周期,将请求信号提前有效信号一个周期
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
pix_data <= 16'h0000;
else if (pix_x >= 10'd0 && pix_x <= 10'd639
&& pix_y >= 10'd0 && pix_y <= 10'd479)
pix_data <= 16'hffff;
else
pix_data <= 16'h0000;
endmodule
说明:
- vsync信号的一个高脉冲代表一帧数据显示结束
- 关注一个行显示周期,因为有组合逻辑的原因,需要设置req请求信号,将其设置为早于valid有效信号一个时钟
- 仿真的时候差不多设置17ms,就刚好是一帧图像的波形
vga_pic模块及测试
module vga_pic (
input wire vga_clk,
input wire sys_rst_n,
input wire [ 9: 0] pix_x,
input wire [ 9: 0] pix_y,
output reg [15: 0] pix_data
);
// 分辨率 也就是坐标信号的最大值
parameter H_VALID = 10'd460,
V_VALID = 10'd480;
// 颜色参数定义
parameter RED = 16'hF800,
ORANGE = 16'hFC00,
YELLOW = 16'hFFE0,
GREEN = 16'h07E0,
CYAN = 16'h07FF,
BLUE = 16'h001F,
PUPPLE = 16'hF81F,
BLACK = 16'h0000,
WHITE = 16'hFFFF,
GRAY = 16'hD69A;
// 输出信号赋值
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
pix_data <= BLACK; // 默认显示黑色
else if (pix_x >= 0 && pix_x < (H_VALID / 10) * 1)
pix_data <= RED;
else if (pix_x >= (H_VALID / 10) * 1 && pix_x < (H_VALID / 10) * 2)
pix_data <= ORANGE;`在这里插入代码片`
else if (pix_x >= (H_VALID / 10) * 2 && pix_x < (H_VALID / 10) * 3)
pix_data <= YELLOW;
else if (pix_x >= (H_VALID / 10) * 3 && pix_x < (H_VALID / 10) * 4)
pix_data <= GREEN;
else if (pix_x >= (H_VALID / 10) * 4 && pix_x < (H_VALID / 10) * 5)
pix_data <= CYAN;
else if (pix_x >= (H_VALID / 10) * 5 && pix_x < (H_VALID / 10) * 6)
pix_data <= BLUE;
else if (pix_x >= (H_VALID / 10) * 6 && pix_x < (H_VALID / 10) * 7)
pix_data <= PUPPLE;
else if (pix_x >= (H_VALID / 10) * 7 && pix_x < (H_VALID / 10) * 8)
pix_data <= BLACK;
else if (pix_x >= (H_VALID / 10) * 8 && pix_x < (H_VALID / 10) * 9)
pix_data <= WHITE;
else if (pix_x >= (H_VALID / 10) * 9 && pix_x < (H_VALID / 10) * 10)
pix_data <= GRAY;
else
pix_data <= BLACK;
endmodule
module tb_vga_ctrl_pic ( );
reg sys_clk ;
reg sys_rst_n ;
wire vga_clk ;
wire locked ;
wire rst_n ;
wire [15: 0] pix_data;
wire [ 9: 0] pix_x ;
wire [ 9: 0] pix_y ;
wire hsync ;
wire vsync ;
wire [15: 0] rgb ;
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#20
sys_rst_n = 1'b1;
end
always #5 sys_clk = ~sys_clk; // 100MHz
assign rst_n = sys_rst_n && locked;
clk_gen instance_name
(
// Clock out ports
.clk_out1 (vga_clk ), // output clk_out1
// Status and control signals
.reset (~sys_rst_n ), // input reset
.locked (locked ), // output locked
// Clock in ports
.clk_in1 (sys_clk )); // input clk_in1
vga_ctrl vga_ctrl_inst (
.vga_clk (vga_clk ), // 25MHz
.sys_rst_n (rst_n ),
.pix_data (pix_data ),
.pix_x (pix_x ),
.pix_y (pix_y ),
.hsync (hsync ),
.vsync (vsync ),
.rgb (rgb )
);
vga_pic vga_pic_inst (
.vga_clk (vga_clk ), // 25MHz
.sys_rst_n (rst_n ),
.pix_x (pix_x ),
.pix_y (pix_y ),
.pix_data (pix_data )
);
// 不需要模拟生成图像数据,vga_pic会根据vga_ctrl控制模块送过来的坐标输出图像数据
endmodule
这是一个完整的行显示周期,可以看到按照“红橙黄绿青蓝紫黑白灰”的颜色设定进行输出
顶层模块
生成的原理图和visio画的是一样的
module vga_colorbar (
input wire sys_clk,
input wire sys_rst_n,
output wire hsync,
output wire vsync,
output wire [15: 0] rgb
);
wire vga_clk ;
wire locked ;
wire rst_n ;
wire [ 9: 0] pix_x;
wire [ 9: 0] pix_y;
wire [15: 0] pix_data;
assign rst_n = sys_rst_n && locked;
clk_gen instance_name (
// Clock out ports
.clk_out1 (vga_clk ), // output clk_out1
// Status and control signals
.reset (~sys_rst_n ), // input reset
.locked (locked ), // output locked
// Clock in ports
.clk_in1 (sys_clk ) // input clk_in1
);
vga_ctrl vga_ctrl_inst (
.vga_clk (vga_clk ), // 25MHz
.sys_rst_n (rst_n ),
.pix_data (pix_data ),
.pix_x (pix_x ),
.pix_y (pix_y ),
.hsync (hsync ),
.vsync (vsync ),
.rgb (rgb )
);
vga_pic vga_pic_inst (
.vga_clk (vga_clk ), // 25MHz
.sys_rst_n (rst_n ),
.pix_x (pix_x ),
.pix_y (pix_y ),
.pix_data (pix_data )
);
endmodule
module tb_vga_colorbar ( );
reg sys_clk ;
reg sys_rst_n ;
wire hsync ;
wire vsync ;
wire [15: 0] rgb ;
initial begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #5 sys_clk = ~sys_clk;
vga_colorbar vga_colorbar_inst (
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.hsync (hsync ),
.vsync (vsync ),
.rgb (rgb )
);
endmodule
总结
使用VGA接口显示彩色像素点,主要是遵循VESA VGA时序标准,上面实现的是640 * 480 @60,也就是有效显示图像每一行有640个像素点,这样的行一共有480行,除去这些有效的像素点,还有四周消隐的部分,前后沿和左右边框。
关于显示还是不显示可以用一个数据有效信号valid来做标志,ctrl控制模块输出(pix_x, pix_y)坐标信号到pic模块,pic根据坐标信息输送特定的像素点信息,也就是颜色参数,ctrl模块还有valid有效信号、req数据请求信号,pic模块传过来的像素信息在valid有效信号的控制下选择是否赋值给rgb端口,也就是在下面的图像显示区域valid拉高,ctrl可以用rgb接收pic传过来的pix_data像素数据
那个req请求信号根据前面代码注释的说明,设置为超前valid信号一个时钟周期,这里其实挺绕的,如果不仔细放大,是不会发现个别信号有滞后或者超前的
上面的接口引脚图是参考老师的视频写的,RGB565:用16个bit表示一个像素,5个bit表示R(红色),6个bit表示G(绿色),5个bit表示B(蓝色),从高位到低位排列如下:
R R R R R G G G G G G B B B B B
根据vga_pic模块的参数定义,本来是要显示下面这样的
而EGo1板子 上的 VGA 接口(J1)通过14 位信号线与 FPGA 连接,红、绿、蓝三个,颜色信号各占 4 bit,另外还包括行同步和场同步信号
所以想要在EGo1上下板还需要改动一点
如果不关注具体显示颜色的话,可以随便给12bit的像素信号一些值,比如下面这样,rgb_reg就是vga_ctrl送出来的15bit的565模式像素信息,然后随便截取12bit的444模式像素信息到rgb,这个rgb送给顶层模块用于管脚约束,然后下板
wire [15: 0] rgb_reg; // 15:11 10:5 4:0
assign rgb = {rgb_reg[ 3: 0], rgb_reg[ 8: 5], rgb_reg[ 3: 0]};