OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 6570|回复: 0
打印 上一主题 下一主题

[XILINX] 【正点原子FPGA连载】第十八章 HDMI彩条显示实验--摘自【正点原子】领航者ZYNQ之FPGA开发指南_V1.2

[复制链接]

1318

主题

1334

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5635
金钱
5635
注册时间
2019-5-8
在线时间
1507 小时
跳转到指定楼层
楼主
发表于 2020-7-2 10:40:38 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 正点原子01 于 2020-7-3 11:39 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-301505-1-1.html
4)本章实例源码下载: HDMI彩条显示实验.zip (1.21 MB, 下载次数: 119)
5)对正点原子FPGA感兴趣的同学可以加群讨论:712557122
6)关注正点原子公众号,获取最新资料更新




第十八章HDMI彩条显示实验

    HDMI接口在消费类电子行业,如电脑、液晶电视、投影仪等产品中得到了广范的应用。一些专业的视频设备如摄像机、视频切换器等也都集成了HDMI接口。本章我们将学习如何驱动ZYNQ开发板上的HDMI接口。
本章包括以下几个部分:
1.1    简介
1.2   实验任务
1.3   硬件设计
1.4   程序设计
1.5   下载验证
1.1 简介
    HDMI是新一代的多媒体接口标准,英文全称是High-Definition Multimedia Interface,即高清多媒体接口。它能够同时传输视频和音频,简化了设备的接口和连线;同时提供了更高的数据传输带宽,可以传输无压缩的数字音频及高分辨率视频信号。HDMI 1.0版本于2002年发布,最高数据传输速度为5Gbps;而2017年发布的HDMI 2.1标准的理论带宽可达48Gbps。
HDMI向下兼容DVI,但是DVI(数字视频接口)只能用来传输视频,而不能同时传输音频,这是两者最主要的差别。此外,DVI接口的尺寸明显大于HDMI接口,如下图所示:
图18.1.1 DVI接口(左)和HDMI接口(右)实物图
图18.1.1右侧是生活中最常见的A型HDMI接口,其引脚定义如下图所示:
图18.1.2HDMI接口引脚定义
    DVI和HDMI接口协议在物理层使用TMDS标准传输音视频数据。TMDS(Transition Minimized Differential Signaling,最小化传输差分信号)是美国Silicon Image公司开发的一项高速数据传输技术,在DVI和HDMI视频接口中使用差分信号传输高速串行数据。TMDS差分传输技术使用两个引脚(如图18.1.2中的“数据2+”和“数据2-”)来传输一路信号,利用这两个引脚间的电压差的正负极性和大小来决定传输数据的数值(0或1)。
    Xilnx在Spartan-3A系列之后的器件中,加入了对TMDS接口标准的支持,用于在FPGA内部实现DVI和HDMI接口。
    由于本次实验只是使用HDMI接口来显示图像,不需要传输音频,因此我们只需要实现DVI接口的驱动逻辑即可。不过在此之前我们还需要简单地了解一下TMDS视频传输协议。
    图18.1.3是TMDS发送端和接收端的连接示意图。DVI或HDMI视频传输所使用的TMDS连接通过四个串行通道实现。对于DVI来说,其中三个通道分别用于传输视频中每个像素点的红、绿、蓝三个颜色分量(RGB 4:4:4格式)。HDMI默认也是使用三个RGB通道,但是它同样可以选择传输像素点的亮度和色度信息(YCrCb 4:4:4或YCrCb 4:2:2格式)。第四个通道是时钟通道,用于传输像素时钟。独立的TMDS时钟通道为接收端提供接收的参考频率,保证数据在接收端能够正确恢复。
图18.1.3TMDS连接示意图
    如果每个像素点的颜色深度为24位,即RGB每个颜色分量各占8位,那么每个通道上的颜色数据将通过一个8B/10B的编码器(Encoder)来转换成一个10位的像素字符。然后这个10位的字符通过并串转换器(Serializer)转换成串行数据,最后由TMDS数据通道发送出去。这个10:1的并转串过程所生成的串行数据速率是实际像素时钟速率的10倍。
    在传输视频图像的过程中,数据通道上传输的是编码后的有效像素字符。而在每一帧图像的行与行之间,以及视频中不同帧之间的时间间隔(消隐期)内,数据通道上传输的则是控制字符。每个通道上有两位控制信号的输入接口,共对应四种不同的控制字符。这些控制字符提供了视频的行同步(HZYNC)以及帧同步(VSYNC)信息,也可以用来指定所传输数据的边界(用于同步)。
    对于DVI传输,整个视频的消隐期都用来传输控制字符。而HDMI传输的消隐期除了控制字符之外,还可以用于传输音频或者其他附加数据,比如字幕信息等。这就是DVI和HDMI协议之间最主要的差别。从图18.1.3中也可以看出这一差别,即“AuxiliaryData”接口标有“HDMI Olny”,即它是HDMI所独有的接口。
    从前面的介绍中我们可以看出,TMDS连接从逻辑功能上可以划分成两个阶段:编码和并串转换。在编码阶段,编码器将视频源中的像素数据、HDMI的音频/附加数据,以及行同步和场同步信号分别编码成10位的字符流。然后在并串转换阶段将上述的字符流转换成串行数据流,并将其从三个差分输出通道发送出去。
