FPGA常见接口及逻辑实现(四)—— VGA

目录

一、VGA接口简介

二、VGA接口实现思路

三、VGA接口的Verilog实现

四、VGA接口仿真

五、VGA接口的板级验证

六、视频相关


一、VGA接口简介

        VGA,全称为Video Graphics Array,是一种视频传输接口,传输模拟视频信号,早期的CRT显示器使用的都是VGA视频接口。

        首先讲讲CRT显示器的显示原理,CRT是阴极射线管,CRT显示器使用射线管发射电子撞击荧光屏来产生图像,每个撞击点就是一个像素,每个像素有RGB三个分量,分别是像素点的三个基本颜色Red红,Blue蓝,Green绿。射线管从屏幕的最左上角开始向右扫描,扫描到最右时即显示了一行图像。射线管扫描到最右之后,为了让射线管回到左侧,并且在回来的时候不要发射多余的电子,系统会产生一个水平同步信号,接收到同步信号的射线管会经过一段时间回到左侧继续进行下一行扫描。当一整个屏幕扫描完成时,即显示了完整一帧图像后,射线管位于最右下角,为了让射线管回到左上角,系统会产生一个垂直同步信号,接收到同步信号后,射线管会经过一段时间回到左上角继续下一帧的扫描。

        有了CRT显示原理的基础,再来看看VGA接口信号,VGA接口信号主要有R,G,B,水平同步,垂直同步这五个,每个信号的作用从CRT显示原理中都能很清晰的认识到,接下来的问题就是VGA接口的时序。众所周知视频有两大基本参数,分辨率和帧率,分辨率和帧率不同,对应的VGA时序也不同,VESA标准规定了各种常见分辨率下VGA的信号时序,以下以最常见的1920x1080@60Hz为例:

图1.1 - VGA时序

图1.2 - 1920x1080@60Hz时序

        简单概括一下,1920x1080@60Hz的视频一帧实际上总共有2200x1125个像素,其中有1920x1080个有效像素,其余为消隐像素。完整的一行中有280个消隐像素,在这280个像素期间,在第88个像素拉高水平同步,再计数44个像素之后拉低水平同步;完整的一帧中有45行消隐行,在这45行中,在4行拉高垂直同步,再计数5行之后拉低垂直同步。

        以上就是VGA时序的关键信息,不难看出,实现VGA接口主要就是产生正确的水平同步信号和垂直同步信号,至于RGB数据,在有效数据期间并行发送即可。

二、VGA接口实现思路

        VGA接口逻辑简单,通过两个计数器就能实现其功能,一个行计数器来计数每一行的像素,一个场计数器来计数每一场的行数。在计数的过程中产生其他的控制信号。

        VGA的接口信号只有RGB和Hsync和Vsync,但是目前的数字显示接口在VGA的基础上又增加了一个DE信号,为了方便兼容数字显示接口,我们设计VGA接口时将DE信号也加入其中。DE信号是Display Enable信号,即显示使能,此信号有效时表示当前传输的RGB数据为有效图像数据。顾名思义,DE信号应该仅在时序图中的Addr Time拉高,同样可以通过计数器简单控制。

        还有一点需要注意的是,不同的分辨率下VGA的时序是不同的,为了兼容不同的分辨率,要留出分辨率配置接口,具体包括水平方向的total,active,syncstart,syncend,垂直方向的total,active,syncstart,syncend,这种配置方式是和赛灵思的图像传输IP保持一致的,使用相同的配置接口可以增加对赛灵思IP核的理解。

        由于前面已经放过VESA标准手册里的时序图,本期我就不自己画时序图了~下面直接进入代码环节。

三、VGA接口的Verilog实现

        话不多说,直接看代码:

`timescale 1ns / 1ps

//     wire    [15:0]      h_total;
//     wire    [15:0]      h_active;
//     wire    [15:0]      h_syncstart;
//     wire    [15:0]      h_syncend;

//     wire    [15:0]      v_total;
//     wire    [15:0]      v_active;
//     wire    [15:0]      v_syncstart;
//     wire    [15:0]      v_syncend;
            
//     wire    [23:0]      data_in;

//     wire                de;
//     wire                hsync;
//     wire                vsync;
//     wire    [23:0]      rgb;

// vga_driver vga_driver_inst(
//     .clk            (clk),
//     .rst_n          (rst_n),
//     .h_total        (h_total),
//     .h_active       (h_active),
//     .h_syncstart    (h_syncstart),
//     .h_syncend      (h_syncend),
//     .v_total        (v_total),
//     .v_active       (v_active),
//     .v_syncstart    (v_syncstart),
//     .v_syncend      (v_syncend),
//     .data_in        (data_in),
//     .de             (de),
//     .hsync          (hsync),
//     .vsync          (vsync),
//     .rgb            (rgb));

module vga_driver(
    input               clk,
    input               rst_n,

    input   [15:0]      h_total,
    input   [15:0]      h_active,
    input   [15:0]      h_syncstart,
    input   [15:0]      h_syncend,

    input   [15:0]      v_total,
    input   [15:0]      v_active,
    input   [15:0]      v_syncstart,
    input   [15:0]      v_syncend,
    
    input   [23:0]      data_in,

    output              de,
    output              hsync,
    output              vsync,
    output  [23:0]      rgb
    );

    reg     [15:0]      h_cnt;
    reg     [15:0]      v_cnt;

    wire one_line = h_cnt == h_total - 1;
    wire one_frame = v_cnt == v_total - 1;

    assign de = h_cnt < h_active & v_cnt < v_active;
    assign hsync = h_cnt >= h_syncstart & h_cnt < h_syncend;
    assign vsync = v_cnt >= v_syncstart & v_cnt < v_syncend;
    assign rgb = de ? data_in: 24'h0;

    always @(posedge clk) begin
        if(!rst_n)
            h_cnt <= 0;
        else
            h_cnt <= one_line ? 0 : h_cnt + 1;
    end

    always @(posedge clk) begin
        if(!rst_n)
            v_cnt <= 0;
        else if(one_line)
            v_cnt <= one_frame ? 0 : v_cnt + 1;
    end

endmodule

        可以看出本次VGA接口的代码非常简单,学习Verilog两小时半就能写出一个完美的VGA接口。

        然后讲几个实现过程中要注意的点,对于控制信号(包括Hsync,Vsync和De)的产生,可以用时序逻辑也可以用组合逻辑,模拟显示接口对于同步信号的时序要求没有那么高,只要在消隐期间拉高几个周期的同步信号,后端能检测到即可,早或者晚几个纳秒都是可以的。现在的数字显示接口沿用了VGA的扫描方式,同步信号也不需要太精确,但是De信号还是需要和数据时序同步的,不然错一个周期就会漏一个数据,所以De信号最好用reg类型通过时序逻辑实现。

        此处再讲一点关于时序优化的问题,一般1080p的图像使用148.5M的时钟,时序很容易收敛,Verilog怎么写都无所谓,但是当传输4K的图像时,要用到297M的时钟,4K@60Hz还是双边沿传输,对于时序收敛的要求就比较高了,这时Verilog的编码方式就要慎重考虑了,对于VGA接口,主要是控制信号的产生方式,像上述代码中大于和小于的组合逻辑肯定是延迟比较大的,替换为通过等于判断来产生信号变化可以降低关键路径延迟,可以更好的实现时序收敛。

四、VGA接口仿真

        VGA的仿真主要是观察产生的控制信号的时序。

        发送一行数据的时序:

图4.1 - 行时序

        发送一帧数据的时序:

图4.2 - 场时序

        数据量很大,基本上看不出什么,我们将观察的重心仅放到同步信号拉高的那几个周期再看看:

图4.3 - 行同步信号

图4.4 - 场同步信号

        可以看出Hsync和Vsync按照预期在syncstart拉高,持续到syncend拉低。仿真结果复合预期。

五、VGA接口的板级验证

        虽然写的是VGA接口的板级验证,但是现在开发板上都很少用VGA接口了,就算有VGA接口,我也没有VGA连接线,我就用VGA的后辈DVI的后辈HDMI接口来代替验证一下吧。

        仅用作视频传输时,HDMI和DVI接口是完全一致的,而DVI接口又是VGA接口的数字化串行接口,所以HDMI ≈ VGA,而需要用HDMI接口验证VGA接口代码,就要实现上述的数字串行化,具体包括编码和串化,这部分网上有很多IP核,我们直接调用digilent的rgb2dvi IP实现即可。

        至于要显示的数据,再编写一个简单的彩条生成模块即可。

        用HDMI传输图像时,因为是串行接口,串行时钟需要十倍频率,即使双边沿传输也需要五倍频率,148.5M的五倍频率在FPGA内是很难实现的,我的K7-325T-2的BUFG理论上最高也只能跑到709M,所以要使用协议标准的像素时钟,最高只能用74.25M时钟,即720P@60Hz或1080P@30Hz。

        简单修改VGA接口模块,使其内部产生彩条数据,然后例化连接VGA接口模块和dvi IP核,约束,综合实现,生成比特流。这里注意HDMI的串行数据和串行时钟IO要约束为TMDS_33。

        修改后的VGA接口代码:

`timescale 1ns / 1ps

//     wire    [15:0]      h_total;
//     wire    [15:0]      h_active;
//     wire    [15:0]      h_syncstart;
//     wire    [15:0]      h_syncend;

