Verilog HDL那些事_建模篇笔记(实验九:VGA驱动)

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驱动建模

     PLLFPGA的重要硬件资源,PLL主要功能是对源时钟编程,翻频,分频。

     同步模块实现对列填充信号与行扫描信号的控制,同时输出当前的地址(Column_Addr_SigRow_Addr_Sig)和有效区域信号(Ready_Sig)。

 

    同步模块的输入只有CLKRSTn信号,不需要外部输入其它信号,在模块里面编写一系列的驱动模型,并输出驱动信号。

    首先,写一个列计数器与一个行计数器:

    注意:时钟频率经过PLL倍频后,时钟周期达到了25ns,对比列计数器与行计数器的写法,列计数器是每来一个时钟上升沿,计数增加1,即每隔25ns,计数增加1。而行计数器,是当列填充完毕后(if(Count_H == 11d1056),计数增加1。这就解决了我刚才发现行扫描与列扫描不匹配的疑惑。

    有效区域信号的产生:

    根据有效区域产生有效区域信号(Ready_Sig

    首先定义了一个名为isRectangle的标志寄存器(有01两种状态),用来划定显示区域,即(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(行地址)信号。

     (3VGA显示控制模块中,需要接收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操作的时候,加的是“1b1”,即二进制加法运算,因为起先我们定义的是 reg [10:0]count_V 位宽为11

    还有,并不是所有更高时钟的频率就能向下兼容。

5.拓展二(点阵显示)

    点阵编码的方式是:逐行显示,高位在前。

    逐行显示:表示图像是逐行填充,完成完一行的填充后,再填充下一行。

    高位在前:列填充从左往右填充,但最左边是列像素的最高位。即:

    以上为一个16个列像素,它的编码为0xf738

    在点阵显示模型中,需要有图片ROM,存储图片(点阵)信息,VGA控制模块需要给出点阵地址,并读取点阵数据,点阵的数据是一行一行读取的,因此,每次从图片ROM模块中取一行的数据。

    以下为部分VGA显示控制的代码,寄存器m存储的是同步扫描模块传过来的行地址,n存储的是同步扫描模块传过来的列地址。

    行地址直接传给图片ROM模块,图片ROM模块接收到行地址后,给出相应行的像素信息ROM_DataVGA控制模块根据传来的行像素信息,填充相应的颜色。

解释:

    Red_Sig = Ready_Sig ? Rom_Data[6d63 - n] : 1b0;

    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,图片ROMred,图片ROMgreen,图片ROMblue)给出对应行的数据。

    体会:对于图片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 == 11d628)  ?  1b1  :  1b0;

    Frame_Sig并没有在always块语句中做处理,只是需要使用已经经过处理的信号,做出判断就可以得出准确的Frame_Sig(也算是一种算法吧)

 

    在VGA显示控制模块中,定义了一个FRAME常量,和一个Count_Frame计数器,每当Frame_Sig高电平时,计数器加1,计数周期为60个,表明,每张图片需要显示60次。

   以上always块语句的作用是一个图片翻页模块,自带状态机与处理功能。初始化状态为i = 0,rAddr = 0,表示显示第一张图片。

    对比以前实验的写法:状态机与处理分开写。

     不同点:状态跳变条件与需要处理的动作条件相同,即状态跳变时刻,应当有一个响应对应状态的变化。本实验的状态变化不是一个完整的回路。分析本实验case里面的语句,实质上还是一个查表的思想,当条件Count_Frame == FRAME;条件满足时,状态发生跳变,而没有做出相应的响应。而rAddr <= 7d0,是当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结构,集中包含了状态机。

原文地址:https://www.cnblogs.com/chensimin1990/p/5783156.html