参考《Verilog HDL那些事儿建模篇》而作。
VGA分为VGA硬件接口和VGA协议。VGA硬件接口没什么。下面先介绍VGA协议。
VGA 协议主要由 5 个输入信号组成:HSYNC Signal, VSYNC Signal, RGB Signal。 说简单一点,HSYNC Signal 是“列同步信号”, VSYNC Signal 是“行同步信号”, RGB Signal 是“红色、绿色、蓝色、颜色信号”。
VGA扫描一帧屏幕是由“m行扫描”和“n列填充”组成。以800 x 600 x 60HZ(一下涉及到的所有与分辨率有关的都以他为例)为例:
扫描次序如下:
扫描第 0 行 - 在第 0 行,列填充 0~799。
扫描第 1 行 - 在第 1 行,列填充 0 ~ 799。
扫描第 2 行 - 在第 2 行,列填充 0 ~ 799。
扫描第 m 行 - 在第 m 行,列填充 0 ~ 799。
扫描第 598 行 - 在第 598 行,列填充 0 ~ 799。
直到描第 599 行- 在第 599 行,列填充 0 ~ 799
宏观上,一帧屏幕的显示是由 600 行从上至下扫描,800 列从左至右填充,然而微观上,一行的行 扫描是由超过 800 个列填充完成。
HSYNC Signal用来控制“列填充”。
a(同步段):拉低128个列像素(这里的像素和图像的像素不太一样,下面会介绍)。
b(后廊段):拉高88个列像素。
c(激活段):拉高800个列像素。
d(前廊段):拉高40个列像素。
所以一列总共有1056个列像素。
VSYNC Signal 是用来控制“行扫描”。
o(同步段):拉低4个行像素。
p(后廊段):拉高23个行像素。
q(激活段):拉高600个行像素。
r(前廊段):拉高一个行像素。
所以一行总共有628个行像素。
一个行像素是以列像素为单位:一个行像素 = 1056个列像素。
一个列像素以时间为单位:一个列像素 = 25ns;一个行像素 = 1056 x 25ns = 2.64us。
上图表示 HSYNC Signal 只有在的 C 段 (红色部分)和 VSYNC Signal 的 q 段 (黄色部分)的激活段,数据的输入才有效。
交叉部分的表达式可以描述为:列像素 > 216 && 列像素 < 1017 && 行像素 > 27 && 行像素 < 627。
先实现VGA驱动模块:
PLL模块用于输出40MHZ的时钟频率,同步模块对 HSYNC Signal 和 VSYNC Signal 进行控制,用于控制VGA的时序。VGA控制模块用于图像的显示控制。
下面先实现同步模块:
module sync
(
CLK,
RSTn,
HSYNC_Sig,
VSYNC_Sig,
Ready_Sig,
Column_Addr_Sig,
Row_Addr_Sig
);
input CLK;
input RSTn;
output HSYNC_Sig; //HSYNC信号线,控制“列填充”
output VSYNC_Sig; //VSYNC信号线,控制“行扫描”
output Ready_Sig; //数据输入的有效时间标志位,1表示可以输入数据
output [10:0] Column_Addr_Sig; //列像素地址信号
output [10:0] Row_Addr_Sig; //行像素地址信号
//列像素计数器
reg [10:0] count_H;
always @ (posedge CLK , negedge RSTn)
begin
if(!RSTn)
count_H <= 11'd0;
else if(count_H == 11'd1056)
count_H <= 11'd0;
else
count_H <= count_H + 1'b1;
end
//行像素计数器
reg [10:0] count_V;
always @ (posedge CLK , negedge RSTn)
begin
if(!RSTn)
count_V <= 11'd0;
else if(count_V == 11'd628)
count_V <= 11'd0;
else if(count_H == 11'd1056)
count_V <= count_V + 1'b1;
end
reg isReady;
always @ (posedge CLK , negedge RSTn)
begin
if(!RSTn)
isReady <= 1'b0;
else if((count_H > 11'd216 && count_H < 11'd1017) &&
(count_V > 11'd27 && count_V < 11'd627)) //数据输入有效区域
isReady <= 1'b1; //表示可以输入数据了
else
isReady <= 1'b0;
end
assign VSYNC_Sig = (count_V <= 11'd4) ? 1'b0 : 1'b1; //表示出时序图的时序,o,p,q,r,段
assign HSYNC_Sig = (count_H <= 11'd128) ? 1'b0 : 1'b1; //表示出时序图的时序,a,b,c,d,段
assign Ready_Sig = isReady;
assign Column_Addr_Sig = isReady ? (count_H - 11'd217) : 11'd0;//可以输入数据时顺便输出一下现在的列像素
assign Row_Addr_Sig = isReady ? (count_V - 11'd28) : 11'd0; //可以输入数据时顺便输出一下现在的行像素
endmodule
然后是VGA控制模块:
module vga_control
(
CLK,
RSTn,
Ready_Sig,
Column_Addr_Sig,
Row_Addr_Sig,
Red_Sig,
Green_Sig,
Blue_Sig
);
input CLK;
input RSTn;
input Ready_Sig;
input [10:0] Column_Addr_Sig;
input [10:0] Row_Addr_Sig;
output Red_Sig;
output Green_Sig;
output Blue_Sig;
reg isRectangl;
always @ ( posedge CLK or negedge RSTn )
begin
if(!RSTn)
isRectangl <= 1'b0;
else if( Column_Addr_Sig > 11'd0 && Row_Addr_Sig < 11'd100)
isRectangl <= 1'b1;
else
isRectangl <= 1'b0;
end
//所有颜色信号被赋予同样的表达式,这表示了在 799 x 100
//(显示部分的像素是800 x 600,然后isRectangl是在Row_Addr_Sig < 11'd100时置一)
//的区域内显示白色的矩形。
assign Red_Sig = (Ready_Sig && isRectangl) ? 1'b1 : 1'b0;
assign Green_Sig = (Ready_Sig && isRectangl) ? 1'b1 : 1'b0;
assign Blue_Sig = (Ready_Sig && isRectangl) ? 1'b1 : 1'b0;
endmodule
然后是PLL模块,我直接复制的例程暂时还不懂原理:
这编辑器我是服了,粘贴太多直接卡死,保存都不行,我贴个链接想用的去下载吧,也可以去网上找找fpga的pll模块的使用方法。
pll_module的代码
最后是顶层模块,把这些功能都连接起来:
module vga
(
CLK,
RSTn,
VSYNC_Sig,
HSYNC_Sig,
Red_Sig,
Green_Sig,
Blue_Sig
);
input CLK;
input RSTn;
output VSYNC_Sig;
output HSYNC_Sig;
output Red_Sig;
output Green_Sig;
output Blue_Sig;
wire CLK_40MHZ;
pll_module U1
(
.inclk0(CLK),
.c0(CLK_40MHZ)
);
wire [10:0]Column_Addr_Sig;
wire [10:0]Row_Addr_Sig;
wire Ready_Sig;
sync U2
(
.CLK(CLK_40MHZ),
.RSTn(RSTn),
.VSYNC_Sig(VSYNC_Sig),
.HSYNC_Sig(HSYNC_Sig),
.Column_Addr_Sig(Column_Addr_Sig),
.Row_Addr_Sig(Row_Addr_Sig),
.Ready_Sig(Ready_Sig)
);
vga_control U3
(
.CLK(CLK_40MHZ),
.RSTn(RSTn),
.Ready_Sig(Ready_Sig),
.Column_Addr_Sig(Column_Addr_Sig),
.Row_Addr_Sig(Row_Addr_Sig),
.Red_Sig(Red_Sig),
.Green_Sig(Green_Sig),
.Blue_Sig(Blue_Sig)
);
endmodule
然后配置引脚并下载到开发板中(不知道怎么配置和下载的可以去看看我的quartus2的使用教程),效果如图:
还是挺有成就感的,接下来让屏幕显示图像。