DVI编码器在视频有效数据段输出像素数据,在消隐期输出控制数据,如图18.1.4所示。其中VDE(Video Data Enable)为高电平时表示视频数据有效,为低电平代表当前处于视频消隐期。
图18.1.4DVI编码输出示意图
    图18.1.5给出了三个通道的DVI编码器示意图。对于像素数据的RGB三个颜色通道,编码器的逻辑是完全相同的。VDE用于各个通道选择输出视频像素数据还是控制数据。HSYNC和VSYNC信号在蓝色通道进行编码得到10位字符,然后在视频消隐期传输。绿色和红色通道的控制信号C0和C1同样需要进行编码,并在消隐期输出。但是DVI规范中这两个通道的控制信号是预留的(未用到),因此将其置为2’b00。
图18.1.5DVI编码器示意图
    每个通道输入的视频像素数据都要使用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/AudioData Enable)信号,其作用和VDE是类似的:当ADE为高电平时,表明输入端的附加数据或者音频数据有效。如果大家想了解更多有关HDMI的细节,可以参考开发板资料(A盘)/软件资料中的HDMI接口规范——《High-DefinitionMultimedia Interface Specification Version 1.3a》。为了简单起见,我们在这里把HDMI接口当作DVI接口进行驱动。
    在编码之后,3个通道的10-bit字符将进行并串转换,这一过程是使用7系列FPGA中专用的硬件资源来实现的。ZYNQ PL部分与7系列的FPGA是等价的,它提供了专用的并串转换器——OSERDESE2。单一的OSERDESE2模块可以实现8:1的并串转换,通过位宽扩展可以实现10:1和14:1的转换率。
1.2 实验任务
    本章的实验任务是驱动领航者ZYNQ开发板上的HDMI接口,在显示器上显示彩条图案。
1.3 硬件设计
图18.3.1 HDMI接口原理图1
    图18.1.1是领航者ZYNQ底板HDMI接口原理图的一部分,其中HDMI的三个数据通道HDMI_D[2:0]至和一个时钟通道HDMI_CLK直接与ZYNQ PL端的TMDS差分引脚相连。
    HDMI_CEC指的是用户电气控制(ConsumerElectronics Control),它用于HDMI连接线上的设备之间进行信息交换。当一个设备的状态发生变化时,CEC可以使用远程控制或自动改变设置来命令连接的关联设备的状态发生相应的变化。例如,如果用户放置一张碟片到蓝光播放器并开始播放,那么高清电视机将会自动打开电源,设置正确的视频输入格式和打开环绕声设备等等,这种关联通信提供了一个更好的客户体验。
    HDMI_HPD指的是热拔插检测(HotPlug Detect),当视频设备与接收设备通过HDMI连接时,接收设备将HPD置为高电平,通知发送设备。当发送设备检测到HPD为低电平时,表明断开连接。
图18.3.2 HDMI接口原理图2
    在图18.3.2中,HDMI_OUT_EN信号用于设置HDMI接口的输入输出模式,当其为高电平时作为输出端,此时由ZYNQ底板输出HDMI接口的5V电源。同时,图18.3.1中HDMI_HPD将作为输入信号使用;反之,当HDMI_OUT_EN为低电平时,开发板上的HDMI接口作为输入端,接口上的5V电源将由外部连接设备提供。同时,HDMI_HPD将输出高电平,用于指示HDMI连接状态。
图18.3.3 HDMI接口原理图3
    在图18.3.3中,HDMI_SCL_LS和HDMI_SDA_LS是HDMI接口的显示数据通道(DDC,DisplayDataChannel),用于HDMI 发送端和接收端之间交换一些配置信息,通过I2C协议通信。发送端通过 DDC 通道,读取接收端保存在 EEPROM中的EDID数据,获取接收端的信息,确认接收端终端显示的设置和功能,决定跟接收端之间以什么格式传输音/视频数据。
    本次实验只使用了HDMI接口的TMDS数据、TMDS时钟以及HDMI输出使能等信号,各端口的管脚分配如下表所示:
表18.3.1HDMI彩条显示实验管脚分配

    需要注意的是,TMDS数据和时钟信号需要在约束文件中指定电平标准为TMDS_33。另外,对于差分信号我们只需要指定正极的引脚位置,工具会自动对负极进行管脚分配。
    相关的管脚约束如下所示:
  1. set_property -dict{PACKAGE_PIN L16  IOSTANDARD TMDS_33 }[get_ports {tmds_data_p[2]}]
  2. set_property -dict{PACKAGE_PIN M14  IOSTANDARD TMDS_33 }[get_ports {tmds_data_p[1]}]
  3. set_property -dict{PACKAGE_PIN K19  IOSTANDARD TMDS_33 }[get_ports {tmds_data_p[0]}]
  4. set_property -dict{PACKAGE_PIN L14  IOSTANDARD TMDS_33 }[get_ports tmds_clk_p]
  5. set_property -dict{PACKAGE_PIN G17  IOSTANDARD LVCMOS33}[get_ports tmds_oen]
  6. set_property -dict{PACKAGE_PIN U18  IOSTANDARD LVCMOS33}[get_ports sys_clk]
  7. set_property -dict{PACKAGE_PIN J15  IOSTANDARD LVCMOS33}[get_ports sys_rst_n]
复制代码
1.4 程序设计
    由于本次实验只需要通过HDMI接口显示图像,因此将其当成DVI接口进行驱动。另外我们只需要实现图像的发送功能。由此得出本次实验的系统框图如下所示:
图18.4.1系统框图
    本次实验在LCD彩条显示实验的基础上添加一个RGB2DVI模块,将RGB888格式的视频图像转换成TMDS数据输出。本章的重点是介绍RGB2DVI模块,其余模块的介绍请参考LCD彩条显示实验。
