搜索
bottom↓
回复: 1

【正点原子FPGA连载】第二十三章HDMI彩条显示实验

[复制链接]

出0入234汤圆

发表于 2021-1-30 11:33:56 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2021-1-30 11:33 编辑

1)实验平台:正点原子超越者FPGA开发板
2)  章节摘自【正点原子】超越者之FPGA开发指南
3)购买链接:https://item.taobao.com/item.htm?&id=631660290421
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-chaoyuezhe.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流群:905624739
QQ群头像.png

100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

第二十三章HDMI彩条显示实验


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

23.1简介
HDMI是新一代的多媒体接口标准,英文全称是High-Definition Multimedia Interface,即高清多媒体接口。它能够同时传输视频和音频,简化了设备的接口和连线;同时提供了更高的数据传输带宽,可以传输无压缩的数字音频及高分辨率视频信号。HDMI 1.0版本于2002年发布,最高数据传输速度为5Gbps;而2017年发布的HDMI 2.1标准的理论带宽可达48Gbps。
HDMI向下兼容DVI,但是DVI(数字视频接口)只能用来传输视频,而不能同时传输音频,这是两者最主要的差别。此外,DVI接口的尺寸明显大于HDMI接口,如下图所示:
23483.png

图 23.1.1 DVI接口(左)和HDMI接口(右)实物图

图 23.1.1中的右侧接口是生活中最常见的A型HDMI接口,其引脚定义如下图所示:
23626.png

图 23.1.2 HDMI接口引脚定义

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

图 23.1.3 TMDS连接示意图

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

图 23.1.4 DVI编码输出示意图

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

图 23.1.5 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字符将进行并串转换,这一过程是使用Spartan6系列FPGA中专用的硬件资源来实现的。Spartan6系列的FPGA提供了专用的并串转换器——OSERDES2。单一的OSERDES2模块可以实现4:1的并串转换,通过扩展可以实现8:1的转换率。
23.2实验任务
本章的实验任务是驱动超越者开发板上的HDMI接口,在显示器上显示彩条图案。
23.3硬件设计
图 23.3.1是超越者底板HDMI接口原理图,其中HDMI的三个数据通道HDMI_D[2:0]和一个时钟通道HDMI_CLK直接与TMDS差分引脚相连。
233660.png

图 23.3.1 HDMI接口原理图

HDMI_CEC指的是用户电气控制(Consumer Electronics Control),它用于HDMI连接线上的设备之间进行信息交换。当一个设备的状态发生变化时,CEC可以使用远程控制或自动改变设置来命令连接的关联设备的状态发生相应的变化。例如,如果用户放置一张碟片到蓝光播放器并开始播放,那么高清电视机将会自动打开电源,设置正确的视频输入格式和打开环绕声设备等等,这种关联通信提供了一个更好的客户体验。
HDMI_HPD指的是热插拔检测(Hot Plug Detect),当视频设备与接收设备通过HDMI连接时,接收设备将HPD置为高电平,通知发送设备。当发送设备检测到HPD为低电平时,表明断开连接。
HDMI_SCL_LS和HDMI_SDA_LS是HDMI接口的显示数据通道(DDC,Display Data Channel),用于HDMI发送端和接收端之间交换一些配置信息,通过I2C协议通信。发送端通过DDC通道,读取接收端保存在EEPROM中的EDID数据,获取接收端的信息,确认接收端终端显示的设置和功能,决定跟接收端之间以什么格式传输音/视频数据。
本次实验只使用了HDMI接口的TMDS数据和TMDS时钟信号,各端口的管脚分配如下表所示:
表 23.3.1 HDMI彩条显示实验管脚分配
2331.png

需要注意的是,TMDS数据和时钟信号需要在约束文件中指定电平标准为TMDS_33。另外,对于差分信号我们只需要指定正极的引脚位置,工具会自动对负极进行管脚分配。
相关的管脚约束如下所示:
  1. NET sys_clk  LOC = N8 | IOSTANDARD = "LVCMOS33" | TNM_NET = sys_clk_pin;  
  2. TIMESPEC "TS_sys_clk" = PERIOD "sys_clk"  20  ns HIGH 50 %;

  3. NET sys_rst_n                             LOC = G16 | IOSTANDARD = "LVCMOS33";   
  4. NET tmds_data_p[0]                       LOC = R7 | IOSTANDARD = "TMDS_33";
  5. NET tmds_data_p[1]                       LOC = P8 | IOSTANDARD = "TMDS_33";
  6. NET tmds_data_p[2]                       LOC = R9 | IOSTANDARD = "TMDS_33";
  7. NET tmds_clk_p                            LOC = P6 | IOSTANDARD = "TMDS_33";  
