本篇内容将介绍 vga_ctrl 模块的Verilog代码实现。
功能介绍
vga_ctrl模块的功能是输出满足时序要求的行扫描与场扫描信号,以及向vga_pic模块发出像素坐标索引,索要该像素点的颜色数据,同时在行有效周期和场有效周期输出像素RGB565参数。
基本参数
- 一个Hsync周期包含800个像素时钟周期
- 一个Vsync周期包含525个Hsync周期
(上图, 单个Hsync的时钟分布,时间单位为像素时钟周Hz)
(上图,单个Vsync的时钟分布,时间单位为 一个Hsync周期,相当于800个像素时钟周期)
输入输出表
(图片来源:野火电子)
实现思路
第一部分:行同步时序与场同步时序
首先解决时序单位的问题,由于场同步是以行同步为周期,行同步是以像素时钟周期为单位;因此首先设计一个模块,计数行同步的周期数,以及场同步的周期数。
- cnt_h :每一个像素时钟周期加一(800一个循环,10位宽)
- cnt_v :cnt_h加到800后加一(525一个循环,10位宽)
reg [9:0] cnt_h,cnt_v;//十位宽的值
always@(posedge clk)begin
if(reset == 1'b1)begin //复位信号来临时
cnt_h <= 9'd0;
cnt_v <= 9'd0;
end else if((cnt_h == 9'd799)&&(cnt_v == 9'd524))begin
cnt_h <= 9'd0;
cnt_v <= 9'd0;
end else if(cnt_h == 9'd799)begin
cnt_h <= 9'd0;
cnt_v <= cnt_v + 9'd1;
end else
cnt_h <= cnt_h +9'd1;
end
编写一个TestBench测试一下:
符合预期,我们的时间单位计数部分完成!
其次我们解决协议的问题:
对于行同步时序:
首先,同步部分,cnt_h从0计数到93,H_sync保持高电平,94到799都保持低电平。
H_sync = (cnt_h>= 10'd0)&&(cnt_h<=95);
对于RGB数据的输出而言,我们可以发现其实前三部分,共计144个周期都是无效的,即0开始计数到143都是无效的,144到783这640个周期是行有效果,其余的784到799也都是无效的。对于有效的周期,我们要进行标注,以便控制模块知道何时输出数据。
h_valid = (cnt_h>=10'd144 )&&(cnt_h <= 10'd783);
对于场同步时序:
我们可以知道,0到1,V_sync为高,其余2到524无效。
assign V_sync = (cnt_v>= 10'd0)&&(cnt_v<=10'd1);
同理可以知道,0到34无效,35到514有效,515到524无效
assign v_valid = (cnt_v>=10'd35 )&&(cnt_v<= 10'd514);
先综合在一起:
module vga_ctrl(
input wire clk,
input wire reset,
output wire H_sync,
output wire V_sync);
wire h_valid,v_valid;
reg [9:0] cnt_h,cnt_v;//九位宽的值
always@(posedge clk)begin
if(reset == 1'b1)begin //复位信号来临时
cnt_h <= 10'd0;
cnt_v <= 10'd0;
end else if((cnt_h == 10'd799)&&(cnt_v == 10'd524))begin
cnt_h <= 10'd0;
cnt_v <= 10'd0;
end else if(cnt_h == 10'd799)begin
cnt_h <= 10'd0;
cnt_v <= cnt_v + 10'd1;
end else
cnt_h <= cnt_h +10'd1;
end
assign h_valid = (cnt_h>=10'd144 )&&(cnt_h <= 10'd783);
assign v_valid = (cnt_v>=10'd35 )&&(cnt_v<= 10'd514);
assign H_sync = (cnt_h>= 10'd0)&&(cnt_h<=95);
assign V_sync = (cnt_v>= 10'd0)&&(cnt_v<=10'd1);
endmodule
编写Test_Bench进行测试:
H_sync:
V_sync:
h_valid:
起始:
终点:
v_valid:
起始:
终点:
测试通过!
第二部分图像请求信号
图像请求信号即向图像数据生成模块vga_pic发出像素坐标,然后vga_pic模块返回这个像素的颜色。
首先我们要解决坐标信号,(pic_x,pic_y)何时送入vga_pic模块?
h_valid从低电平向高电平跳变后,显示屏便开始采集rgb[15:0]端口上的值对应输出的模拟量,所以在h_valid变高之前,将坐标值(pic_x,pic_y)送到vga_pic的输入端口,这样,当h_valid跳变之时,第0个像素的数据就送到rgb[15:0]端口上了。这里我们设定一个pix_data_req用来控制坐标型号的生成。由题可知,pix_data_req在(35<= cnt_v <= 514)且(143<=cnt_h<=782)范围有效;
assign pix_data_req = (cnt_v>=10'd35 )&&(cnt_v<= 10'd514)&&(cnt_h>=10'd143 )&&(cnt_h <= 10'd782);
其次,我们要解决坐标信号(pic_x,pic_y)值的大小,由对应关系可以知道:
assign pic_y = (pix_data_req)?(cnt_h - 10'd143):10h'3ff;//0~639
assign pic_x = (pix_data_req)?(cnt_v - 10'd35):10'h3ff; //0~479
Test_bench仿真:
module vga_ctrl(
input wire clk,
input wire reset,
output wire H_sync,
output wire V_sync,
output wire [9:0] pic_x,
output wire [9:0] pic_y);
wire h_valid,v_valid,pix_data_req;
reg [9:0] cnt_h,cnt_v;//九位宽的值
always@(posedge clk)begin
if(reset == 1'b1)begin //复位信号来临时
cnt_h <= 10'd0;
cnt_v <= 10'd0;
end else if((cnt_h == 10'd799)&&(cnt_v == 10'd524))begin
cnt_h <= 10'd0;
cnt_v <= 10'd0;
end else if(cnt_h == 10'd799)begin
cnt_h <= 10'd0;
cnt_v <= cnt_v + 10'd1;
end else
cnt_h <= cnt_h +10'd1;
end
assign h_valid = (cnt_h>=10'd144 )&&(cnt_h <= 10'd783);
assign v_valid = (cnt_v>=10'd35 )&&(cnt_v<= 10'd514);
assign H_sync = (cnt_h>= 10'd0)&&(cnt_h<=95);
assign V_sync = (cnt_v>= 10'd0)&&(cnt_v<=10'd1);
assign pix_data_req = (cnt_v>=10'd35 )&&(cnt_v<= 10'd514)&&(cnt_h>=10'd143 )&&(cnt_h <= 10'd782);
assign pic_y = (pix_data_req)?(cnt_h - 10'd143):10'h3ff;//0~639
assign pic_x = (pix_data_req)?(cnt_v - 10'd35):10'h3ff; //0~479
endmodule
仿真结果:
从上图中可以看出:
- pix_data_req早于h_valid一个时钟周期,因此在cnt_h==143时,坐标数据就被送到vga_pic模块的输入端,当cnt_h由143到144时,(1,0)点对应的颜色数据就被输送到rbg[15:0]上,也就是第一个像素数据。如果不提前一个时钟周期,那么143到144这个跳变沿来临时,坐标(10‘h3ff,10’h3ff)的颜色数据将被送入rbg[15:0]。
- pic_x为行坐标,范围为 0~479,pic_y为列坐标,范围在0~639。
从上图中可以看出,
- pic_x为行坐标,范围为 0~479,pic_y为列坐标,范围在0~639。
- h_valid比pix_data_req滞后一个周期,当然最后一个周期显示的点坐标是(0,639)
至此我们的vga_ctrl模块设计成功!