• 【正点原子FPGA连载】 第十七章 HDMI彩条显示实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0


    1)实验平台:正点原子MPSoC开发板
    2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
    3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html

    第十七章 HDMI彩条显示实验

    HDMI接口在消费类电子行业,如电脑、液晶电视、投影仪等产品中得到了广泛的应用。一些专业的视频设备如摄像机、视频切换器等也都集成了HDMI接口。本章我们将学习如何驱动开拓者开发板上的HDMI接口。
    本章包括以下几个部分:
    151717.1简介
    17.2实验任务
    17.3硬件设计
    17.4程序设计
    17.5下载验证

    17.1简介
    HDMI是新一代的多媒体接口标准,英文全称是High-Definition Multimedia Interface,即高清多媒体接口。它能够同时传输视频和音频,简化了设备的接口和连线;同时提供了更高的数据传输带宽,可以传输无压缩的数字音频及高分辨率视频信号。HDMI 1.0版本于2002年发布,最高数据传输速度为5Gbps;而2017年发布的HDMI 2.1标准的理论带宽可达48Gbps。
    在HDMI接口出现之前,被广泛应用的是VGA接口。VGA的全称是Video Graphics Array,即视频图形阵列,是一个使用模拟信号进行视频传输的标准。VGA接口采用15针插针式结构,里面传输模拟信号颜色分量、同步等信号,是很多老显卡、笔记本和投影仪所使用的接口。由于VGA接口传输的是模拟信号,其信号容易受到干扰,因此VGA在高分辨率下字体容易虚,信号线长的话,图像有拖尾现象。VGA接在这里插入图片描述
    口由下图所示:

    图 17.1.1 VGA接口
    VGA接口除信号容易受到干扰外,其体积也较大,因此VGA接口已逐渐退出舞台,一些显示器也不再带有VGA接口,在数字设备高度发展的今天,取而代之的是HDMI接口和DP(Display Port)接口等。
    HDMI向下兼容DVI,但是DVI(数字视频接口)只能用来传输视频,而不能同时传输音频,这是两者最主要的差别。此外,DVI接口的尺寸明显大于HDMI接口,如下图所示:
    在这里插入图片描述

    图 17.1.2 DVI接口(左)和HDMI接口(右)实物图
    图 17.1.2右侧是生活中最常见的A型HDMI接口,其引脚定义如下图所示:
    在这里插入图片描述

    图 17.1.3 HDMI接口引脚定义
    DVI和HDMI接口协议在物理层使用TMDS标准传输音视频数据。TMDS(Transition Minimized Differential Signaling,最小化传输差分信号)是美国Silicon Image公司开发的一项高速数据传输技术,在DVI和HDMI视频接口中使用差分信号传输高速串行数据。TMDS差分传输技术使用两个引脚(如图 17.1.3中的“数据2+”和“数据2-”)来传输一路信号,利用这两个引脚间的电压差的正负极性和大小来决定传输数据的数值(0或1)。
    Xilnx在Spartan-3A系列之后的器件中,加入了对TMDS接口标准的支持,用于在FPGA内部实现DVI和HDMI接口。
    由于本次实验只是使用HDMI接口来显示图像,不需要传输音频,因此我们只需要实现DVI接口的驱动逻辑即可。不过在此之前我们还需要简单地了解一下TMDS视频传输协议。
    图 17.1.4是TMDS发送端和接收端的连接示意图。DVI或HDMI视频传输所使用的TMDS连接通过四个串行通道实现。对于DVI来说,其中三个通道分别用于传输视频中每个像素点的红、绿、蓝三个颜色分量(RGB 4:4:4格式)。HDMI默认也是使用三个RGB通道,但是它同样可以选择传输像素点的亮度和色度信息(YCrCb 4:4:4或YCrCb 4:2:2格式)。第四个通道是时钟通道,用于传输像素时钟。独立的TMDS时钟通道为接收端提供接收的参考频率,保证数据在接收端能够正确恢复。
    在这里插入图片描述

    图 17.1.4 TMDS连接示意图
    如果每个像素点的颜色深度为24位,即RGB每个颜色分量各占8位,那么每个通道上的颜色数据将通过一个8B/10B的编码器(Encoder)来转换成一个10位的像素字符。然后这个10位的字符通过并串转换器(Serializer)转换成串行数据,最后由TMDS数据通道发送出去。这个10:1的并转串过程所生成的串行数据速率是实际像素时钟速率的10倍。
    在传输视频图像的过程中,数据通道上传输的是编码后的有效像素字符。而在每一帧图像的行与行之间,以及视频中不同帧之间的时间间隔(消隐期)内,数据通道上传输的则是控制字符。每个通道上有两位控制信号的输入接口,共对应四种不同的控制字符。这些控制字符提供了视频的行同步(HSYNC)以及帧同步(VSYNC)信息,也可以用来指定所传输数据的边界(用于同步)。
    对于DVI传输,整个视频的消隐期都用来传输控制字符。而HDMI传输的消隐期除了控制字符之外,还可以用于传输音频或者其他附加数据,比如字幕信息等。这就是DVI和HDMI协议之间最主要的差别。从图 17.1.4中也可以看出这一差别,即“Auxiliary Data”接口标有“HDMI Olny”,即它是HDMI所独有的接口。
    从前面的介绍中我们可以看出,TMDS连接从逻辑功能上可以划分成两个阶段:编码和并串转换。在编码阶段,编码器将视频源中的像素数据、HDMI的音频/附加数据,以及行同步和场同步信号分别编码成10位的字符流。然后在并串转换阶段将上述的字符流转换成串行数据流,并将其从三个差分输出通道发送出去。
    DVI编码器在视频有效数据段输出像素数据,在消隐期输出控制数据,如图 17.1.5所示。其中VDE(Video Data Enable)为高电平时表示视频数据有效,为低电平代表当前处于视频消隐期。
    在这里插入图片描述

    图 17.1.5 DVI编码输出示意图
    图 17.1.6给出了三个通道的DVI编码器示意图。对于像素数据的RGB三个颜色通道,编码器的逻辑是完全相同的。VDE用于各个通道选择输出视频像素数据还是控制数据。HSYNC和VSYNC信号在蓝色通道进行编码得到10位字符,然后在视频消隐期传输。绿色和红色通道的控制信号C0和C1同样需要进行编码,并在消隐期输出。但是DVI规范中这两个通道的控制信号是预留的(未用到),因此将其置为2’b00。
    在这里插入图片描述

    图 17.1.6 DVI编码器示意图
    每个通道输入的视频像素数据都要使用DVI规范中的TMDS编码算法进行编码。每个8-bit的数据都将被转换成460个特定10-bit字符中的一个。这个编码机制大致上实现了传输过程中的直流平衡,即一段时间内传输的高电平(数字“1”)的个数大致等于低电平(数字“0”)的个数。同时,每个编码后的10-bit字符中状态跳转(“由1到0”或者“由0到1”)的次数将被限制在五次以内。
    除了视频数据之外,每个通道2-bit控制信号的状态也要进行编码,编码后分别对应四个不同的10-bit控制字符,分别是10’b1101010100,10’b0010101011,10’b0101010100,和10’b1010101011。可以看出,每个控制字符都有七次以上的状态跳转。视频字符和控制字符状态跳转次数的不同将会被用于发送和接收设备的同步。
    HDMI协议与DVI协议在很多方面都是相同的,包括物理连接(TMDS)、有效视频编码算法以及控制字符的定义等。但是,相比于DVI,HDMI在视频的消隐期会传输更多的数据,包括音频数据和附加数据。4-bit音频和附加数据将通过TERC4编码机制转换成10-bit TERC4字符,然后在绿色和红色通道上传输。
    HDMI在输入附加数据的同时,还需要输入ADE(Aux/Audio Data Enable)信号,其作用和VDE是类似的:当ADE为高电平时,表明输入端的附加数据或者音频数据有效。如果大家想了解更多有关HDMI的细节,可以参考开发板资料(A盘)/软件资料中的HDMI接口规范——《High-Definition Multimedia Interface Specification Version 1.3a》。为了简单起见,我们在这里把HDMI接口当作DVI接口进行驱动。
    在编码之后,3个通道的10-bit字符将进行并串转换,这一过程是使用UltraScale系列FPGA中专用的硬件资源来实现的。UltraScale设备中提供了专用的并串转换器——OSERDESE3。单一的OSERDESE3是一个4位或8位并行-串行转换器,不支持10:1的并串转换,所以先将10位数据转换为4位的数据,然后利用OSERDESE3进行4:1的并串转换。
    17.2实验任务
    本章的实验任务是驱动DFZU2EG/4EV MPSoC开发板上的HDMI接口,在显示器上显示彩条图案。
    17.3硬件设计
    在这里插入图片描述

    图 17.3.1 HDMI接口原理图1
    图 17.3.1是DFZU2EG/4EV MPSoC开发板的HDMI接口原理图的一部分,其中HDMI的三个数据通道HDMIB_C_D[2:0]和一个时钟通道HDMI_CLK直接与DFZU2EG/4EV MPSoC开发板的TMDS差分引脚相连。
    HDMIB_HPD_LS指的是热拔插检测(Hot Plug Detect),当视频设备与接收设备通过HDMI连接时,接收设备将HPD置为高电平,通知发送设备。当发送设备检测到HPD为低电平时,表明断开连接。
    在这里插入图片描述

    图 17.3.2 HDMI接口原理图2
    在图 17.3.2中,HDMIB_SCL_LS和HDMIB_SDA_LS是HDMI接口的显示数据通道(DDC,Display Data Channel),用于HDMI发送端和接收端之间交换一些配置信息,通过I2C协议通信。发送端通过 DDC 通道,读取接收端保存在HDMI显示器EEPROM中的EDID数据,获取接收端的信息,确认接收端终端显示的设置和功能,决定跟接收端之间以什么格式传输音/视频数据。
    本次实验只使用了HDMI接口的TMDS数据和TMDS时钟信号,各端口的管脚分配如下表所示:
    表 17.3.1 HDMI彩条显示实验管脚分配
    信号名 方向 管脚 端口说明 电平标准
    sys_clk_p input AE5 系统差分输入时钟 DIFF_HSTL_I_12
    sys_clk_n input AF5 系统差分输入时钟 DIFF_HSTL_I_12
    sys_rst_n input AH11 系统复位,低有效 LVCMOS33
    tmds_data_p[2] output G3 TMDS 数据通道2(正极) LVDS
    tmds_data_p[1] output F2 TMDS 数据通道1(正极) LVDS
    tmds_data_p[0] output G1 TMDS 数据通道0(正极) LVDS
    tmds_clk_p output E1 TMDS 时钟通道(正极) LVDS
    需要注意的是,TMDS数据和时钟信号需要在约束文件中指定电平标准为TMDS_33。另外,对于差分信号我们只需要指定正极的引脚位置,工具会自动对负极进行管脚分配。
    相关的管脚约束如下所示:
    #IO管脚约束
    #时钟周期约束
    create_clock -name sys_clk_p -period 10.000 [get_ports sys_clk_p]
    #时钟
    set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_p]
    set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_n]
    set_property PACKAGE_PIN AE5 [get_ports sys_clk_p]
    set_property PACKAGE_PIN AF5 [get_ports sys_clk_n]
    #复位
    set_property -dict {PACKAGE_PIN AH11 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
    #HDMI
    set_property -dict {PACKAGE_PIN G3 IOSTANDARD LVDS } [get_ports {tmds_data_p[2]}]
    set_property -dict {PACKAGE_PIN F2 IOSTANDARD LVDS } [get_ports {tmds_data_p[1]}]
    set_property -dict {PACKAGE_PIN G1 IOSTANDARD LVDS } [get_ports {tmds_data_p[0]}]
    set_property -dict {PACKAGE_PIN E1 IOSTANDARD LVDS } [get_ports tmds_clk_p]
    17.4程序设计
    由于本次实验只需要通过HDMI接口显示图像,因此将其当成DVI接口进行驱动。另外我们只需要实现图像的发送功能。由此得出本次实验的系统框图如下所示:
    在这里插入图片描述

    图 17.4.1 系统框图
    本次实验在LCD彩条显示实验的基础上添加一个RGB2DVI模块,将RGB888格式的视频图像转换成TMDS数据输出。本章的重点是介绍RGB2DVI模块,其余模块的介绍请参考LCD彩条显示实验。
    RGB2DVI顶层模块的设计框图如下所示:
    在这里插入图片描述

    图 17.4.2 RGB2DVI模块框图
    图 17.4.2中,Encoder模块负责对数据进行编码,Serializer模块对编码后的数据进行并串转换,最后通过OBUFDS转化成TMDS差分信号传输。
    整个系统需要三个输入时钟,一个是视频的像素时钟Pixel Clk,第二个时钟是10位数据转换为4位数据的时钟pixel_clk_2_5x,第三个时钟是在第二个时钟进行的倍频时钟pixel_clk_5x。由前面的简介部分我们知道,并串转换过程要实现的是10:1的转换率,但是OSERDESE3不能直接进行10:1的并串转换,所以我们先将10位数据转换为4位的数据,然后利用OSERDESE3进行4:1的并串转换。pixel_clk_5x是pixel_clk_2_5x的两倍时钟是因为OSERDESE3模块可以实现DDR的功能,即它在pixel_clk_2_5x时钟的基础上又实现了双倍数据速率。
    TMDS连接的时钟通道我们采用与数据通道相同的并转串逻辑来实现。通过对10位二进制序列10’b11111_00000在10倍像素时钟频率下进行并串转换,就可以得到像素时钟频率下的TMDS参考时钟。
    另外需要注意的是,图中左下脚HDMI的音频/附加数据输入在本次实验中并未用到,因此以虚线表示。
    首先来看一下Encoder模块的代码:

    1   `timescale 1 ps / 1ps
    2   
    3   module dvi_encoder (
    4     input            clkin,    // pixel clock input
    5     input            rstin,    // async. reset input (active high)
    6     input      [7:0] din,      // data inputs: expect registered
    7     input            c0,       // c0 input
    8     input            c1,       // c1 input
    9     input            de,       // de input
    10    output reg [9:0] dout      // data outputs
    11  );
    12  
    13    
    14    // Counting number of 1s and 0s for each incoming pixel
    15    // component. Pipe line the result.
    16    // Register Data Input so it matches the pipe lined adder
    17    // output
    18    
    19    reg [3:0] n1d; //number of 1s in din
    20    reg [7:0] din_q;
    21  
    22  //计算像素数据中“1”的个数
    23    always @ (posedge clkin) begin
    24      n1d <=#1 din[0]+din[1]+din[2]+din[3]+din[4]+din[5]+din[6]+din[7];
    25  
    26      din_q <=#1 din;  
    27    end
    28  
    29    ///
    30    // Stage 1: 8 bit -> 9 bit
    31    // Refer to DVI 1.0 Specification, page 29, Figure 3-5
    32    ///
    33    wire decision1;
    34  
    35    assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q[0] == 1'b0));
    36  
    37    wire [8:0] q_m;
    38    assign q_m[0] = din_q[0];
    39    assign q_m[1] = (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
    40    assign q_m[2] = (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
    41    assign q_m[3] = (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
    42    assign q_m[4] = (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
    43    assign q_m[5] = (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
    44    assign q_m[6] = (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
    45    assign q_m[7] = (decision1) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);
    46    assign q_m[8] = (decision1) ? 1'b0 : 1'b1;
    47  
    48    /
    49    // Stage 2: 9 bit -> 10 bit
    50    // Refer to DVI 1.0 Specification, page 29, Figure 3-5
    51    /
    52    reg [3:0] n1q_m, n0q_m; // number of 1s and 0s for q_m
    53    always @ (posedge clkin) begin
    54      n1q_m  <=#1 q_m[0]+q_m[1]+q_m[2]+q_m[3]+q_m[4]+q_m[5]+q_m[6]+q_m[7];
    55      n0q_m  <=#1 4'h8 - (q_m[0]+q_m[1]+q_m[2]+q_m[3]+q_m[4]+q_m[5]+q_m[6]+q_m[7]);
    56    end
    57  
    58    parameter CTRLTOKEN0 = 10'b1101010100;
    59    parameter CTRLTOKEN1 = 10'b0010101011;
    60    parameter CTRLTOKEN2 = 10'b0101010100;
    61    parameter CTRLTOKEN3 = 10'b1010101011;
    62  
    63    reg [4:0] cnt; //disparity counter, MSB is the sign bit
    64    wire decision2, decision3;
    65  
    66    assign decision2 = (cnt == 5'h0) | (n1q_m == n0q_m);
    67    /
    68    // [(cnt > 0) and (N1q_m > N0q_m)] or [(cnt < 0) and (N0q_m > N1q_m)]
    69    /
    70    assign decision3 = (~cnt[4] & (n1q_m > n0q_m)) | (cnt[4] & (n0q_m > n1q_m));
    71  
    72    
    73    // pipe line alignment
    74    
    75    reg       de_q, de_reg;
    76    reg       c0_q, c1_q;
    77    reg       c0_reg, c1_reg;
    78    reg [8:0] q_m_reg;
    79  
    80    always @ (posedge clkin) begin
    81      de_q    <=#1 de;
    82      de_reg  <=#1 de_q;
    83      
    84      c0_q    <=#1 c0;
    85      c0_reg  <=#1 c0_q;
    86      c1_q    <=#1 c1;
    87      c1_reg  <=#1 c1_q;
    88  
    89      q_m_reg <=#1 q_m;
    90    end
    91  
    92    ///
    93    // 10-bit out
    94    // disparity counter
    95    ///
    96    always @ (posedge clkin or posedge rstin) begin
    97      if(rstin) begin
    98        dout <= 10'h0;
    99        cnt <= 5'h0;
    100     end else begin
    101       if (de_reg) begin
    102         if(decision2) begin
    103           dout[9]   <=#1 ~q_m_reg[8]; 
    104           dout[8]   <=#1 q_m_reg[8]; 
    105           dout[7:0] <=#1 (q_m_reg[8]) ? q_m_reg[7:0] : ~q_m_reg[7:0];
    106 
    107           cnt <=#1 (~q_m_reg[8]) ? (cnt + n0q_m - n1q_m) : (cnt + n1q_m - n0q_m);
    108         end else begin
    109           if(decision3) begin
    110             dout[9]   <=#1 1'b1;
    111             dout[8]   <=#1 q_m_reg[8];
    112             dout[7:0] <=#1 ~q_m_reg[7:0];
    113 
    114             cnt <=#1 cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);
    115           end else begin
    116             dout[9]   <=#1 1'b0;
    117             dout[8]   <=#1 q_m_reg[8];
    118             dout[7:0] <=#1 q_m_reg[7:0];
    119 
    120             cnt <=#1 cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);
    121           end
    122         end
    123       end else begin
    124         case ({c1_reg, c0_reg})
    125           2'b00:   dout <=#1 CTRLTOKEN0;
    126           2'b01:   dout <=#1 CTRLTOKEN1;
    127           2'b10:   dout <=#1 CTRLTOKEN2;
    128           default: dout <=#1 CTRLTOKEN3;
    129         endcase
    130 
    131         cnt <=#1 5'h0;
    132       end
    133     end
    134   end
    135   
    136 endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136

    dvi_encoder模块按照DVI接口规范中TMDS编码算法对输入的8位像素数据以及2位行场同步信号进行编码。该模块是Xilinx应用笔记XAPP460中所提供的编码模块,其具体实现的编码算法如下图所示:
    在这里插入图片描述

    图 17.4.3 TMDS编码算法
    TMDS通过逻辑算法将8位字符数据通过最小转换编码为10位字符数据,前8位数据由原始信号经运算后获得,第9位表示运算的方式,1表示异或0表示异或非。经过DC平衡后(第10位),采用差分信号传输数据。第10位实际是一个反转标志位,1表示进行了反转而0表示没有反转,从而达到 DC 平衡。
    接收端在收到信号后,再进行相反的运算。TMDS和LVDS、TTL相比有较好的电磁兼容性能。这种算法可以减小传输信号过程的上冲和下冲,而DC平衡使信号对传输线的电磁干扰减少,可以用低成本的专用电缆实现长距离、高质量的数字信号传输。
    图 17.4.3所描述的算法是DVI接口规范所定义的,我们不作深入研究,大家有兴趣的话也可以对照dvi_encoder模块中的代码来分析整个算法流程是如何使用Verilog来实现的。算法中各个参数的含义如下图所示:
    在这里插入图片描述

    图 17.4.4 TMDS编码算法的参数
    TMDS编码之后的数据由Serializer模块进行并串转换,代码如下所示:

    1   module serializer_10_to_1(
    2       input [9:0] paralell_data,   // 10-bit 像素数据
    3       input       reset,           // 复位信号,高电平有效
    4       input       paralell_clk,    // pixel_clk (1/10串行传输速率时钟)
    5       input       pixel_clk_5x,    
    6       // pixel_clk的5倍时钟 (1/2串行传输速率时钟)
    7       input       pixel_clk_2_5x,  
    8       // pixel_clk的2.5倍时钟 (1/4串行传输速率时钟) 
    9       output      serial_data_out  // 输出串行数据
    10  );
    11  
    12  //reg define
    13  reg  [3:0]  wr_addr;      // 写地址
    14  reg  [3:0]  rd_addr;      // 读地址
    15  wire [9:0]  rd_curr;      // 当前读数据
    16  reg  [9:2]  rd_last;      // 寄存读到的数据
    17  reg  [2:0]  rd_ctrl_cnt;  // 读控制计数器
    18  reg  [3:0]  tx_data;      // 4bit发送数据 
    19  
    20  //*****************************************************
    21  //**                    main code
    22  //***************************************************** 
    23  
    24  //RAM写地址,0~15循环计数
    25  always @ (posedge paralell_clk) begin
    26      if(reset)
    27          wr_addr <= 4'd0;
    28      else
    29          wr_addr <= wr_addr + 1'b1;
    30  end
    31  
    32  //寄存读到的数据
    33  always @(posedge pixel_clk_2_5x) begin
    34      rd_last[9:2] <= rd_curr[9:2];
    35  end
    36  
    37  //读控制计数器,将读到的10位数据转成4位数据
    38  always @ (posedge pixel_clk_2_5x) begin
    39      if(reset) begin
    40          rd_ctrl_cnt<= 3'd0;
    41          rd_addr <= 4'd0;
    42      end
    43      else begin
    44          case (rd_ctrl_cnt) 
    45              3'h0 : begin 
    46                  rd_addr <= rd_addr;
    47                  tx_data <= rd_curr[3:0];
    48                  rd_ctrl_cnt<= rd_ctrl_cnt + 1'b1;
    49              end
    50              3'h1 : begin 
    51                  rd_addr <= rd_addr + 1'b1;
    52                  tx_data <= rd_curr[7:4];
    53                  rd_ctrl_cnt<= rd_ctrl_cnt + 1'b1;
    54              end
    55              3'h2 : begin 
    56                  rd_addr <= rd_addr;
    57                  tx_data <= {rd_curr[1:0], rd_last[9:8]} ;
    58                  rd_ctrl_cnt<= rd_ctrl_cnt + 1'b1;
    59              end
    60              3'h3 : begin 
    61                  rd_addr <= rd_addr + 1'b1;
    62                  tx_data <= rd_curr[5:2];
    63                  rd_ctrl_cnt<= rd_ctrl_cnt + 1'b1;
    64              end
    65              3'h4 : begin 
    66                  rd_addr <= rd_addr; 
    67                  tx_data <= rd_last[9:6];
    68                  rd_ctrl_cnt<= 3'h0;
    69              end              
    70              default:begin
    71                  rd_ctrl_cnt<= 3'd0;
    72                  rd_addr <= 4'd0;
    73              end
    74          endcase
    75      end
    76  end
    77  
    78  //例化10个1位分布式RAM
    79  genvar i;
    80  generate
    81  for (i = 0 ; i <= 10 ; i = i+1) begin : bit
    82    RAM32X1D mem (
    83       .D     (paralell_data[i]),
    84       .WCLK  (paralell_clk),
    85       .WE    (!reset),
    86       .A0    (wr_addr[0]),
    87       .A1    (wr_addr[1]),
    88       .A2    (wr_addr[2]),
    89       .A3    (wr_addr[3]),
    90       .A4    (1'b0),
    91       .SPO   (),
    92       .DPRA0 (rd_addr[0]),
    93       .DPRA1 (rd_addr[1]),
    94       .DPRA2 (rd_addr[2]),
    95       .DPRA3 (rd_addr[3]),
    96       .DPRA4 (1'b0),
    97       .DPO   (rd_curr[i]));
    98  end
    99  endgenerate
    100 
    101 //4:1 DDR模式下的OSERDESE3
    102    OSERDESE3 #(
    103       .DATA_WIDTH            (4),               // 并行数据宽度 (4-8)
    104       .INIT                  (1'b0),            // OSERDES触发器的初始化值
    105       .IS_CLKDIV_INVERTED    (1'b0),            //(可选)翻转 CLKDIV
    106       .IS_CLK_INVERTED       (1'b0),            //(可选)翻转 CLK
    107       .IS_RST_INVERTED       (1'b0),            //(可选)翻转 RST
    108       .SIM_DEVICE            ("ULTRASCALE_PLUS")
    109 // 设置设备版本 (ULTRASCALE, ULTRASCALE_PLUS, ULTRASCALE_PLUS_ES1, 
    110 // ULTRASCALE_PLUS_ES2)                           
    111    )
    112    OSERDESE3_inst (
    113       .OQ        (serial_data_out),        //1-bit输出:串行数据输出
    114       .T_OUT     (),                       //1-bit输出:到IBO的三态控制输出
    115       .CLK       (pixel_clk_5x),           //1-bit输入:高速时钟
    116       .CLKDIV    (pixel_clk_2_5x),         //1-bit输入:分频时钟
    117       .D         ({4'b0,tx_data[3:0]}),    //4-bit输入:并行数据输入
    118       .RST       (reset),                  //1-bit输入:异步复位
    119       .T         (1'b0)                    //1-bit输入:来自结构的三态输入
    120    );
    121 
    122 endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122

    serdes_10_to_1模块的主要功能是实现10:1并串转换,但是OSERDESE3只能实现最多8:1或者4:1的转换率,所以在代码的第78-99行通过调用10个分布式RAM原语来实现将10位数据转换成4位数据。如下图所示,图中标线处当前10位的读数据为11_1111_1111,下一个10位读数据为01_0000_0000,从低位开始读取,转换出的五组4位数据分别为1111、1111、0011、0000、0100。
    在这里插入图片描述

    图 17.4.5 10位数据转为4位数据
    然后代码101-120是通过调用OSERDESE3原语来实现4:1的并串转换。原语是Xilinx器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于IP核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。OSERDESE3的4:1的并串转换时序如下图所示,要了解OSERDESE3的详细内容可以阅读Xilinx官方文档ug571-ultrascale-selectio。
    在这里插入图片描述

    图 17.4.6 OSERDESE3的4:1的并串转换
    最后,我们在RGB2DVI顶层模块中调用上述两个模块,其代码如下所示:

    1   module dvi_transmitter_top(
    2       input        pclk,           // pixel_clk  
    3       input        pixel_clk_5x,   // pixel_clk的5倍时钟   
    4       input        pixel_clk_2_5x, // pixel_clk的2.5倍时钟  
    5       input        reset_n,        // reset
    6       
    7       input [23:0] video_din,      // RGB888 video in
    8       input        video_hsync,    // hsync data
    9       input        video_vsync,    // vsync data
    10      input        video_de,       // data enable
    11      
    12      output       tmds_clk_p,     // TMDS 时钟通道
    13      output       tmds_clk_n,     
    14      output [2:0] tmds_data_p,    // TMDS 数据通道
    15      output [2:0] tmds_data_n,    
    16      output       tmds_oen        // TMDS 输出使能
    17      );
    18      
    19  //wire define    
    20  wire        reset;
    21      
    22  //并行数据
    23  wire [9:0]  red_10bit;
    24  wire [9:0]  green_10bit;
    25  wire [9:0]  blue_10bit;
    26  wire [9:0]  clk_10bit;  
    27    
    28  //串行数据
    29  wire [2:0]  tmds_data_serial;
    30  wire        tmds_clk_serial;
    31  
    32  //*****************************************************
    33  //**                    main code
    34  //***************************************************** 
    35  assign tmds_oen = 1'b1;  
    36  assign clk_10bit = 10'b1111100000;
    37  
    38  //异步复位,同步释放
    39  asyn_rst_syn u_asyn_rst_syn(
    40      .reset_n    (reset_n),
    41      .clk        (pclk),
    42      .syn_reset  (reset)    //高有效
    43      );
    44      
    45  //对三个颜色通道进行编码
    46  dvi_encoder encoder_b (
    47      .clkin      (pclk),
    48      .rstin      (reset),
    49      
    50      .din        (video_din[7:0]),
    51      .c0         (video_hsync),
    52      .c1         (video_vsync),
    53      .de         (video_de),
    54      .dout       (blue_10bit)
    55      ) ;
    56  
    57  dvi_encoder encoder_g (
    58      .clkin      (pclk),
    59      .rstin      (reset),
    60      .din        (video_din[15:8]),
    61      .c0         (1'b0),
    62      .c1         (1'b0),
    63      .de         (video_de),
    64      .dout       (green_10bit)
    65      ) ;
    66      
    67  dvi_encoder encoder_r (
    68      .clkin      (pclk),
    69      .rstin      (reset),
    70      .din        (video_din[23:16]),
    71      .c0         (1'b0),
    72      .c1         (1'b0),
    73      .de         (video_de),
    74      .dout       (red_10bit)
    75      ) ;
    76      
    77  //对编码后的数据进行并串转换
    78  serializer_10_to_1 serializer_b(
    79      .reset              (reset),                // 复位,高有效
    80      .paralell_clk       (pclk),                 // pixel_clk  
    81      .pixel_clk_5x       (pixel_clk_5x),         // pixel_clk的5倍时钟  
    82      .pixel_clk_2_5x     (pixel_clk_2_5x),       // pixel_clk的2.5倍时钟
    83      .paralell_data      (blue_10bit),           // 输入并行数据
    84      .serial_data_out    (tmds_data_serial[0])   // 输出串行数据
    85      );    
    86      
    87  serializer_10_to_1 serializer_g(
    88      .reset              (reset),                // 复位,高有效
    89      .paralell_clk       (pclk),                 // pixel_clk  
    90      .pixel_clk_5x       (pixel_clk_5x),         // pixel_clk的5倍时钟  
    91      .pixel_clk_2_5x     (pixel_clk_2_5x),       // pixel_clk的2.5倍时钟
    92      .paralell_data      (green_10bit),
    93      .serial_data_out    (tmds_data_serial[1])
    94      );
    95      
    96  serializer_10_to_1 serializer_r(
    97      .reset              (reset),                // 复位,高有效
    98      .paralell_clk       (pclk),                 // pixel_clk  
    99      .pixel_clk_5x       (pixel_clk_5x),         // pixel_clk的5倍时钟  
    100     .pixel_clk_2_5x     (pixel_clk_2_5x),       // pixel_clk的2.5倍时钟
    101     .paralell_data      (red_10bit),
    102     .serial_data_out    (tmds_data_serial[2])
    103     );
    104             
    105 serializer_10_to_1 serializer_clk(
    106     .reset              (reset),                // 复位,高有效
    107     .paralell_clk       (pclk),                 // pixel_clk  
    108     .pixel_clk_5x       (pixel_clk_5x),         // pixel_clk的5倍时钟  
    109     .pixel_clk_2_5x     (pixel_clk_2_5x),       // pixel_clk的2.5倍时钟
    110     .paralell_data      (clk_10bit),
    111     .serial_data_out    (tmds_clk_serial)
    112     );
    113     
    114 //转换差分信号  
    115 OBUFDS io_clk_out0 (
    116     .I        (tmds_data_serial[0]),
    117     .O        (tmds_data_p[0]),
    118     .OB       (tmds_data_n[0])
    119     ); 
    120 
    121 OBUFDS io_clk_out1 (
    122     .I        (tmds_data_serial[2]),
    123     .O        (tmds_data_p[1]),
    124     .OB       (tmds_data_n[1])
    125     ); 
    126 
    127 OBUFDS io_clk_out2 (
    128     .I        (tmds_data_serial[1]),
    129     .O        (tmds_data_p[2]),
    130     .OB       (tmds_data_n[2])
    131     ); 
    132 
    133 OBUFDS io_clk_out (
    134     .I        (tmds_clk_serial),
    135     .O        (tmds_clk_p),
    136     .OB       (tmds_clk_n)
    137     );
    138 
    139 endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139

    在dvi_transmitter_top模块中,不仅例化了编码模块和并转串模块,同时还例化了四个OBUFDS原语,用于将三路数据和一路时钟信号转换成差分信号输出,如程序第115至139行所示。
    OBUFDS是差分输出缓冲器,用于将来自FPGA内部逻辑的信号转换成差分信号输出,支持TMDS电平标准。OBUFDS原语示意图如下所示:
    在这里插入图片描述

    图 17.4.7 OBUFDS原语示意图
    在程序的第39至43行,例化了asyn_rst_syn模块。OSERDESE3模块要求复位信号高电平有效,并且需要将异步复位信号同步到串行时钟域。因此,我们在asyn_rst_syn模块中将低电平有效的异步复位信号转换成高有效,同时对其进行异步复位,同步释放处理。
    asyn_rst_syn模块的代码如下所示:

    1  module asyn_rst_syn(
    2      input clk,          //目的时钟域
    3      input reset_n,      //异步复位,低有效
    4      
    5      output syn_reset    //高有效
    6      );
    7      
    8  //reg define
    9  reg reset_1;
    10 reg reset_2;
    11     
    12 //*****************************************************
    13 //**                    main code
    14 //***************************************************** 
    15 assign syn_reset  = reset_2;
    16     
    17 //对异步复位信号进行同步释放,并转换成高有效
    18 always @ (posedge clk or negedge reset_n) begin
    19     if(!reset_n) begin
    20         reset_1 <= 1'b1;
    21         reset_2 <= 1'b1;
    22     end
    23     else begin
    24         reset_1 <= 1'b0;
    25         reset_2 <= reset_1;
    26     end
    27 end
    28     
    29 endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    可以看出,该模块的代码非常简单,相当于在需要同步的时钟域下对输入的异步复位信号连接寄存了两次,这是一种非常常用的对异步信号进行同步的方法。需要注意的是,在程序第18行的always块中,还实现了将低电平有效的复位信号转换成高电平有效的功能。
    到这里RGB2DVI模块的程序设计就介绍完了,整个模块的原理图如下所示:
    在这里插入图片描述

    图 17.4.8 RGB2DVI模块原理图
    在图 17.4.2中,我们用红/绿/蓝三种颜色分别标识出了输入的视频数据video_din中三个不同的颜色通道。从图中也可以看出,每个颜色通道的处理过程都是一样的,都是先经过dvi_encoder进行编码,然后经过serializer_10_to_1模块进行并串转换,最后通过OBUFDS原语转换成TMDS差分信号。
    接下来,我们在整个系统的顶层模块中调用RGB2DVI模块,通过HDMI接口输出彩条图案。
    系统的顶层模块为hdmi_colorbar_top,其代码如下所示:

    1  module  hdmi_colorbar_top(
    2      input        sys_clk_p,
    3      input        sys_clk_n,
    4      input        sys_rst_n,
    5      
    6      output       tmds_clk_p,    // TMDS 时钟通道
    7      output       tmds_clk_n,
    8      output [2:0] tmds_data_p,   // TMDS 数据通道
    9      output [2:0] tmds_data_n
    10 );
    11 
    12 //wire define
    13 wire          pixel_clk;
    14 wire          pixel_clk_5x;
    15 wire          pixel_clk_2_5x;
    16 wire          clk_locked;
    17 
    18 wire  [10:0]  pixel_xpos_w;
    19 wire  [10:0]  pixel_ypos_w;
    20 wire  [23:0]  pixel_data_w;
    21 
    22 wire          video_hs;
    23 wire          video_vs;
    24 wire          video_de;
    25 wire  [23:0]  video_rgb;
    26 
    27 //*****************************************************
    28 //**                    main code
    29 //*****************************************************
    30 
    31 //例化MMCM/PLL IP核
    32 clk_wiz_0 u_clk_wiz_0
    33    (
    34     // Clock out ports
    35     .clk_out1     (pixel_clk),       //pixel_clk  
    36     .clk_out2     (pixel_clk_5x),    //pixel_clk的5倍时钟   
    37     .clk_out3     (pixel_clk_2_5x),  //pixel_clk的2.5倍时钟      
    38     // Status and control signals
    39     .reset        (~sys_rst_n),     
    40     .locked       (clk_locked),     
    41     // Clock in ports
    42     .clk_in1_p    (sys_clk_p),      
    43     .clk_in1_n    (sys_clk_n));     
    44 
    45 //例化视频显示驱动模块
    46 video_driver u_video_driver(
    47     .pixel_clk      (pixel_clk),
    48     .sys_rst_n      (sys_rst_n),
    49 
    50     .video_hs       (video_hs),
    51     .video_vs       (video_vs),
    52     .video_de       (video_de),
    53     .video_rgb      (video_rgb),
    54 
    55     .pixel_xpos     (pixel_xpos_w),
    56     .pixel_ypos     (pixel_ypos_w),
    57     .pixel_data     (pixel_data_w)
    58     );
    59 
    60 //例化视频显示模块
    61 video_display  u_video_display(
    62     .pixel_clk      (pixel_clk),
    63     .sys_rst_n      (sys_rst_n),
    64 
    65     .pixel_xpos     (pixel_xpos_w),
    66     .pixel_ypos     (pixel_ypos_w),
    67     .pixel_data     (pixel_data_w)
    68     );
    69 
    70 //例化HDMI驱动模块
    71 dvi_transmitter_top u_rgb2dvi_0(
    72     .pclk           (pixel_clk),
    73     .pixel_clk_5x   (pixel_clk_5x),
    74     .pixel_clk_2_5x (pixel_clk_2_5x),
    75     .reset_n        (sys_rst_n & clk_locked),
    76                 
    77     .video_din      (video_rgb),
    78     .video_hsync    (video_hs), 
    79     .video_vsync    (video_vs),
    80     .video_de       (video_de),
    81                 
    82     .tmds_clk_p     (tmds_clk_p),
    83     .tmds_clk_n     (tmds_clk_n),
    84     .tmds_data_p    (tmds_data_p),
    85     .tmds_data_n    (tmds_data_n), 
    86     .tmds_oen       ()                        //预留的端口,本次实验未用到
    87     );
    88 
    89 endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    在代码的31至43行,我们通过调用时钟IP核来产生三个时钟,其中pixel_clk为像素时钟,pixel_clk_2_5x是10位转4位数据所需要的时钟,其频率为pixel_clk的2.5倍。pixel_clk_5x为并串转换模块所需要的串行数据时钟,其频率为pixel_clk的5倍。
    在顶层模块中,video_display模块(第67行)负责产生RGB888格式的彩条图案,然后在video_driver模块(第57行)的驱动下按照工业标准的VGA显示时序输出视频信号、行场同步信号以及视频有效信号。这些信号作为RGB2DVI模块(第77行)的输入,最终转换成DVI/HDMI接口标准的TMDS串行数据输出到HDMI接口。
    上述代码中的video_display模块和video_driver模块与LCD彩条显示实现中的lcd_driver和lcd_display模块几乎完全相同,如果大家对这两个模块不熟悉的话,请参考《LCD彩条显示实验》。
    顶层模块hdmi_colorbar_top的原理图如下所示:
    在这里插入图片描述

    图 17.4.9 顶层模块原理图
    在图 17.4.9中,粉色的线条为像素时钟pixel_clk,该时钟信号连接到了其他所有模块。黄色的线条为串行数据时钟pixel_clk_5x,红色线条为10位数据转换为4位数据的时钟pixel_clk_2_5x,这两个时钟信号仅连接到了RGB2DVI模块。
    17.5下载验证
    首先我们将下载器与DFZU2EG/4EV MPSoC开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用HDMI连接线将HDMI显示器连接到DFZU2EG/4EV MPSoC开发板上的HDMI接口。最后连接电源线后拨动开关按键给开发板上电。如下图所示:
    在这里插入图片描述

    图 17.5.1 DFZU2EG/4EV MPSoC开发板连接示意图
    然后我们将本次实验生成的BIT文件下载下开发板中,下载完成之后HDMI显示器上显示彩条图案,说明本次实验在DFZU2EG/4EV MPSoC开发板上面下载验证成功。
    实验结果如下图所示:
    在这里插入图片描述

    图 17.5.2 实验结果

  • 相关阅读:
    【JavaWeb】案例 1:记录网站的登录成功人数
    Python:找素数
    如何快速使用Vue3在electron项目开发chrome Devtools插件
    计算机毕业设计(附源码)python筑柏机器人培训中心管理系统
    手机便签功能在哪里?如何在便签里添加文字图片视频?
    适用于多种场景功能强大的在线海报图片素材设计器源码
    第十五天-爬虫项目实战
    算法:记忆化搜索
    Java 日志技术
    我试图给你分享一种自适应的负载均衡。
  • 原文地址:https://blog.csdn.net/weixin_55796564/article/details/128081334