复制代码

23.4程序设计
由于本次实验只需要通过HDMI接口显示图像,因此将其当成DVI接口进行驱动。另外我们只需要实现图像的发送功能。由此得出本次实验的系统框图如下所示:
235313.png

图 23.4.1 系统框图

由系统框图可知,FPGA部分包括三个模块,分别是HDMI显示驱动模块(video_driver)、HDMI显示模块(video_display)和RGB转DVI模块(rgbtodvi_top)。
HDMI显示驱动模块(video_driver)在像素时钟的驱动下输出数据使能信号用于数据同步,同时还输出像素点的横纵坐标,供HDMI显示模块(video_display)调用。
视频显示模块(video_display)在像素时钟的驱动下根据像素点的横纵坐标来绘制彩条图案。
RGB转DVI模块(rgbtodvi_top)是将将RGB888格式的视频图像转换成TMDS数据输出并输出像素时钟。
顶层模块的代码如下:
  1. 1  module  top_hdmi_colorbar(
  2. 2      input        sys_clk,
  3. 3      input        sys_rst_n,
  4. 4        
  5. 5      output       tmds_clk_p,    // TMDS 时钟通道
  6. 6      output       tmds_clk_n,
  7. 7      output [2:0] tmds_data_p,   // TMDS 数据通道
  8. 8      output [2:0] tmds_data_n
  9. 9  );
  10. 10
  11. 11 //wire define
  12. 12 wire  [10:0]  pixel_xpos_w;
  13. 13 wire  [10:0]  pixel_ypos_w;
  14. 14 wire  [23:0]  pixel_data_w;
  15. 15 wire          video_hs;
  16. 16 wire          video_vs;
  17. 17 wire          video_de;
  18. 18 wire  [23:0]  video_rgb;
  19. 19
  20. 20 //*****************************************************
  21. 21 //**                    main code
  22. 22 //*****************************************************
  23. 23
  24. 24 //例化视频显示驱动模块
  25. 25 video_driver u_video_driver(
  26. 26     .pixel_clk      (tx_pclk),
  27. 27     .sys_rst_n      (sys_rst_n),
  28. 28
  29. 29     .video_hs       (video_hs),      //行信号
  30. 30     .video_vs       (video_vs),      //场信号
  31. 31     .video_de       (video_de),      //数据使能
  32. 32     .video_rgb      (video_rgb),     //像素点颜色数据输出
  33. 33
  34. 34     .pixel_xpos     (pixel_xpos_w),  //像素点横坐标
  35. 35     .pixel_ypos     (pixel_ypos_w),  //像素点纵坐标
  36. 36     .pixel_data     (pixel_data_w)   //像素点颜色数据输入
  37. 37     );
  38. 38
  39. 39 //例化视频显示模块
  40. 40 video_display  u_video_display(
  41. 41     .pixel_clk      (tx_pclk),
  42. 42     .sys_rst_n      (sys_rst_n),
  43. 43
  44. 44     .pixel_xpos     (pixel_xpos_w),
  45. 45     .pixel_ypos     (pixel_ypos_w),
  46. 46     .pixel_data     (pixel_data_w)
  47. 47     );
  48. 48
  49. 49 rgbtodvi_top u_rgbtodvi_top (
  50. 50   .sys_clk     (sys_clk),
  51. 51   .blue_din    (video_rgb[7:0]),
  52. 52   .green_din   (video_rgb[15:8]),
  53. 53   .red_din     (video_rgb[23:16]),
  54. 54   .hsync       (video_hs),
  55. 55   .vsync       (video_vs),
  56. 56   .de          (video_de),   
  57. 57
  58. 58   .pclk        (tx_pclk),  
  59. 59   .TMDS_CLK    (tmds_clk_p),          // TMDS 时钟通道
  60. 60   .TMDS_CLKB   (tmds_clk_n),   
  61. 61   .TMDS        (tmds_data_p),         // TMDS 数据通道
  62. 62   .TMDSB       (tmds_data_n)
  63. 63  );
  64. 64  
  65. 65 endmodule