RGB2DVI顶层模块的设计框图如下所示:
图18.4.2RGB2DVI模块框图
    图18.4.2中,Encoder模块负责对数据进行编码,Serializer模块对编码后的数据进行并串转换,最后通过OBUFDS转化成TMDS差分信号传输。
    整个系统需要两个输入时钟,一个是视频的像素时钟Pixel Clk,另外一个时钟Pixel Clk x5的频率是像素时钟的五倍。由前面的简介部分我们知道,并串转换过程的实现的是10:1的转换率,理论上转换器需要一个10倍像素时钟频率的串行时钟。这里我们只用了一个5倍的时钟频率,这是因为OSERDESE2模块可以实现DDR的功能,即它在五倍时钟频率的基础上又实现了双倍数据速率。
    TMDS连接的时钟通道我们采用与数据通道相同的并转串逻辑来实现。通过对10位二进制序列10’b11111_00000在10倍像素时钟频率下进行并串转换,就可以得到像素时钟频率下的TMDS参考时钟。
    另外需要注意的是,图中左下脚HDMI的音频/附加数据输入在本次实验中并未用到,因此以虚线表示。
     首先来看一下Encoder模块的代码:
  1. `timescale1 ps /1ps

  2. module dvi_encoder (
  3. input            clkin,// pixel clock input
  4. input            rstin,// async. reset input (active high)
  5. input[7:0] din,//data inputs: expect registered
  6. input            c0,// c0 input
  7. input            c1,// c1 input
  8. input            de,// de input
  9. outputreg[9:0] dout      // data outputs
  10. );

  11. ////////////////////////////////////////////////////////////
  12. // Counting number of 1s and 0s for eachincoming pixel
  13. // component. Pipe line the result.
  14. // Register Data Input so it matches the pipelined adder
  15. // output
  16. ////////////////////////////////////////////////////////////
  17. reg[3:0] n1d;//numberof 1s in din
  18. reg[7:0] din_q;

  19. //计算像素数据中“1”的个数
  20. always@(posedge clkin)begin
  21.       n1d <=#1 din[0]+din[1]+din[2]+din[3]+din[4]+din[5]+din[6]+din[7];

  22.       din_q <=#1 din;
  23. end

  24. ///////////////////////////////////////////////////////
  25. // Stage 1: 8 bit -> 9 bit
  26. // Refer to DVI 1.0 Specification, page 29,Figure 3-5
  27. ///////////////////////////////////////////////////////
  28. wire decision1;

  29. assign decision1 =(n1d >4'h4)|((n1d==4'h4)&(din_q[0]==1'b0));

  30. wire[8:0] q_m;
  31. assign q_m[0]= din_q[0];
  32. assign q_m[1]=(decision1)?(q_m[0]^~ din_q[1]):(q_m[0]^ din_q[1]);
  33. assign q_m[2]=(decision1)?(q_m[1]^~ din_q[2]):(q_m[1]^ din_q[2]);
  34. assign q_m[3]=(decision1)?(q_m[2]^~ din_q[3]):(q_m[2]^ din_q[3]);
  35. assign q_m[4]=(decision1)?(q_m[3]^~ din_q[4]):(q_m[3]^ din_q[4]);
  36. assign q_m[5]=(decision1)?(q_m[4]^~ din_q[5]):(q_m[4]^ din_q[5]);
  37. assign q_m[6]=(decision1)?(q_m[5]^~ din_q[6]):(q_m[5]^ din_q[6]);
  38. assign q_m[7]=(decision1)?(q_m[6]^~ din_q[7]):(q_m[6]^ din_q[7]);
  39. assign q_m[8]=(decision1)?1'b0:1'b1;

  40. /////////////////////////////////////////////////////////
  41. // Stage 2: 9 bit -> 10 bit
  42. // Refer to DVI 1.0 Specification, page 29,Figure 3-5
  43. /////////////////////////////////////////////////////////
  44. reg[3:0] n1q_m, n0q_m;// number of 1s and 0sfor q_m
  45. always@(posedge clkin)begin
  46.       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];
  47. n0q_m  <=#14'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]);
  48. end

  49. parameter CTRLTOKEN0 =10'b1101010100;
  50. parameter CTRLTOKEN1 =10'b0010101011;
  51. parameter CTRLTOKEN2 =10'b0101010100;
  52. parameter CTRLTOKEN3 =10'b1010101011;

  53. reg[4:0] cnt;//disparitycounter, MSB is the sign bit
  54. wire decision2, decision3;

  55. assign decision2 =(cnt ==5'h0)|(n1q_m== n0q_m);
  56. /////////////////////////////////////////////////////////////////////////
  57. // [(cnt > 0) and (N1q_m > N0q_m)] or[(cnt < 0) and (N0q_m > N1q_m)]
  58. /////////////////////////////////////////////////////////////////////////
  59. assign decision3 =(~cnt[4]&(n1q_m> n0q_m))|(cnt[4]&(n0q_m >n1q_m));

  60. ////////////////////////////////////
  61. // pipe line alignment
  62. ////////////////////////////////////
  63. reg       de_q,de_reg;
  64. reg       c0_q,c1_q;
  65. reg       c0_reg,c1_reg;
  66. reg[8:0] q_m_reg;

  67. always@(posedge clkin)begin
  68.       de_q    <=#1 de;
  69.       de_reg  <=#1 de_q;

  70.       c0_q    <=#1 c0;
  71.       c0_reg  <=#1 c0_q;
  72.       c1_q    <=#1 c1;
  73.       c1_reg  <=#1 c1_q;

  74.       q_m_reg <=#1 q_m;
  75. end

  76. ///////////////////////////////
  77. // 10-bit out
  78. // disparity counter
  79. ///////////////////////////////
  80. always@(posedge clkin orposedge rstin)begin
  81. if(rstin)begin
  82.         dout <=10'h0;
  83.         cnt <=5'h0;
  84. endelsebegin
  85. if(de_reg)begin
  86. if(decision2)begin
  87.            dout[9]<=#1~q_m_reg[8];
  88.            dout[8]<=#1q_m_reg[8];
  89.            dout[7:0]<=#1(q_m_reg[8])?q_m_reg[7:0]:~q_m_reg[7:0];

  90.            cnt <=#1(~q_m_reg[8])?(cnt + n0q_m -n1q_m):(cnt +n1q_m - n0q_m);
  91. endelsebegin
  92. if(decision3)begin
  93.              dout[9]<=#11'b1;
  94.              dout[8]<=#1q_m_reg[8];
  95.              dout[7:0]<=#1~q_m_reg[7:0];

  96.              cnt <=#1 cnt +{q_m_reg[8],1'b0}+(n0q_m- n1q_m);
  97. endelsebegin
  98.              dout[9]<=#11'b0;
  99.              dout[8]<=#1q_m_reg[8];
  100.              dout[7:0]<=#1q_m_reg[7:0];

  101.              cnt <=#1 cnt -{~q_m_reg[8],1'b0}+(n1q_m- n0q_m);
  102. end
  103. end
  104. endelsebegin
  105. case({c1_reg, c0_reg})
  106. 2'b00:   dout <=#1 CTRLTOKEN0;
  107. 2'b01:   dout <=#1 CTRLTOKEN1;
  108. 2'b10:   dout <=#1 CTRLTOKEN2;
  109. default: dout <=#1 CTRLTOKEN3;
  110. endcase

  111.          cnt <=#15'h0;
  112. end
  113. end
  114. end

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

  2. module serializer_10_to_1(
  3. input           reset,//复位,高有效
  4. input          paralell_clk,// 输入并行数据时钟
  5. input          serial_clk_5x,// 输入串行数据时钟
  6. input[9:0]  paralell_data,// 输入并行数据

  7. output         serial_data_out     // 输出串行数据
  8. );

  9. //wire define
  10. wire        cascade1;//用于两个OSERDESE2级联的信号
  11. wire        cascade2;

  12. //*****************************************************
  13. //**                    main code
  14. //*****************************************************

  15. //例化OSERDESE2原语,实现并串转换,Master模式
  16. OSERDESE2 #(
  17. .DATA_RATE_OQ   ("DDR"),//设置双倍数据速率
  18. .DATA_RATE_TQ   ("SDR"),//DDR, BUF, SDR
  19. .DATA_WIDTH     (10),//输入的并行数据宽度为10bit
  20. .SERDES_MODE    ("MASTER"),//设置为Master,用于10bit宽度扩展
  21. .TBYTE_CTL      ("FALSE"),//Enable tristate byte operation (FALSE, TRUE)
  22. .TBYTE_SRC      ("FALSE"),//Tristate byte source (FALSE, TRUE)
  23. .TRISTATE_WIDTH(1)// 3-state converter width (1,4)
  24. )
  25. OSERDESE2_Master (
  26. .CLK        (serial_clk_5x),//串行数据时钟,5倍时钟频率
  27. .CLKDIV     (paralell_clk),//并行数据时钟
  28. .RST        (reset),//1-bit input: Reset
  29. .OCE        (1'b1),//1-bit input: Output data clock enable

  30. .OQ         (serial_data_out),//串行输出数据

  31. .D1         (paralell_data[0]),//D1 - D8: 并行数据输入
  32. .D2         (paralell_data[1]),
  33. .D3         (paralell_data[2]),
  34. .D4         (paralell_data[3]),
  35. .D5         (paralell_data[4]),
  36. .D6         (paralell_data[5]),
  37. .D7         (paralell_data[6]),
  38. .D8         (paralell_data[7]),

  39. .SHIFTIN1   (cascade1),//SHIFTIN1 用于位宽扩展
  40. .SHIFTIN2   (cascade2),//SHIFTIN2
  41. .SHIFTOUT1  (),// SHIFTOUT1: 用于位宽扩展
  42. .SHIFTOUT2  (),// SHIFTOUT2

  43. .OFB        (),// 以下是未使用信号
  44. .T1         (1'b0),
  45. .T2         (1'b0),
  46. .T3         (1'b0),
  47. .T4         (1'b0),
  48. .TBYTEIN    (1'b0),
  49. .TCE        (1'b0),
  50. .TBYTEOUT   (),
  51. .TFB        (),
  52. .TQ         ()
  53. );

  54. //例化OSERDESE2原语,实现并串转换,Slave模式
  55. OSERDESE2 #(
  56. .DATA_RATE_OQ   ("DDR"),//设置双倍数据速率
  57. .DATA_RATE_TQ   ("SDR"),//DDR, BUF, SDR
  58. .DATA_WIDTH     (10),//输入的并行数据宽度为10bit
  59. .SERDES_MODE    ("SLAVE"),//设置为Slave,用于10bit宽度扩展
  60. .TBYTE_CTL      ("FALSE"),//Enable tristate byte operation (FALSE, TRUE)
  61. .TBYTE_SRC      ("FALSE"),//Tristate byte source (FALSE, TRUE)
  62. .TRISTATE_WIDTH(1)// 3-state converter width (1,4)
  63. )
  64. OSERDESE2_Slave (
  65. .CLK        (serial_clk_5x),//串行数据时钟,5倍时钟频率
  66. .CLKDIV     (paralell_clk),//并行数据时钟
  67. .RST        (reset),//1-bit input: Reset
  68. .OCE        (1'b1),//1-bit input: Output data clock enable

  69. .OQ         (),// 串行输出数据

  70. .D1         (1'b0),//D1 - D8: 并行数据输入
  71. .D2         (1'b0),
  72. .D3         (paralell_data[8]),
  73. .D4         (paralell_data[9]),
  74. .D5         (1'b0),
  75. .D6         (1'b0),
  76. .D7         (1'b0),
  77. .D8         (1'b0),

  78. .SHIFTIN1   (),// SHIFTIN1 用于位宽扩展
  79. .SHIFTIN2   (),// SHIFTIN2
  80. .SHIFTOUT1  (cascade1),//SHIFTOUT1: 用于位宽扩展
  81. .SHIFTOUT2  (cascade2),//SHIFTOUT2

  82. .OFB        (),// 以下是未使用信号
  83. .T1         (1'b0),
  84. .T2         (1'b0),
  85. .T3         (1'b0),
  86. .T4         (1'b0),
  87. .TBYTEIN    (1'b0),
  88. .TCE        (1'b0),
  89. .TBYTEOUT   (),
  90. .TFB        (),
  91. .TQ         ()
  92. );

  93. endmodule
复制代码
    serdes_10_to_1模块通过调用OSERDESE2原语来实现10:1的并串转换。原语是Xilinx器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于IP核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。
    需要注意的是,一个OSERDESE2只能实现最多8:1的转换率,在这里我们通过位宽扩展实现了10:1的并串转换,如下图所示:
file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image026.gif
图18.4.5 OSERDESE2位宽扩展
    如图18.4.5所示,OSERDESE2位宽扩展通过两个OSERDESE2模块来实现,其中一个作为Master,另一个作为Slave,通过这种方式最多可实现14:1的并串转换。需要注意的是,在位宽扩展时,Slave模块的数据输入端只能使用D3至D8。
    最后,我们在RGB2DVI顶层模块中调用上述两个模块,其代码如下所示:
  1. moduledvi_transmitter_top(
  2. input        pclk,// pixel clock
  3. input        pclk_x5,// pixel clock x5
  4. input        reset_n,// reset

  5. input[23:0] video_din,// RGB888 video in
  6. input        video_hsync,// hsync data
  7. input        video_vsync,// vsync data
  8. input        video_de,// data enable

  9. output       tmds_clk_p,// TMDS 时钟通道
  10. output       tmds_clk_n,
  11. output[2:0] tmds_data_p,// TMDS 数据通道
  12. output[2:0] tmds_data_n,
  13. output       tmds_oen       // TMDS 输出使能
  14. );

  15. //wire define   
  16. wire        reset;

  17. //并行数据
  18. wire[9:0]  red_10bit;
  19. wire[9:0]  green_10bit;
  20. wire[9:0]  blue_10bit;
  21. wire[9:0]  clk_10bit;

  22. //串行数据
  23. wire[2:0]  tmds_data_serial;
  24. wire        tmds_clk_serial;

  25. //*****************************************************
  26. //**                    main code
  27. //*****************************************************
  28. assign tmds_oen =1'b1;
  29. assign clk_10bit =10'b1111100000;

  30. //异步复位,同步释放
  31.   asyn_rst_syn reset_syn(
  32. .reset_n    (reset_n),
  33. .clk        (pclk),

  34. .syn_reset  (reset)//高有效
  35. );

  36. //对三个颜色通道进行编码
  37.   dvi_encoder encoder_b (
  38. .clkin      (pclk),
  39. .rstin      (reset),

  40. .din        (video_din[7:0]),
  41. .c0         (video_hsync),
  42. .c1         (video_vsync),
  43. .de         (video_de),
  44. .dout       (blue_10bit)
  45. );

  46.   dvi_encoder encoder_g (
  47. .clkin      (pclk),
  48. .rstin      (reset),

  49. .din        (video_din[15:8]),
  50. .c0         (1'b0),
  51. .c1         (1'b0),
  52. .de         (video_de),
  53. .dout       (green_10bit)
  54. );

  55.   dvi_encoder encoder_r (
  56. .clkin      (pclk),
  57. .rstin      (reset),

  58. .din        (video_din[23:16]),
  59. .c0         (1'b0),
  60. .c1         (1'b0),
  61. .de         (video_de),
  62. .dout       (red_10bit)
  63. );

  64. //对编码后的数据进行并串转换
  65.   serializer_10_to_1serializer_b(
  66. .reset              (reset),// 复位,高有效
  67. .paralell_clk       (pclk),// 输入并行数据时钟
  68. .serial_clk_5x      (pclk_x5),// 输入串行数据时钟
  69. .paralell_data      (blue_10bit),// 输入并行数据

  70. .serial_data_out    (tmds_data_serial[0])// 输出串行数据
  71. );

  72.   serializer_10_to_1serializer_g(
  73. .reset              (reset),
  74. .paralell_clk       (pclk),
  75. .serial_clk_5x      (pclk_x5),
  76. .paralell_data      (green_10bit),

  77. .serial_data_out    (tmds_data_serial[1])
  78. );

  79.   serializer_10_to_1serializer_r(
  80. .reset              (reset),
  81. .paralell_clk       (pclk),
  82. .serial_clk_5x      (pclk_x5),
  83. .paralell_data      (red_10bit),

  84. .serial_data_out    (tmds_data_serial[2])
  85. );

  86. serializer_10_to_1 serializer_clk(
  87. .reset              (reset),
  88. .paralell_clk       (pclk),
  89. .serial_clk_5x      (pclk_x5),
  90. .paralell_data      (clk_10bit),

  91. .serial_data_out    (tmds_clk_serial)
  92. );

  93. //转换差分信号  
  94. OBUFDS #(
  95. .IOSTANDARD         ("TMDS_33")// I/O电平标准为TMDS
  96. ) TMDS0 (
  97. .I                  (tmds_data_serial[0]),
  98. .O                  (tmds_data_p[0]),
  99. .OB                 (tmds_data_n[0])
  100. );

  101. OBUFDS #(
  102. .IOSTANDARD         ("TMDS_33")// I/O电平标准为TMDS
  103. ) TMDS1 (
  104. .I                  (tmds_data_serial[1]),
  105. .O                  (tmds_data_p[1]),
  106. .OB                 (tmds_data_n[1])
  107. );

  108. OBUFDS #(
  109. .IOSTANDARD         ("TMDS_33")// I/O电平标准为TMDS
  110. ) TMDS2 (
  111. .I                  (tmds_data_serial[2]),
  112. .O                  (tmds_data_p[2]),
  113. .OB                 (tmds_data_n[2])
  114. );

  115. OBUFDS #(
  116. .IOSTANDARD         ("TMDS_33")// I/O电平标准为TMDS
  117. ) TMDS3 (
  118. .I                  (tmds_clk_serial),
  119. .O                  (tmds_clk_p),
  120. .OB                 (tmds_clk_n)
  121. );

  122. endmodule
复制代码
    在dvi_transmitter_top模块中,不仅例化了编码模块和并转串模块,同时还例化了四个OBUFDS原语,用于将三路数据和一路时钟信号转换成差分信号输出,如程序第116至147行所示。
OBUFDS是差分输出缓冲器,用于将来自FPGA内部逻辑的信号转换成差分信号输出,支持TMDS电平标准。OBUFDS原语示意图如下所示:
图18.4.6OBUFDS原语示意图
    在程序的第37至43行,例化了asyn_rst_syn模块。OSERDESE2模块要求复位信号高电平有效,并且需要将异步复位信号同步到串行时钟域。因此,我们在asyn_rst_syn模块中将低电平有效的异步复位信号转换成高有效,同时对其进行异步复位,同步释放处理。
    asyn_rst_syn模块的代码如下所示:
  1. module asyn_rst_syn(
  2. input clk,//目的时钟域
  3. input reset_n,//异步复位,低有效

  4. output syn_reset    //高有效
  5. );

  6. //reg define
  7. reg reset_1;
  8. reg reset_2;

  9. //*****************************************************
  10. //**                    main code
  11. //*****************************************************
  12. assign syn_reset  =reset_2;

  13. //对异步复位信号进行同步释放,并转换成高有效
  14. always@(posedge clk ornegedge reset_n)begin
  15. if(!reset_n)begin
  16.          reset_1 <=1'b1;
  17.          reset_2 <=1'b1;
  18. end
  19. elsebegin
  20.          reset_1 <=1'b0;
  21.          reset_2 <= reset_1;
  22. end
  23. end

  24. endmodule
复制代码
    可以看出,该模块的代码非常简单,相当于在需要同步的时钟域下对输入的异步复位信号连接寄存了两次,这是一种非常常用的对异步信号进行同步的方法。需要注意的是,在程序第18行的always块中,还实现了将低电平有效的复位信号转换成高电平有效的功能。
    到这里RGB2DVI模块的程序设计就介绍完了,整个模块的原理图如下所示:
图18.4.7RGB2DVI模块原理图
    在图18.4.2中,我们用红/绿/蓝三种颜色分别标识出了输入的视频数据video_din中三个不同的颜色通道。从图中也可以看出,每个颜色通道的处理过程都是一样的,都是先经过dvi_encoder进行编码,然后经过serializer_10_to_1模块进行并串转换,最后通过OBUFDS原语转换成TMDS差分信号。
    接下来,我们在整个系统的顶层模块中调用RGB2DVI模块,通过HDMI接口输出彩条图案。
    系统的顶层模块为hdmi_colorbar_top,其代码如下所示:
  1. modulehdmi_colorbar_top(
  2. input        sys_clk,
  3. input        sys_rst_n,

  4. output       tmds_clk_p,// TMDS 时钟通道
  5. output       tmds_clk_n,
  6. output[2:0] tmds_data_p,// TMDS 数据通道
  7. output[2:0] tmds_data_n,
  8. output       tmds_oen       // TMDS 输出使能
  9. );

  10. //wire define
  11. wire          pixel_clk;
  12. wire          pixel_clk_5x;
  13. wire          clk_locked;

  14. wire[10:0]  pixel_xpos_w;
  15. wire[10:0]  pixel_ypos_w;
  16. wire[23:0]  pixel_data_w;

  17. wire          video_hs;
  18. wire          video_vs;
  19. wire          video_de;
  20. wire[23:0]  video_rgb;

  21. //*****************************************************
  22. //**                    main code
  23. //*****************************************************

  24. //例化MMCM/PLL IP核
  25. clk_wiz_0  clk_wiz_0(
  26. .clk_in1        (sys_clk),
  27. .clk_out1       (pixel_clk),//像素时钟
  28. .clk_out2       (pixel_clk_5x),//5倍像素时钟

  29. .reset          (~sys_rst_n),
  30. .locked         (clk_locked)
  31. );

  32. //例化视频显示驱动模块
  33. video_driver u_video_driver(
  34. .pixel_clk      (pixel_clk),
  35. .sys_rst_n      (sys_rst_n),

  36. .video_hs       (video_hs),
  37. .video_vs       (video_vs),
  38. .video_de       (video_de),
  39. .video_rgb      (video_rgb),

  40. .pixel_xpos     (pixel_xpos_w),
  41. .pixel_ypos     (pixel_ypos_w),
  42. .pixel_data     (pixel_data_w)
  43. );

  44. //例化视频显示模块
  45. video_display u_video_display(
  46. .pixel_clk      (pixel_clk),
  47. .sys_rst_n      (sys_rst_n),

  48. .pixel_xpos     (pixel_xpos_w),
  49. .pixel_ypos     (pixel_ypos_w),
  50. .pixel_data     (pixel_data_w)
  51. );

  52. //例化HDMI驱动模块
  53. dvi_transmitter_top u_rgb2dvi_0(
  54. .pclk           (pixel_clk),
  55. .pclk_x5        (pixel_clk_5x),
  56. .reset_n        (sys_rst_n& clk_locked),

  57. .video_din      (video_rgb),
  58. .video_hsync    (video_hs),
  59. .video_vsync    (video_vs),
  60. .video_de       (video_de),

  61. .tmds_clk_p     (tmds_clk_p),
  62. .tmds_clk_n     (tmds_clk_n),
  63. .tmds_data_p    (tmds_data_p),
  64. .tmds_data_n    (tmds_data_n),
  65. .tmds_oen       (tmds_oen)
  66. );

  67. endmodule
复制代码
    在代码的30至38行,我们通过调用时钟IP核来产生两个时钟,其中pixel_clk为像素时钟,而pixel_clk_5x为并串转换模块所需要的串行数据时钟,其频率为pixel_clk的5倍。
    在顶层模块中,video_display模块(第56行)负责产生RGB888格式的彩条图案,然后在video_driver模块(第41行)的驱动下按照工业标准的VGA显示时序输出视频信号、行场同步信号以及视频有效信号。这些信号作为RGB2DVI模块(第66行)的输入,最终转换成DVI/HDMI接口标准的TMDS串行数据输出到HDMI接口。
    上述代码中的video_display模块和video_driver模块与LCD彩条显示实现中的lcd_driver和lcd_display模块几乎完全相同,如果大家对这两个模块不熟悉的话,请参考《LCD彩条显示实验》。
顶层模块hdmi_colorbar_top的原理图如下所示:
图18.4.8顶层模块原理图
    在图18.4.8中,橙色的线条为像素时钟pixel_clk,该时钟信号连接到了其他所有模块。而蓝色的线条为串行数据时钟pixel_clk_5x,该时钟信号仅连接到了RGB2DVI模块。
1.5 下载验证
    首先我们将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用HDMI连接线将HDMI显示器连接到领航者底板上的HDMI接口。最后连接开发板的电源,并打开电源开关,如下图所示:
图18.5.1领航者开发板连接示意图
    然后我们将本次实验生成的BIT文件下载下开发板中,下载完成之后HDMI显示器上显示彩条图案,说明本次实验在领航者ZYNQ开发板上面下载验证成功。
    实验结果如下图所示:
图18.5.2实验结果


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-4-28 04:13

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表