zoukankan      html  css  js  c++  java
  • 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十六:VGA模块

    实验二十六:VGA模块

    VGA这家伙也算孽缘之一,从《建模篇》那时候开始便一路缠着笔者。《建模篇》之际,学习主要针对像素,帧,颜色等VGA的简单概念。《时序篇》之际,笔者便开始摸索VGA的时序。《整合篇》之际,笔者尝试控制VGA的时序。如今《驱动篇I》的内容返回VGA的本题,也就是图像方面的故事。

    此刻,澎湃之情不容怠慢,请怒笔者不再回忆往事,失忆者请复习《Verilog HDL那些事儿》,笔者虽然也想直奔主题 ... 可是在此之前,笔者必须补足那些不易注意的细节

    。俗语有云,细节是关键的藏身之地,那些不起眼的小细节,往往都是左右大局的关键,学习也是如此。不过,实验二十六究竟存在多少影响成败的小细节呢?请竖起耳朵,让笔者慢慢告诉读者 ...

    clip_image002

    图26.1 C1计数的理想时序。

    图26.1是一段计数时序,C1扮演计数器,而且时序理想。对此,这段时序一定按照“时间点”在表现。假设笔者想要建立判断,那么判断基准会基于C1的过去值,例如:

    if( C1 == 0 );

    那么if( C1 == 0 ) 必须在T1才能成立。再假设笔者想要拉长判断的长度,例如T0~T8之间,那么笔者可以这样写:

    if( C1 == 0 || C1 == 1 || C1 == 2 || C1 == 3 || C1 == 4 || C1 == 5 || C1 == 6 || C1 == 7 );

    如果长度像妈妈一样长气,例如T2~T99。当然,上述方法一定行不通,因为后果不仅累坏自己,而且模块的内容也会沉长臃肿。为此,笔者可以或者这样写:

    if(C1 >= 0 && C1 <= 7);

    其中,if( C1 >= 0 && C1 <= 7 ) 表示9个时钟沿的长度。

    clip_image004

    图26.2 长度有AB之别的计数时序。

    假设某某长度是固定的家伙,例如图26.2,T0~T3组成4个时钟沿的长度A,而

    T3~T8组成5个时钟沿的长度B。为了方便,笔者可以建立常量:

    parameter A = 3, B = 5;

    如果笔者想把长度A作为有效的判断基准,那么笔者可以这样写:

    if( C1 >= 0 && C1 <= A -1 );

    代码的用意非常明显,C1 >= 0表示长度的起始,然而 C1 <= A -1 则是长度的结束。

    如果长度B也作为判断的基准,同样的写法也适用:

    if( C1 >= 2 && C1 <= A + B -1 );

    可是,不管怎么看 ... 笔者觉得 C1 >= 2 非常别扭,对此笔者可以这样修改:

    if( C1 >= A -1 && C1 < A + B -1 );

    怎么样,读者是不是稍微顺眼一点?

    clip_image006

    图26.3 长度有CDE之别的计数时序。

    假设顽皮的长度作出手势,然后分身成为3个,结果如图26.3所示。T0~T1组成3个时钟沿的长度C,T2~T6组成5个时钟沿的长度D,T6~T8组成3个时钟沿的长度E。为了方便,笔者先做常量声明:

    parameter C = 2, D = 4, E = 2。

    如果笔者想把长度C作为判断的基准,那么笔者可以这样写:

    if( C1 >= 0 && C1 <= C -1 );

    如果笔者想把长度D作为判断的基准,那么笔者可以这样写:

    if( C1 >= C -1 && C1 <= C + D -1 );

    如果笔者想把长度E作为判断的基准,那么笔者可以这样写:

    if( C1 >= C + D -1 && C1 <= C + D + E -1 );

    理解完毕以后,笔者可以这样总结 ... 由于C1从0开始计数,除非长度的起始地方是从0开始,否则长度的起始地方必须添加 -1 的处理。反之,不管结束地方怎么歇斯底里抵抗,长度的结束地方都必须加上 -1 处理。

    clip_image008

    图26.4 VGA时序。

    图26.4是典型的VGA时序,VGA有5条信号,其中 HSYNC 与 VSYNC 控制显示分辨率还有显示帧。本实验的显示标准选为 1024 × 768 @ 60Hz - 65Mhz,而表26.1就是该显示标准的内容:

    表26.1 显示标准 1024 × 768 @ 60Hz - 65Mhz。

    信号

    A

    B

    C

    D

    E

    VGA_HSYNC

    136

    160

    1024

    24

    1344

    信号

    O

    P

    Q

    R

    S

    VGA_VSYNC

    6

    29

    768

    3

    806

    如表26.1所示,A段与O段都是拉低的起始段,然后B段与P段是准备段,C段与Q段是数据段,D段与R段都是结束段,至于E段是ABCD段的总和,S段则是OPRQ段的总和。HSYNC有时候也称为列像素或者X,VSYNC则称为行像素或者Y。列像素最小的周期时间(或称像素时钟 Pixel Clock)是 1/65Mhz,行像素最小的周期时间则是 E段 × 1/65Mhz。话题继续之前,笔者再稍微补充一下细节内容。

    clip_image010

    图26.5 计数例子。

    我们先假设 HSYNC长度有8,前3个为拉低的起始段,后8个为拉高的数据段。如图26.5所示,C1总共计数两次,分别是T0~T8还有T8~T16。期间,第一次是无效的计数,所以HSYNC并没有拉低起始段。换之,第二次开始才是有效的计数,因为HSYNC拉低起始段。为此,Verilog可以这样描述:

    if( C1 == 7 ) HSYNC <= 1’b0;

    else if( C1 == 2 ) HSYNC <= 1’b1;

    假设起始段声明为A,而结束段声明为D,E则是A与D的总和,那么内容可以继续修改:

    parameter A = 3, D = 5, E = 8;

    if( C1 == E -1 ) HSYNC <= 1’b0;

    else if( C1 == A -1 ) HSYNC <= 1’b1;

    明白以后,我们便可以用Verilog 描述表26.1的内容,结果如代码26.1所示:

    1.         parameter SA = 11'd136, SE = 11'd1344;
    2.         parameter SO = 11'd6, SS = 11'd806;
    3.         
    4.          reg [10:0]C1;
    5.         reg [9:0]C2;
    6.         reg rH,rV;
    7.         
    8.         always @ ( posedge CLOCK or negedge RESET )
    9.             if( !RESET )
    10.                  begin
    11.                          C1 <= 11'd0;
    12.                         C2 <= 10'd0;
    13.                         rH <= 1'b1;
    14.                         rV <= 1'b1;
    15.                    end
    16.              else 
    17.                  begin
    18.                        if( C1 == SE -1 ) rH <= 1'b0; 
    19.                         else if( C1 == SA -1  ) rH <= 1'b1;
    20.                         
    21.                        if( C2 == SS -1 ) rV <= 1'b0;
    22.                         else if( C2 == SO -1  ) rV <= 1'b1;
    23.                         
    24.                         if( C2 == SS -1 ) C2 <= 10'd0;
    25.                         else if( C1 == SE -1 ) C2 <= C2 + 1'b1;
    26.                         
    27.                         if( C1 == SE -1 ) C1 <= 11'd0;
    28.                         else C1 <= C1 + 1'b1; 
    29.                    end

    代码26.1

    第1~2行是 A段与E段,O段还有S段的常量声明。第4~6是相关的寄存器声明,第10~14行则是这些寄存器的复位操作。请注意一下第8行的主时钟是65Mhz。第27~28行是针对列像素的计数器C1,计数范围为0~1343。第24~25行是针对行像素的计数器C2,计数范围为0~805。第18~19行用来控制 HSYNC,第21~22行则是用来控制VSYNC。

    理解完毕 HSYNC 与 VSYNC信号以后,接下来我们便要学习 RGB 信号。事实上,HSYNC与VSYNC的数据段,其实也是RGB有效的数据段。表26.1基于1024×768 @ 60Hz,所以HSYNC数据段的长度有1024,而VSYNC的数据段的长度则有768。如果VGA拥有显示标准,那么RGB信号也有所谓的颜色标准。根据Photoshop ,常见的颜色如表26.2所示:

    表26.2 常见的颜色。

    颜色

    位宽

    黑白

    1bit

    灰度级

    4bit,8bit

    RGB

    8bit,16bit,24bit,32bit

    黑白可谓是最典型的颜色,不禁让笔者想起怀念的小绿人。灰度级正如字面上的意思,意指失去颜色的图像,4bit有16灰度级,8bit则有256灰度级。RGB是电脑专用的颜色标准,从过去发展至今,RGB的颜色分辨率也从4bit发展到32bit。虽说8位RGB是历史悠久的前辈,不过至今它还未退休吔,例如街边的LED招牌。8位RGB也称为索引色。

    16位RGB可是人眼比较接近的的颜色标准,也是本实验的主题,其中RGB[15:11]是5位红色,RGB[10:5]是6位绿色,RGB[4:0]则是5位蓝色,16位RGB称为高彩。24位RGB也是目前流行的颜色标准,其中字节0为蓝色,字节1为绿色,字节2为红色,24位称为真彩。32位RGB相较24位RGB则多了一个字节3的通道字节,通道也指透明效果,它称为全彩。

    为消除读者对RGB的恐惧,笔者就解剖一下16位RGB:

    clip_image012

    图26.6 4×1与16位RGB图像。

    图26.6是一幅宽为4高为1的16位RGB图像,如果从左至右开始数起,列0为饱和的红色,列1为饱和的绿色,列2为饱和的蓝色,列3为纯黑。如果将其卸甲并且一字排开高位在前的内容,结果如表26.2所示:

    表示26.3 4×1与16位RGB图像内容(高位在前)。

    列0

    列1

    列2

    列3

    饱和红

    饱和绿

    饱和蓝

    16’hF800

    16’h07E0

    16’h001F

    16’h0000

    16’b1111_1000_0000_0000

    16’b0000_0111_1110_0000

    16’b0000_0000_0001_1111

    16’b0000_0000_0000_0000

    如表26.3所示,我们可以看见列0的红色占据RGB[15:11]的位置,亦即红色有25=32的分辨率。列1的绿色则是占据RGB[10:5]的位置,亦即26= 64的分辨率。至于列2的蓝色占据RGB[4:0]的位置,结果它有25=32的分辨率。

    clip_image014

    图26.7 4×1与16位RGB图像(红色渐变)。

    图26.7也是一幅宽有4高有1的16位RGB图像,不过是红色渐变的图像。如果从左至右开始数起,列0为4/4饱和的红色,列1为3/4饱和的红色,列2为2/4饱和的红色,列3为1/4饱和的红色。笔者随之伸出魔爪将其卸甲,然后一字排开高位在前的内容,结果如表26.4所示:

    表示26.4 4×1与16位RGB图像内容(红色渐变与高位在前)。

    列0

    列1

    列2

    列3

    4/4饱和红

    3/4饱和红

    2/4饱和红

    1/4饱和红

    16’hF800

    16’hC000

    16’h8000

    16’h4000

    16’b1111_1000_0000_0000

    16’b1100_0000_0000_0000

    16’b1000_0000_0000_0000

    16’b0100_0000_0000_0000

    如表26.4所示,我们可以看见列0的红色充斥整座RGB[15:11],列1的红色则是占据其中的14份内容而已。列2更惨,它占据的份儿只有8份而已,而最惨的列3则是只有麻雀眼泪般的4份内容而已。

    clip_image016

    图26.8 实验用的小可爱。

    图26.6是实验二十六所用的图像资源,内容是一群可爱的比卡丘在玩耍。图像大小为128 × 96 × 16 bit,结果容量为:

    128 × 96 × 16 bit = 196608 bit

    如果储存器的位宽有16位,那么储存器的地址位宽则是:

    128 × 96 = 12208

    因此,必须建立位宽为 214 的地址信号:

    214 = 16384

    简单而言,图26.8的X为128还有Y为96,而Y也充当偏移量的角色。为此,正确的地址公式如下所示:

    Address = X + ( Y × 128 )

    = X + (Y << 7)

    理解完毕这些准备知识以后,我们便可以开始建模了。

    clip_image018

    图26.9 实验二十六(VGA基础模块)的建模图。

    图26.9是实验二十六的建模图,其中PLL将50Mhz 的CLOCK增至 65Mhz。VGA储存模块有 128 × 96 × 16 bit 的容量,里边暂存皮卡丘的图像信息。VGA功能模块则是负责1024 × 768 @ 60Hz的显示标准,还有将内部的计数信息反馈给VGA控制模块。VGA控制模块位与中间,它借助 iAddr然后转换成为图像读取的地址,最后再将反馈回来的图像信息发至 VGAD顶层信号。

    vga_savemod.v

    clip_image020

    图26.10 VGA储存模块的建模图。

    图26.10虽是VGA储存模块的建模图,不过内容却是简单ROM模块,具体内容让我们来看代码吧。

    1.    module vga_savemod
    2.    (
    3.         input CLOCK, RESET,
    4.         input [13:0]iAddr,
    5.         output [15:0]oData
    6.    );
    7.        (* ramstyle = "no_rw_check , m9k" , ram_init_file = "pikachu_128x96_16_msb.mif" *) 
    8.         reg [15:0] RAM[12287:0];
    9.         reg [15:0]D1;
    10.         
    11.         always @ ( posedge CLOCK or negedge RESET )
    12.             if( !RESET )
    13.                  D1 <= 16'd0;
    14.              else 
    15.                  D1 <= RAM[ iAddr ];
    16.         
    17.         assign oData = D1;
    18.    
    19.    endmodule

    以上内容虽然简单,不过还是注意一下第7行的建立声明。其中 no_rw_check是用来告诉综合器无视 read during write 的检测,m9k则声明片上内存用 m9k,至于ram

    init file 则是RAM的初始化内容,亦即图26.8。

    vga_funcmod.v

    clip_image022

    图26.11 VGA功能模块的建模图。

    图26.11是VGA功能模块的建模图,左边是反馈给朋友的oAddr,其中[20:10]是X地址,[9:0]则是Y地址。右边是驱动顶层的VGA_HSYNC与 VGA_VSYNC。

    1.    module vga_funcmod 
    2.    (
    3.         input CLOCK, RESET,
    4.         output VGA_HSYNC, VGA_VSYNC,
    5.         output [20:0]oAddr
    6.    );
    7.         parameter SA = 11'd136, SE = 11'd1344;
    8.         parameter SO = 11'd6, SS = 11'd806;
    9.         

    以上内容为相关的出入端声明,还有A段,E段,O段还有S段的常量声明。

    10.         reg [10:0]C1;
    11.         reg [9:0]C2;
    12.         reg rH,rV;
    13.         
    14.         always @ ( posedge CLOCK or negedge RESET )
    15.             if( !RESET )
    16.                  begin
    17.                         C1 <= 11'd0;
    18.                         C2 <= 10'd0;
    19.                         rH <= 1'b1;
    20.                         rV <= 1'b1;
    21.                    end
    22.              else 

    以上内容为相关的寄存器声明,其中C1为HSYNC计数,C2则为VSYNC计数。

    23.                  begin
    24.                        
    25.                         if( C1 == SE -1 ) rH <= 1'b0; 
    26.                         else if( C1 == SA -1  ) rH <= 1'b1;
    27.                         
    28.                         if( C2 == SS -1 ) rV <= 1'b0;
    29.                         else if( C2 == SO -1  ) rV <= 1'b1;
    30.                         
    31.                         if( C2 == SS -1 ) C2 <= 10'd0;
    32.                         else if( C1 == SE -1 ) C2 <= C2 + 1'b1;
    33.                         
    34.                         if( C1 == SE -1 ) C1 <= 11'd0;
    35.                         else C1 <= C1 + 1'b1;
    36.                         
    37.                    end
    38.                    

    以上内容为 HSYNC 与 VSYNC的驱动过程。第35~36行是计数HSYNC,第32~33行则是计数VYSNC,一个VSYNC为1344个HSYNC。第26~27行是 rH的控制,第29~30行则是 rV的控制。

    39.        reg [1:0]B1,B2,B3;
    40.         
    41.        always @ ( posedge CLOCK or negedge RESET )
    42.             if( !RESET )
    43.                  {  B3, B2, B1 } <= 6'b11_11_11;
    44.              else
    45.                  begin
    46.                         B1 <= { rH,rV };
    47.                         B2 <= B1;
    48.                         B3 <= B2;
    49.                    end    
    50.        

    第40~51则是用来延迟 rH与rV的周边操作,期间总共延迟3个时钟。详细内容往后再说。

    51.         assign { VGA_HSYNC, VGA_VSYNC } = B3;
    52.         assign oAddr = {C1,C2}
    53.         
    54.    endmodule

    以上内容为相关的输出驱动。

    vga_ctrlmod.v

    clip_image024

    图26.12 VGA控制模块的建模图。

    图26.12是VGA控制模块的建模图,而设计思路来源《整合篇》。左边信号连接储存模块,右边信号连接功能模块,北边信号则驱动顶层。具体内容还是让我们来看代码吧:

    1.    module vga_ctrlmod
    2.    (
    3.         input CLOCK, RESET,
    4.         output [15:0]VGAD,
    5.         output [13:0]oAddr,
    6.         input [15:0]iData,
    7.         input [20:0]iAddr
    8.    );

    以上内容为相关的出入端声明。

    9.         parameter SA = 11'd136, SB = 11'd160, SC = 11'd1024, SD = 11'd24, SE = 11'd1344;
    10.         parameter SO = 11'd6, SP = 11'd29, SQ = 11'd768, SR = 11'd3, SS = 11'd806;
    11.         
    12.         // Height , width, x-offset and y-offset
    13.         parameter XSIZE = 8'd128, YSIZE = 8'd96, XOFF = 10'd0, YOFF = 10'd0; 
    14.        
    15.         wire isX = ( (iAddr[20:10] >= SA + SB + XOFF -1 ) && ( iAddr[20:10] <= SA + SB + XOFF + XSIZE -1) );
    16.         wire isY = ( (iAddr[9:0] >= SO + SP + YOFF -1 ) && ( iAddr[9:0] <= SO + SP + YOFF + YSIZE -1) );
    17.         wire isReady = isX & isY;
    18.         
    19.         wire [31:0] x = iAddr[20:10] - XOFF - SA - SB -1; 
    20.         wire [31:0] y = iAddr[9:0] - YOFF - SO - SP -1;
    21.         

    以上内容为相关的常量声明。第9~10行是针对 1024 × 768 @ 60Hz 显示标准的常量声明。第13行声明图像信息,如大小还有位置。第15行声明有效的列像素,第16行则声明有效的Y像素,至于第17行则是声明图像的有效区域,注意它们都是即时事件。第19~20行用来是计算有效的X与Y,主要用来取得图像的读取地址,注意它们也是即时事件。

    22.         reg [31:0]D1;
    23.         reg [15:0]rVGAD;
    24.         
    25.         always @ ( posedge CLOCK or negedge RESET )
    26.             if( !RESET )
    27.                  begin
    28.                        D1 <= 18'd0;
    29.                        rVGAD <= 16'd0;
    30.                    end
    31.                else

    以上内容为相关的寄存器声明还有复位操作。

    32.                   begin
    33.                    
    34.                        // step 1 : compute data address and index-n
    35.                         if( isReady )
    36.                             D1 <= (y << 7) + x; 
    37.                         else
    38.                             D1 <= 14'd0;
    39.                         
    40.                         // step 2 : reading data from rom
    41.                         // but do-nothing
    42.                         
    43.                         // step 3 : assign RGB_Sig
    44.                         rVGAD <= isReady ? iData : 16'd0;
    45.                         
    46.                    end
    47.                    

    以上内容为VGA控制模块的核心操作。该控制模块采用流水操作,一边不断发送读取地址,一边不断等待图像信息读出,一边不断输出图像信息。

    clip_image026

    图26.13 VGA功能模块的流水操作。

    从某种程度来说,实验二十六的VGA基础模块可以看成如图26.13所示。VGA功能模块输出HSYNC与VSYNC的瞬间,它也发送 Addr 给 VGA控制模块。VGA控制模块计算读取地址以后再发送给 VGA储存模块,VGA储存模块随意也将图像信息返回发送给VGA控制模块,最后VGA控制模块再将图像信息驱动至 VGAD。

    简单来说,图像信息一共慢HSYNC和VSYNC信号3拍,因此VGA功能模块多了旁路的周边操作,目的是为了同步 HSYNC,VSYNC还有 VGAD之间的延迟。返回话题,有效的读取地址只有 isReady 拉高的时候,如代码行第36所示。同样,有效的图像信息也是 isReady 拉高的时候,如代码行第45所示。

    48.        assign VGAD = rVGAD;
    49.        assign oAddr = D1[13:0];
    50.    
    51.    endmodule

    以上内容为相关的输出驱动。

    vga_basemod.v

    该模块是VGA基础模块,至于内部的连线部署请参考图26.9。

    1.    module vga_basemod
    2.    (
    3.         input CLOCK, RESET,
    4.         output VGA_HSYNC, VGA_VSYNC,
    5.         output [15:0]VGAD
    6.    );
    7.        wire CLOCK_65M;
    8.         
    9.        pll_module U1  
    10.        (
    11.            .inclk0 ( CLOCK ),
    12.            .c0 ( CLOCK_65M ) // CLOCK 65Mhz
    13.         );
    14.         
    15.         wire [20:0]AddrU2;
    16.         
    17.         vga_funcmod U2    // 1024 * 758 @ 60Hz
    18.         (
    19.           .CLOCK( CLOCK_65M ), 
    20.           .RESET( RESET ),
    21.           .VGA_HSYNC( VGA_HSYNC ), 
    22.           .VGA_VSYNC( VGA_VSYNC ),
    23.           .oAddr( AddrU2 ),
    24.         );             
    25.         
    26.         wire [15:0]DataU3;
    27.         
    28.         vga_savemod U3
    29.         (
    30.            .CLOCK( CLOCK_65M ),
    31.            .RESET( RESET ),
    32.            .iAddr( AddrU4 ),
    33.            .oData ( DataU3 )
    34.        );
    35.        
    36.         wire [13:0]AddrU4;
    37.         
    38.         vga_ctrlmod U4  // 128 * 96 * 16bit, X0,Y0
    39.         (
    40.             .CLOCK( CLOCK_65M ),
    41.              .RESET( RESET ),
    42.              .VGAD( VGAD ),
    43.             .iData( DataU3 ),
    44.             .oAddr( AddrU4 ),
    45.             .iAddr( AddrU2 ),
    46.         );
    47.         
    48.    endmodule

    详细的内容请读者自己看着办吧。

    clip_image028

    图26.14 实验二十六的结果。

    综合完毕便下载程序,如果实验成功,分辨率为1024 * 768 的显示器左上角便会出现一幅128 × 96 × 16 bit 的图像显示在 X0与Y0的位置,结果如图26.14所示。

    细节一:完整的个体模块

    实验二十六的VGA基础模块虽为就绪状态,不过它是一只能力有限的家伙。原因很单纯,因为储存128 × 96 × 16 bit 的图像已经是EP4CE6F17C8 这块FPGA的极限,这家伙的度量很小,所以片上内存也不怎么大。

    细节二: 片上内存

    笔者曾经说过,片上内存是资粮很棒的储存资源,它不仅访问时间短,自定义强,而且操作也简单,但是肚量是它永远的痛。EP4CE6F17C8 虽然有 476280 bit 的片上内存,不过实际可用的范围只有其中的90%而已。

    细节三: 缓冲图像的储存模块

    如果片上内存不行,那么SDRAM当然是最好的替代。同样,笔者也曾经说过,SDRAM的优点除了肚量大以外,无论是访问速度还有操作程度也不及片上内存。一旦SDRAM谋朝散位成功,VGA控制模块,VGA储存模块还有VGA功能模块之间就会失去同步性。此外,SDRAM也不能经过综合器初始化内容。不管怎么样,实验二十六已经完成任务,以后的问题以后再说吧。

  • 相关阅读:
    字符编码
    数据类型
    流程控制之判断,while循环,for循环
    数据类型,与用户交互,格式化输出,基本运算符
    操作系统,编程语言分类,执行python两种方式,变量,内存管理,定义变量的三个特征
    计算机硬件基础-笔记
    20200714_31adb命令和monkey压力稳定性测试
    20200707_28POM
    20200705_27DDT与Yaml数据驱动
    20200702_26UnitTest套件与运行器
  • 原文地址:https://www.cnblogs.com/alinx/p/4547190.html
Copyright © 2011-2022 走看看