复制代码

顶层模块主要完成对其他模块的例化。在顶层模块中,video_display模块(第40行)负责产生RGB888格式的彩条图案,然后在video_driver模块(第25行)的驱动下按照工业标准的VGA显示时序输出视频信号、行场同步信号以及视频有效信号。这些信号作为RGB2DVI模块(第49行)的输入,最终转换成DVI/HDMI接口标准的TMDS串行数据输出到HDMI接口。
上述代码中的video_display模块和video_driver模块与LCD彩条显示实现中的lcd_driver和lcd_display模块几乎完全相同,如果大家对这两个模块不熟悉的话,请参考《LCD彩条显示实验》。
RGB2DVI顶层模块的设计图如下所示:
237888.png

图 23.4.2 RGB2DVI模块图

图 23.4.2中,Encoder模块负责对数据进行编码,Gear Box模块是将10bit输入转换为5bit输出,PLL模块负责产生3个时钟,OSERDES模块对编码后的数据进行并串转换,最后通过OBUFDS转换成TMDS差分信号传输。
首先来看一下rgbtodvi_top模块的代码:
  1. 1   module rgbtodvi_top (
  2. 2     input  wire       sys_clk,        
  3. 3     input  wire [7:0] blue_din,       // Blue data in
  4. 4     input  wire [7:0] green_din,      // Green data in
  5. 5     input  wire [7:0] red_din,        // Red data in
  6. 6     input  wire       hsync,          // hsync data
  7. 7     input  wire       vsync,          // vsync data
  8. 8     input  wire       de,             // data enable
  9. 9     
  10. 10    output wire       pclk,           // pixel clock   
  11. 11    output wire       TMDS_CLK,
  12. 12    output wire       TMDS_CLKB,  
  13. 13    output wire [2:0] TMDS,
  14. 14    output wire [2:0] TMDSB
  15. 15    );
  16. 16  
  17. 17  //wire define  
  18. 18  wire [9:0] red ;
  19. 19  wire [9:0] green ;
  20. 20  wire [9:0] blue ;
  21. 21  wire [4:0] tmds_data0;
  22. 22  wire [4:0] tmds_data1;
  23. 23  wire [4:0] tmds_data2;
  24. 24  wire [2:0] tmdsint;
  25. 25  wire [2:0] tmdsint1;
  26. 26  
  27. 27  //*****************************************************
  28. 28  //**                    main code
  29. 29  //*****************************************************
  30. 30   
  31. 31  assign rstin = ~tx_bufpll_lock;
  32. 32   
  33. 33   
  34. 34  pll u_pll
  35. 35     (// Clock in ports
  36. 36      .CLK_IN1(sys_clk),     
  37. 37      // Clock out ports
  38. 38      .CLK_OUT1(tx_pllclk0),     
  39. 39      .CLK_OUT2(pclkx2),     
  40. 40      .CLK_OUT3(pclk),   
  41. 41      // Status and control signals
  42. 42      .LOCKED(tx_plllckd)     
  43. 43  );  
  44. 44  // regenerate pclkx10 for TX
  45. 45  
  46. 46  BUFPLL #(
  47. 47      .DIVIDE(5)
  48. 48  ) tx_ioclk_buf (
  49. 49      .PLLIN(tx_pllclk0),
  50. 50      .GCLK(pclkx2),
  51. 51      .LOCKED(tx_plllckd),
  52. 52      .IOCLK(pclkx10),
  53. 53      .SERDESSTROBE(serdesstrobe),
  54. 54      .LOCK(tx_bufpll_lock)
  55. 55  );  
  56. 56   
  57. 57  // Forward TMDS Clock Using OSERDES2 block
  58. 58  reg [4:0] tmdsclkint = 5'b00000;
  59. 59  reg toggle = 1'b0;
  60. 60  
  61. 61  always @ (posedge pclkx2 or posedge rstin) begin
  62. 62    if (rstin)
  63. 63      toggle <= 1'b0;
  64. 64    else
  65. 65      toggle <= ~toggle;
  66. 66  end
  67. 67  
  68. 68  always @ (posedge pclkx2) begin
  69. 69    if (toggle)
  70. 70      tmdsclkint <= 5'b11111;
  71. 71    else
  72. 72      tmdsclkint <= 5'b00000;
  73. 73  end
  74. 74  
  75. 75  wire tmdsclk;
  76. 76  
  77. 77  serdes_n_to_1 #(
  78. 78    .SF           (5))
  79. 79  clkout (
  80. 80    .iob_data_out (tmdsclk),
  81. 81    .ioclk        (pclkx10),
  82. 82    .serdesstrobe (serdesstrobe),
  83. 83    .gclk         (pclkx2),
  84. 84    .reset        (rstin),
  85. 85    .datain       (tmdsclkint));
  86. 86  
  87. 87  OBUFDS TMDS3 (.I(tmdsclk), .O(TMDS_CLK), .OB(TMDS_CLKB)) ;// clock
  88. 88  
  89. 89  //8b/10b编码
  90. 90  encode encb (
  91. 91    .clkin    (pclk),
  92. 92    .rstin    (rstin),
  93. 93    .din      (blue_din),
  94. 94    .c0       (hsync),
  95. 95    .c1       (vsync),
  96. 96    .de       (de),
  97. 97    .dout     (blue)) ;
  98. 98  
  99. 99  encode encg (
  100. 100   .clkin    (pclk),
  101. 101   .rstin    (rstin),
  102. 102   .din      (green_din),
  103. 103   .c0       (1'b0),
  104. 104   .c1       (1'b0),
  105. 105   .de       (de),
  106. 106   .dout     (green)) ;
  107. 107   
  108. 108 encode encr (
  109. 109   .clkin    (pclk),
  110. 110   .rstin    (rstin),
  111. 111   .din      (red_din),
  112. 112   .c0       (1'b0),
  113. 113   .c1       (1'b0),
  114. 114   .de       (de),
  115. 115   .dout     (red)) ;
  116. 116
  117. 117 wire [29:0] s_data = {red[4:0], green[4:0], blue[4:0],
  118. 118                       red[9:5], green[9:5], blue[9:5]};
  119. 119                        
  120. 120 convert_30to15_fifo u_convert_30to15_fifo (
  121. 121   .rst(rstin),     // input rst
  122. 122   .wr_clk(pclk),   // input wr_clk
  123. 123   .rd_clk(pclkx2), // input rd_clk
  124. 124   .din(s_data), // input [29 : 0] din
  125. 125   .wr_en(1'b1), // input wr_en
  126. 126   .rd_en(1'b1), // input rd_en
  127. 127   .dout({tmds_data2, tmds_data1, tmds_data0}), // output [14 : 0] dout
  128. 128   .full(), // output full
  129. 129   .empty() // output empty
  130. 130 );  
  131. 131   
  132. 132 // Forward TMDS Data: 3 channels
  133. 133 serdes_n_to_1 #(.SF(5)) oserdes0 (
  134. 134            .ioclk(pclkx10),
  135. 135            .serdesstrobe(serdesstrobe),
  136. 136            .reset(rstin),
  137. 137            .gclk(pclkx2),
  138. 138            .datain(tmds_data0),
  139. 139            .iob_data_out(tmdsint[0])) ;
  140. 140
  141. 141 serdes_n_to_1 #(.SF(5)) oserdes1 (
  142. 142            .ioclk(pclkx10),
  143. 143            .serdesstrobe(serdesstrobe),
  144. 144            .reset(rstin),
  145. 145            .gclk(pclkx2),
  146. 146            .datain(tmds_data1),
  147. 147            .iob_data_out(tmdsint[1])) ;
  148. 148
  149. 149 serdes_n_to_1 #(.SF(5)) oserdes2 (
  150. 150            .ioclk(pclkx10),
  151. 151            .serdesstrobe(serdesstrobe),
  152. 152            .reset(rstin),
  153. 153            .gclk(pclkx2),
  154. 154            .datain(tmds_data2),
  155. 155            .iob_data_out(tmdsint[2])) ;
  156. 156
  157. 157 OBUFDS TMDS0 (.I(tmdsint[0]), .O(TMDS[0]), .OB(TMDSB[0])) ;
  158. 158 OBUFDS TMDS1 (.I(tmdsint[1]), .O(TMDS[1]), .OB(TMDSB[1])) ;
  159. 159 OBUFDS TMDS2 (.I(tmdsint[2]), .O(TMDS[2]), .OB(TMDSB[2])) ;
  160. 160
  161. 161 endmodule
复制代码

代码34行至42行是对PLL进行的例化。Pll锁相环产生3个时钟,分别是像素时钟(pclk)、2倍像素时钟(pclkx2)和10倍像素时钟(tx_pllclk0)。
这里需要注意下代码46至55行,这段代码是对BUFPLL的例化。BUFPLL原语是用于高速I/O接口,为高速串并转换器ISERDES2 (SDR)和OSERDES2 (SDR)生成时钟和选通脉冲。其结构如下图所示:
2312895.png

图 23.4.3 BUFFPLL结构图

BUFPLL拥有6个接口,其中PLLIN、GCLK和LOCKED是BUFPLL的输入信号,IOCLK、SERDESSTROBE和LOCK是BUFPLL的输出信号。各个信号的含义如下图所示:
2313055.png

图 23.4.4 BUFFPLL信号的含义

当BUFPLL连接到ISERDES2或者OSERDES2时,其波形如下图所示:
2313162.png

图 23.4.5 BUFPLL波形图

由上图可知,BUFPLL中的选通脉冲信号(tx_serdesstrobe)都是与GCLK的下降沿对齐的。
代码第61到73行是产生在像素时钟下的10bit的时钟数据。toggle信号代表像素时钟的高低电平,tmdsclkint信号代表时钟数据。
代码第77到85行是将5bit的并行时钟数据转换为串行时钟数据。
代码第87行是将串行的单端时钟信号转换为差分信号。因为本次实验最后是按TMDS接口输出的,而TMDS接口是差分输出,所以这里要将单端信号转换为差分信号。下面的像素数据也是同样的原理。其结构如下图所示:
2313483.png

图 23.4.6 OBUFDS结构

上图中I代表输入的单端信号,O代表差分信号的P端,OB代表差分信号的N端。
代码第90到115行是对8b/10b编码模块的例化。HDMI接口加这个的编码的意义是实现了传输过程中的直流平衡,即一段时间内传输的高电平(数字“1”)的个数大致等于低电平(数字“0”)的个数。同时,每个编码后的10-bit字符中状态跳转(“由1到0”或者“由0到1”)的次数将被限制在五次以内。
代码第117到130行是将数据由30bit转换为15bit输出。Spartan6系列的FPGA中专用的并串转换器——OSERDES2最大只支持1:4的串并转换,2个OSERDES2级联后也只能支持到1:8,而HDMI接口需要1:10的串并转换,所以这里将1:10的串并转换拆分为2个1:5的串并转换,故需要将30bit的并行数据转换为15bit并行数据输出。
代码第133到155行是对并串转换器模块的例化。该模块是将并行数据转换为串行数据输出。
下面来看一下Encoder模块的代码:
  1. 1   module encode (
  2. 2     input            clkin,    // pixel clock input
  3. 3     input            rstin,    // async. reset input (active high)
  4. 4     input      [7:0] din,      // data inputs: expect registered
  5. 5     input            c0,       // c0 input
  6. 6     input            c1,       // c1 input
  7. 7     input            de,       // de input
  8. 8     output reg [9:0] dout      // data outputs
  9. 9   );
  10. 10  
  11. 11    ////////////////////////////////////////////////////////////
  12. 12    // Counting number of 1s and 0s for each incoming pixel
  13. 13    // component. Pipe line the result.
  14. 14    // Register Data Input so it matches the pipe lined adder
  15. 15    // output
  16. 16    ////////////////////////////////////////////////////////////
  17. 17    reg [3:0] n1d; //number of 1s in din
  18. 18    reg [7:0] din_q;
  19. 19  
  20. 20    always @ (posedge clkin) begin
  21. 21      n1d <=#1 din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
  22. 22  
  23. 23      din_q <=#1 din;
  24. 24    end
  25. 25  
  26. 26    ///////////////////////////////////////////////////////
  27. 27    // Stage 1: 8 bit -> 9 bit
  28. 28    // Refer to DVI 1.0 Specification, page 29, Figure 3-5
  29. 29    ///////////////////////////////////////////////////////
  30. 30    wire decision1;
  31. 31  
  32. 32    assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q[0] == 1'b0));
  33. 33  /*
  34. 34    reg [8:0] q_m;
  35. 35    always @ (posedge clkin) begin
  36. 36      q_m[0] <=#1 din_q[0];
  37. 37      q_m[1] <=#1 (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
  38. 38      q_m[2] <=#1 (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
  39. 39      q_m[3] <=#1 (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
  40. 40      q_m[4] <=#1 (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
  41. 41      q_m[5] <=#1 (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
  42. 42      q_m[6] <=#1 (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
  43. 43      q_m[7] <=#1 (decision1) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);
  44. 44      q_m[8] <=#1 (decision1) ? 1'b0 : 1'b1;
  45. 45    end
  46. 46  */
  47. 47    wire [8:0] q_m;
  48. 48    assign q_m[0] = din_q[0];
  49. 49    assign q_m[1] = (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
  50. 50    assign q_m[2] = (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
  51. 51    assign q_m[3] = (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
  52. 52    assign q_m[4] = (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
  53. 53    assign q_m[5] = (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
  54. 54    assign q_m[6] = (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
  55. 55    assign q_m[7] = (decision1) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);
  56. 56    assign q_m[8] = (decision1) ? 1'b0 : 1'b1;
  57. 57  
  58. 58    /////////////////////////////////////////////////////////
  59. 59    // Stage 2: 9 bit -> 10 bit
  60. 60    // Refer to DVI 1.0 Specification, page 29, Figure 3-5
  61. 61    /////////////////////////////////////////////////////////
  62. 62    reg [3:0] n1q_m, n0q_m; // number of 1s and 0s for q_m
  63. 63    always @ (posedge clkin) begin
  64. 64      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];
  65. 65      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]);
  66. 66    end
  67. 67  
  68. 68    parameter CTRLTOKEN0 = 10'b1101010100;
  69. 69    parameter CTRLTOKEN1 = 10'b0010101011;
  70. 70    parameter CTRLTOKEN2 = 10'b0101010100;
  71. 71    parameter CTRLTOKEN3 = 10'b1010101011;
  72. 72  
  73. 73    reg [4:0] cnt; //disparity counter, MSB is the sign bit
  74. 74    wire decision2, decision3;
  75. 75  
  76. 76    assign decision2 = (cnt == 5'h0) | (n1q_m == n0q_m);
  77. 77    /////////////////////////////////////////////////////////////////////////
  78. 78    // [(cnt > 0) and (N1q_m > N0q_m)] or [(cnt < 0) and (N0q_m > N1q_m)]
  79. 79    /////////////////////////////////////////////////////////////////////////
  80. 80    assign decision3 = (~cnt[4] & (n1q_m > n0q_m)) | (cnt[4] & (n0q_m > n1q_m));
  81. 81  
  82. 82    ////////////////////////////////////
  83. 83    // pipe line alignment
  84. 84    ////////////////////////////////////
  85. 85    reg       de_q, de_reg;
  86. 86    reg       c0_q, c1_q;
  87. 87    reg       c0_reg, c1_reg;
  88. 88    reg [8:0] q_m_reg;
  89. 89  
  90. 90    always @ (posedge clkin) begin
  91. 91      de_q    <=#1 de;
  92. 92      de_reg  <=#1 de_q;
  93. 93      
  94. 94      c0_q    <=#1 c0;
  95. 95      c0_reg  <=#1 c0_q;
  96. 96      c1_q    <=#1 c1;
  97. 97      c1_reg  <=#1 c1_q;
  98. 98  
  99. 99      q_m_reg <=#1 q_m;
  100. 100   end
  101. 101
  102. 102   ///////////////////////////////
  103. 103   // 10-bit out
  104. 104   // disparity counter
  105. 105   ///////////////////////////////
  106. 106   always @ (posedge clkin or posedge rstin) begin
  107. 107     if(rstin) begin
  108. 108       dout <= 10'h0;
  109. 109       cnt <= 5'h0;
  110. 110     end else begin
  111. 111       if (de_reg) begin
  112. 112         if(decision2) begin
  113. 113           dout[9]   <=#1 ~q_m_reg[8];
  114. 114           dout[8]   <=#1 q_m_reg[8];
  115. 115           dout[7:0] <=#1 (q_m_reg[8]) ? q_m_reg[7:0] : ~q_m_reg[7:0];
  116. 116
  117. 117           cnt <=#1 (~q_m_reg[8]) ? (cnt + n0q_m - n1q_m) : (cnt + n1q_m - n0q_m);
  118. 118         end else begin
  119. 119           if(decision3) begin
  120. 120             dout[9]   <=#1 1'b1;
  121. 121             dout[8]   <=#1 q_m_reg[8];
  122. 122             dout[7:0] <=#1 ~q_m_reg[7:0];
  123. 123
  124. 124             cnt <=#1 cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);
  125. 125           end else begin
  126. 126             dout[9]   <=#1 1'b0;
  127. 127             dout[8]   <=#1 q_m_reg[8];
  128. 128             dout[7:0] <=#1 q_m_reg[7:0];
  129. 129
  130. 130             cnt <=#1 cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);
  131. 131           end
  132. 132         end
  133. 133       end else begin
  134. 134         case ({c1_reg, c0_reg})
  135. 135           2'b00:   dout <=#1 CTRLTOKEN0;
  136. 136           2'b01:   dout <=#1 CTRLTOKEN1;
  137. 137           2'b10:   dout <=#1 CTRLTOKEN2;
  138. 138           default: dout <=#1 CTRLTOKEN3;
  139. 139         endcase
  140. 140
  141. 141         cnt <=#1 5'h0;
  142. 142       end
  143. 143     end
  144. 144   end
  145. 145   
  146. 146 endmodule
复制代码

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

图 23.4.7 TMDS编码算法

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

图 23.4.8 TMDS编码算法的参数

这里有个地方需要大家注意下,上图中信号cnt的最高位为符号位。
下面是serdes_n_to_1模块的代码:
  1. <font size="5">1   module serdes_n_to_1 (
  2. 2       ioclk,
  3. 3       serdesstrobe,
  4. 4       reset,
  5. 5       gclk,
  6. 6       datain,
  7. 7       iob_data_out
  8. 8   ) ;
  9. 9   
  10. 10  parameter integer SF = 8 ;          // Parameter to set the serdes factor 1..8
  11. 11  
  12. 12  input           ioclk ;         // IO Clock network
  13. 13  input           serdesstrobe ;  // Parallel data capture strobe
  14. 14  input           reset ;         // Reset
  15. 15  input           gclk ;          // Global clock
  16. 16  input   [SF-1 : 0]  datain ;    // Data for output
  17. 17  output          iob_data_out ;  // output data
  18. 18  
  19. 19  wire        cascade_di ;      
  20. 20  wire        cascade_do ;      
  21. 21  wire        cascade_ti ;      
  22. 22  wire        cascade_to ;      
  23. 23  wire    [8:0]   mdatain ;      
  24. 24  
  25. 25  genvar i ;              // Pad out the input data bus with 0's to 8 bits to avoid errors
  26. 26  generate
  27. 27  for (i = 0 ; i <= (SF - 1) ; i = i + 1)
  28. 28  begin : loop0
  29. 29  assign mdatain</font><font size="5"> = datain</font><span style="font-style: italic;"><font size="5" style="font-style: normal;"> ;
  30. 30  end
  31. 31  endgenerate
  32. 32  generate
  33. 33  for (i = (SF) ; i <= 8 ; i = i + 1)
  34. 34  begin : loop1
  35. 35  assign mdatain</font><font size="5" style="font-style: normal;"> = 1'b0 ;
  36. 36  end
  37. 37  endgenerate
  38. 38  
  39. 39  OSERDES2 #(
  40. 40      .DATA_WIDTH         (SF), // SERDES word width.  This should match the setting is BUFPLL
  41. 41      .DATA_RATE_OQ       ("SDR"),        // <SDR>, DDR
  42. 42      .DATA_RATE_OT       ("SDR"),        // <SDR>, DDR
  43. 43      .SERDES_MODE        ("MASTER"),     // <DEFAULT>, MASTER, SLAVE
  44. 44      .OUTPUT_MODE        ("DIFFERENTIAL"))
  45. 45  oserdes_m (
  46. 46      .OQ             (iob_data_out),
  47. 47      .OCE            (1'b1),
  48. 48      .CLK0           (ioclk),
  49. 49      .CLK1           (1'b0),
  50. 50      .IOCE           (serdesstrobe),
  51. 51      .RST            (reset),
  52. 52      .CLKDIV         (gclk),
  53. 53      .D4             (mdatain[7]),
  54. 54      .D3             (mdatain[6]),
  55. 55      .D2             (mdatain[5]),
  56. 56      .D1             (mdatain[4]),
  57. 57      .TQ             (),
  58. 58      .T1             (1'b0),
  59. 59      .T2             (1'b0),
  60. 60      .T3             (1'b0),
  61. 61      .T4             (1'b0),
  62. 62      .TRAIN          (1'b0),
  63. 63      .TCE            (1'b1),
  64. 64      .SHIFTIN1       (1'b1),             // Dummy input in Master
  65. 65      .SHIFTIN2       (1'b1),             // Dummy input in Master
  66. 66      .SHIFTIN3       (cascade_do),       // Cascade output D data from slave
  67. 67      .SHIFTIN4       (cascade_to),       // Cascade output T data from slave
  68. 68      .SHIFTOUT1      (cascade_di),       // Cascade input D data to slave
  69. 69      .SHIFTOUT2      (cascade_ti),       // Cascade input T data to slave
  70. 70      .SHIFTOUT3      (),                 // Dummy output in Master
  71. 71      .SHIFTOUT4      ()) ;               // Dummy output in Master
  72. 72  
  73. 73  OSERDES2 #(
  74. 74      .DATA_WIDTH         (SF), // SERDES word width.  This should match the setting is BUFPLL
  75. 75      .DATA_RATE_OQ       ("SDR"),        // <SDR>, DDR
  76. 76      .DATA_RATE_OT       ("SDR"),        // <SDR>, DDR
  77. 77      .SERDES_MODE        ("SLAVE"),      // <DEFAULT>, MASTER, SLAVE
  78. 78      .OUTPUT_MODE        ("DIFFERENTIAL"))
  79. 79  oserdes_s (
  80. 80      .OQ             (),
  81. 81      .OCE            (1'b1),
  82. 82      .CLK0           (ioclk),
  83. 83      .CLK1           (1'b0),
  84. 84      .IOCE           (serdesstrobe),
  85. 85      .RST            (reset),
  86. 86      .CLKDIV         (gclk),
  87. 87      .D4             (mdatain[3]),
  88. 88      .D3             (mdatain[2]),
  89. 89      .D2             (mdatain[1]),
  90. 90      .D1             (mdatain[0]),
  91. 91      .TQ             (),
  92. 92      .T1             (1'b0),
  93. 93      .T2             (1'b0),
  94. 94      .T3             (1'b0),
  95. 95      .T4             (1'b0),
  96. 96      .TRAIN          (1'b0),
  97. 97      .TCE            (1'b1),
  98. 98      .SHIFTIN1       (cascade_di),   // Cascade input D from Master
  99. 99      .SHIFTIN2       (cascade_ti),   // Cascade input T from Master
  100. 100     .SHIFTIN3       (1'b1),         // Dummy input in Slave
  101. 101     .SHIFTIN4       (1'b1),         // Dummy input in Slave
  102. 102     .SHIFTOUT1      (),             // Dummy output in Slave
  103. 103     .SHIFTOUT2      (),             // Dummy output in Slave
  104. 104     .SHIFTOUT3      (cascade_do),       // Cascade output D data to Master
  105. 105     .SHIFTOUT4      (cascade_to)) ;     // Cascade output T data to Master
  106. 106
  107. 107 endmodule</font></span>
复制代码

serdes_n_to_1模块通过调用OSERDES2原语来实现5:1的并串转换。原语是Xilinx器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于IP核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。需要注意的是,一个OSERDES2只能实现最多4:1的转换,在这里我们通过位宽扩展实现了8:1的并串转换,如下图所示:
2324859.png

图 23.4.9位宽扩展示意图

如图 23.4.9所示,OSERDES2位宽扩展通过两个OSERDES2模块来实现,其中一个作为Master,另一个作为Slave,通过这种方式最多可实现8:1的并串转换。
23.5下载验证
首先我们将下载器与超越者开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用HDMI连接线将HDMI显示器连接到超越者开发板上的HDMI接口,最后连接开发板的电源,如下图所示:
2325130.png

图 23.5.1 超越者开发板连接示意图

打开电源开关,然后我们将本次实验生成的BIT文件下载下开发板中,下载完成之后HDMI显示器上显示彩条图案,说明本次实验在超越者开发板上面下载验证成功。
实验结果如下图所示:
2325283.png

图 23.5.2 实验结果


阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 2021-3-26 16:09:26 | 显示全部楼层
例程是1280X720的,想改成1920X1080 的,但是PLL时钟锁定了,如何修改?
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 05:35

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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