1.了解VGA协议
VGA协议有5个输入信号,列同步信号(HSYNC Signal),行同步信号(VSYNC Signal),红-绿-蓝,颜色信号(RGB Signal)。
一帧屏幕的显示是由行从上至下扫描,列从左至右填充。
以800x600x60Hz为例:
对于列填充信号:a是拉低的128个列像素,b是拉高的88个列像素,c是拉高的800个列像素,d是拉高的40个列像素。
对于行扫描信号:o是拉低的4个行像素,p是拉高的23个行像素,q是拉高的600个行像素,r是拉高的1个行像素。
其中,橘色为有效显示区域
一个行像素是以列像素为单位来定义的:
1个行像素 = 1056个列像素 (即1行中有1056个像素需要填充)
一个列像素是以时间单位来定义的:
1个列像素 = 25ns。
2.VGA驱动建模
PLL是FPGA的重要硬件资源,PLL主要功能是对源时钟编程,翻频,分频。
同步模块实现对列填充信号与行扫描信号的控制,同时输出当前的地址(Column_Addr_Sig,Row_Addr_Sig)和有效区域信号(Ready_Sig)。
同步模块的输入只有CLK和RSTn信号,不需要外部输入其它信号,在模块里面编写一系列的驱动模型,并输出驱动信号。
首先,写一个列计数器与一个行计数器:
注意:时钟频率经过PLL倍频后,时钟周期达到了25ns,对比列计数器与行计数器的写法,列计数器是每来一个时钟上升沿,计数增加1,即每隔25ns,计数增加1。而行计数器,是当列填充完毕后(if(Count_H == 11’d1056)),计数增加1。这就解决了我刚才发现行扫描与列扫描不匹配的疑惑。
有效区域信号的产生:
根据有效区域产生有效区域信号(Ready_Sig)
首先定义了一个名为isRectangle的标志寄存器(有0和1两种状态),用来划定显示区域,即(Column_Addr_Sig>11’d0 && Row_Addr_Sig < 11’d100)当扫描到以上区域时,显示相应的颜色,其它区域则不显示。
3.总体描述
(1)整个系统由PLL模块将外部时钟倍频,得到同步扫描与填充模块与VGA显示控制模块所需要的时钟。
(2)同步扫描模块接收经过倍频后的时钟信号后,通过内部编写的行扫描计数器与列填充计数器来工作,通过编写Ready_Sig状态标志寄存器相关块语句(扫描到有效区域后置1,不在有效区域则置0),同时结合isReady与行扫描计数器,列扫描计数器,计算出Column_Addr_Sig(列地址),Row_Addr_Sig(行地址)信号。
(3)VGA显示控制模块中,需要接收Ready_Sig(有效区域信号),列地址,行地址信号。然后在相应的位置填充颜色。同时,我们也可以在有效区域内,自己划定显示区域(通过自定义状态标志寄存器和划定行地址与列地址的范围),并填充相应的颜色。
4.拓展一(向下兼容的概念)
640x480x60Hz的显示标准需要的时钟频率是25.175MHz,即填充1个列像素需要的时间是39.7ns。则可以用更高的源时钟求得39.7ns的定时。引出向下兼容的处理方法:将外部20MHz时钟源翻倍至100MHz,即1个时钟周期为10ns,因此可以用4个时钟周期40ns的时间来满足填充1个列像素所需要的时间。
//40ns_Count,main_source = 10ns parameter T40NS = 3'd3; reg [2:0]count1; always@(posedge CLK or negedge RSTn) if(!RSTn) count1 <= 3'd0; else if(count1 == T40NS) count1 <= 3'd0; else count1 <= count1 + 1'b1; reg [10:0]count_H; always@(posedge CLK or negedge RSTn) if(!RSTn) count_H <= 11'd0; else if(count_H == 11'd800) count_H <= 11'd0; else if(count1 == T40NS) count_H <= count_H + 1'b1; reg [10:0]count_V; always@(posedge CLK or negedge RSTn) if(!RSTn) count_V <= 11'd0; else if(count_V == 11'd625) count_V <= 11'd0; else if(count_H == 11'd800) count_V <= count_V + 1'b1;
在写代码的过程中,需要注意的是寄存器的位宽要定义好,在使用数据的时候也需要带上。计数器在进行加1操作的时候,加的是“1’b1”,即二进制加法运算,因为起先我们定义的是 reg [10:0]count_V 位宽为11。
还有,并不是所有更高时钟的频率就能向下兼容。
5.拓展二(点阵显示)
点阵编码的方式是:逐行显示,高位在前。
逐行显示:表示图像是逐行填充,完成完一行的填充后,再填充下一行。
高位在前:列填充从左往右填充,但最左边是列像素的最高位。即:
以上为一个16个列像素,它的编码为0xf738。
在点阵显示模型中,需要有图片ROM,存储图片(点阵)信息,VGA控制模块需要给出点阵地址,并读取点阵数据,点阵的数据是一行一行读取的,因此,每次从图片ROM模块中取一行的数据。
以下为部分VGA显示控制的代码,寄存器m存储的是同步扫描模块传过来的行地址,n存储的是同步扫描模块传过来的列地址。
行地址直接传给图片ROM模块,图片ROM模块接收到行地址后,给出相应行的像素信息ROM_Data,VGA控制模块根据传来的行像素信息,填充相应的颜色。
解释:
Red_Sig = Ready_Sig ? Rom_Data[6’d63 - n] : 1’b0;
VGA扫描控制模块接收到列地址后,读取Rom_Data中相应的列数据,即当n=0时,应该读取第0列的数据,即Rom_Data[63] 为第0列数据,符合高位在前的原则。
给出图片ROM模块的实例化代码:
Wire [63:0] Rom_Data; Rom_module U3 ( .clock(CLK_40MHz); .address(Rom_Addr); .q(Rom_Data) );
6.拓展三(图层显示建模)
关于图层显示的概论理解如下:
黄色是由红色图层与绿色图层叠加得到。
黑色是由红色图层,绿色图层和蓝色图层得到。
如若显示以下4个列像素的信息:
此表应该竖着和横着同时看,第1个列像素为红色,则图层显示码为:(第四位)红色层:1,绿色层:0,蓝色层:0;第2个列像素为黄色,则图层显示码为:(第三位)红色层:1,绿色层:1,蓝色层:0。
红色层:4’b1101
绿色层:4’b0101
蓝色层:4’b0001
在显示控制模块中,只需要将相同的行,相同的列,不同的层信息发送到相关的输出信号。
VGA控制模块给出行信号Rom_Addr,图片ROM(red),图片ROM(green),图片ROM(blue)给出对应行的数据。
体会:对于图片ROM模块,需要同步时钟,不需要复位信号处理,里面应该事先存储好图片的数据信息,等待VGA显示控制模块来读取。
图层的概念,就是通过使用不同颜色叠加,来产生我们需要的颜色。
7.拓展四(帧的概念)
在VGA显示标准中,如:800x600x60Hz,最后的“60Hz”,表示显示标准,意味着在1s内可以显示60帧图像。
60Hz的来历:1个行像素为1056个列像素,一个列像素需要25ns,则1帧的图像需要628*1056*25ns的时间,则1s内显示(1/(628*1056*25ns) = 60)帧图片。
显示动态图片的时候,对于图片ROM模块怎样存放图片的点阵信息,我们需要做如下处理:
假设每张图片都是16x16x1Bit,共有6张图片。则,一张图片有16行,16列,保持这6张图片列对齐,0~15行放置第一张图片,16~31行放置第二张图片.....以此类推。
等待显示完第一张图片后,再显示第二张图片,怎样判断一帧图片是否显示完全呢?需要同步扫描填充模块输出一个显示完成信号(Frame_Sig,)。
assign Frame_Sig = (Count_V == 11’d628) ? 1’b1 : 1’b0;
Frame_Sig并没有在always块语句中做处理,只是需要使用已经经过处理的信号,做出判断就可以得出准确的Frame_Sig(也算是一种算法吧)。
在VGA显示控制模块中,定义了一个FRAME常量,和一个Count_Frame计数器,每当Frame_Sig高电平时,计数器加1,计数周期为60个,表明,每张图片需要显示60次。
以上always块语句的作用是一个图片翻页模块,自带状态机与处理功能。初始化状态为i = 0,rAddr = 0,表示显示第一张图片。
对比以前实验的写法:状态机与处理分开写。
不同点:状态跳变条件与需要处理的动作条件相同,即状态跳变时刻,应当有一个响应对应状态的变化。本实验的状态变化不是一个完整的回路。分析本实验case里面的语句,实质上还是一个查表的思想,当条件Count_Frame == FRAME;条件满足时,状态发生跳变,而没有做出相应的响应。而rAddr <= 7’d0,是当Count_Frame == FRAME;这一条件不满足时,做出的响应。
额外体会:本实验中的case/endcase模块中:
4’d0: If( Count_Frame == FRAME ) i <= 4’d1; else rAddr <= 7’d0;
之所以两条语句不分开写(状态机与响应分开的形式),还有一部分原因是里面是一个if/else条件判断语句,两条语句只执行其中的一条,并不是都会执行。
assign Rom_Addr = rAddr + m;
rAddr表示为显示第几张图片,即ROW的区域。
m表示行偏移量,从同步扫描填充模块获得。
总结:
对于动态图片的显示(多张图片)形成的动画,在同步行扫描,列填充的模块中,需要对一副图画扫描完成与否做出处理,最后提供一个扫描完成信号。
在VGA显示控制模块中,首先要划定显示区域,根据行与列扫描地址(确定一个显示范围)。如果对同一副图片扫描次数有要求,则需要用到Frame_Sig扫描完成信号,设定一个计数器,记录扫描完成信号产生的次数。在设定不同图片行地址时,使用了case/endcase结构,集中包含了状态机。