//     wire    [15:0]      v_total;
//     wire    [15:0]      v_active;
//     wire    [15:0]      v_syncstart;
//     wire    [15:0]      v_syncend;
            
//     wire    [23:0]      data_in;

//     wire                de;
//     wire                hsync;
//     wire                vsync;
//     wire    [23:0]      rgb;

// vga_driver vga_driver_inst(
//     .clk            (clk),
//     .rst_n          (rst_n),
//     .h_total        (h_total),
//     .h_active       (h_active),
//     .h_syncstart    (h_syncstart),
//     .h_syncend      (h_syncend),
//     .v_total        (v_total),
//     .v_active       (v_active),
//     .v_syncstart    (v_syncstart),
//     .v_syncend      (v_syncend),
//     .data_in        (data_in),
//     .de             (de),
//     .hsync          (hsync),
//     .vsync          (vsync),
//     .rgb            (rgb));

module vga_driver(
    input               clk,
    input               rst_n,

    input   [15:0]      h_total,
    input   [15:0]      h_active,
    input   [15:0]      h_syncstart,
    input   [15:0]      h_syncend,

    input   [15:0]      v_total,
    input   [15:0]      v_active,
    input   [15:0]      v_syncstart,
    input   [15:0]      v_syncend,
    
//    input   [23:0]      data_in,

    output              de,
    output              hsync,
    output              vsync,
    output  [23:0]      rgb
    );

    reg     [15:0]      h_cnt;
    reg     [15:0]      v_cnt;
    
    reg     [23:0]      color_bar;

    wire one_line = h_cnt == h_total - 1;
    wire one_frame = v_cnt == v_total - 1;

    assign de = h_cnt < h_active & v_cnt < v_active;
    assign hsync = h_cnt >= h_syncstart & h_cnt < h_syncend;
    assign vsync = v_cnt >= v_syncstart & v_cnt < v_syncend;
    assign rgb = de ? color_bar : 24'h0;
    
    always @(posedge clk) begin
        if(h_cnt == 1918)
            color_bar <= 24'hff0000;
        else if(h_cnt == 274)
            color_bar <= 24'hffa800;
        else if(h_cnt == 548)
            color_bar <= 24'hffff00;
        else if(h_cnt == 822)
            color_bar <= 24'h00ff00;
        else if(h_cnt == 1096)
            color_bar <= 24'h00ffff;
        else if(h_cnt == 1370)
            color_bar <= 24'h0000ff;
        else if(h_cnt == 1644)
            color_bar <= 24'hff00ff;
    end

    always @(posedge clk) begin
        if(!rst_n)
            h_cnt <= 0;
        else
            h_cnt <= one_line ? 0 : h_cnt + 1;
    end

    always @(posedge clk) begin
        if(!rst_n)
            v_cnt <= 0;
        else if(one_line)
            v_cnt <= one_frame ? 0 : v_cnt + 1;
    end

endmodule

        顶层代码:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2024/05/28 09:40:52
// Design Name: 
// Module Name: hdmi_top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module hdmi_top(
    input               sys_clk,
    input               sys_rst_n,
    
    output              tmds_clk_p,
    output              tmds_clk_n,
    output  [2:0]       tmds_data_p,
    output  [2:0]       tmds_data_n
    );
    
    wire                clk_27M;
    wire                clk_74_25M;
    wire                clk_371_25M;
    
    wire                de;
    wire                hsync;
    wire                vsync;
    wire    [23:0]      rgb;
    
    clk_27 clk_27_inst(
    .clk_in1            (sys_clk),
    .clk_out1           (clk_27M),
    .locked             ());

    clk_pix clk_pix_inst(
    .clk_27             (clk_27M),
    .clk_pix            (clk_74_25M),
    .clk_pix5x          (clk_371_25M),
    .locked             ());
    
    vga_driver vga_driver_inst(
    .clk                (clk_74_25M),
    .rst_n              (sys_rst_n),
    
    .h_total            (2200),
    .h_active           (1920),
    .h_syncstart        (2008),
    .h_syncend          (2052),
    
    .v_total            (1125),
    .v_active           (1080),
    .v_syncstart        (1083),
    .v_syncend          (1088),
    
    .de                 (de),
    .hsync              (hsync),
    .vsync              (vsync),
    .rgb                (rgb));
    
    dvi_encoder dvi_encoder_inst(
    .pixelclk           (clk_74_25M),
    .pixelclk5x         (clk_371_25M),
    .rst_p              (~sys_rst_n),
    .blue_din           (rgb[7:0]),
    .green_din          (rgb[15:8]),
    .red_din            (rgb[23:16]),
    .hsync              (hsync),
    .vsync              (vsync),
    .de                 (de),
    .tmds_clk_p         (tmds_clk_p),
    .tmds_clk_n         (tmds_clk_n),
    .tmds_data_p        (tmds_data_p),
    .tmds_data_n        (tmds_data_n));
    
endmodule

        布局布线之后发现时序不满足,这就是前文提到的时序优化问题,不过这一点时序问题不大,显示接口对于时序要求还是很宽松的,能跑就行。

        下载比特流之后连接屏幕,就可以观察到显示的彩条了:

图5.1 - HDMI彩条显示

        至此,VGA接口实验圆满完成!

六、视频相关

        我从业的方向就是视频相关领域,之后这段时间更新的视频接口属于是专业对口了,在工作的过程中,有很多视频接口或视频协议的内容在网上很难了解到,有时候一个很简单的东西却翻来覆去看了好几遍协议文档才弄明白,在更新之后几篇文章时,我会将工作中总结的一些经验和收获分享出来,希望能对大家有所帮助!

        那么从本文开始,刚开始工作有一个困扰本人很久的问题,很多板卡上对于VGA,DVI,HDMI三种接口的叫法比较混乱。

        首先明确一点,上文中出现的hsync,vsync,de和24位RGB数据这一组接口严格意义上不是VGA接口,一般可以叫做RGB接口,或者RGB 888接口,代表R、G、B三个颜色分量各有8位数据,相对应的有RGB 565接口,R、G、B三个分量各有5、6、5位数据。

        而VGA接口是指RGB接口通过数模转换之后的模拟接口,VGA接口主要有五个信号,三根RGB信号线,一个Hsync和一个Vsync信号。

        与VGA类似,DVI接口是RGB接口通过编码串行化之后的数字接口,主要有三位差分数据线tmds_data[2:0]和一位差分时钟线tmds_clk。

        至于HDMI,在DVI的基础上加入了HPD热插拔检测信号,DDC控制信息通道,还有更多其他的功能。

        本文实际上实现的是RGB接口时序,由于VGA接口信号和RGB没有太大的变化,所以很多地方直接就叫做VGA接口了,但是这种叫法细究之下很容易让人混乱,比如有很多视频接口芯片实现的是RGB数据的编码和串行化,再作为DVI或HDMI数据发送出去,那这种时候要是说接口芯片和FPGA以VGA接口连接就很奇怪。

        RGB接口是芯片之间连接的并行接口,也有RGB接口直连的显示器件,比如LCD屏幕,但是都只能短距离传输,而VGA接口是视频源设备和视频显示设备之间连接的接口,传输距离比起RGB接口要远很多,不能混为一谈。

        前段时间新项目开始了,天天忙着做项目,这两天终于搞完了,又可以快乐摸鱼写文章了。

        在此处偷偷DISS一下微相科技,买了个他们的z7010板子,结果原理图和例程不符,例程文档好几处写的都是错的,用户群里看到过好几次有人说他们例程写的是错的,但从来没人理。

        我遇到的问题是例程里写的eeprom是1字节地址,我用I2C给1字节地址死活读不出来东西,这个I2C都是我用了好久的模块了,不可能有问题,排查了一万遍发现原理图上画的eeprom和例程里写的eeprom型号不一样,是2字节地址的,换成2字节读写瞬间就正常了,浪费我两天时间,气死我了,一开始我还去用户群里耐心反馈,结果根本没人理,我只好去淘宝给了差评,差评的结果就是客服说资料给错了,给我发了份新资料问我能不能删差评,我一看新资料例程里面还是错的,我是真无语了,我心软,又不想为难客服,就删了差评,但是我以后再也不会买微相的板子了!

        下一期讲讲BT1120接口,欢迎大家持续关注!

  • 26
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FPGA中使用VGA时序显示图像的仿真过程可以通过以下步骤实现: 1. 首先,需要使用时钟生成模块(PLL)来生成VGA工作所需的时钟信号。这个时钟信号将用于控制图像的扫描和刷新。 2. 接下来,使用图像生成模块(VGA_P)来确定每个有效图像区域的像素点。这个模块将根据控制模块传入的坐标信号,生成待显示图像的色彩信息。 3. 图像控制模块(VGA_C)负责生成行场同步信号,并确定有效图像区域的位置和零点坐标。这个模块还需要接收RGB信息和行场同步信号。 4. 最后,将生成的图像信号通过VGA端口输出,连接到VGA显示器上进行显示。 通过以上步骤,可以在FPGA中使用VGA时序显示图像的仿真。这样,你就可以在仿真环境中验证图像的显示效果,并进行必要的调试和优化。 #### 引用[.reference_title] - *1* [FPGA学习——VGA显示](https://blog.csdn.net/weixin_56102526/article/details/124964347)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [FPGA——VGA显示协议](https://blog.csdn.net/a17377547725/article/details/129729079)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值