正点原子 发表于 2021-1-30 11:33:56

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

本帖最后由 正点原子 于 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





第二十三章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接口,如下图所示:

图 23.1.1 DVI接口(左)和HDMI接口(右)实物图
图 23.1.1中的右侧接口是生活中最常见的A型HDMI接口,其引脚定义如下图所示:

图 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时钟通道为接收端提供接收的参考频率,保证数据在接收端能够正确恢复。

图 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)为高电平时表示视频数据有效,为低电平代表当前处于视频消隐期。

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

图 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和一个时钟通道HDMI_CLK直接与TMDS差分引脚相连。

图 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彩条显示实验管脚分配

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

NET sys_rst_n                           LOC = G16 | IOSTANDARD = "LVCMOS33";   
NET tmds_data_p                     LOC = R7 | IOSTANDARD = "TMDS_33";
NET tmds_data_p                     LOC = P8 | IOSTANDARD = "TMDS_33";
NET tmds_data_p                     LOC = R9 | IOSTANDARD = "TMDS_33";
NET tmds_clk_p                            LOC = P6 | IOSTANDARD = "TMDS_33";
23.4程序设计
由于本次实验只需要通过HDMI接口显示图像,因此将其当成DVI接口进行驱动。另外我们只需要实现图像的发送功能。由此得出本次实验的系统框图如下所示:

图 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数据输出并输出像素时钟。
顶层模块的代码如下:
1moduletop_hdmi_colorbar(
2      input      sys_clk,
3      input      sys_rst_n,
4      
5      output       tmds_clk_p,    // TMDS 时钟通道
6      output       tmds_clk_n,
7      output tmds_data_p,   // TMDS 数据通道
8      output tmds_data_n
9);
10
11 //wire define
12 wirepixel_xpos_w;
13 wirepixel_ypos_w;
14 wirepixel_data_w;
15 wire          video_hs;
16 wire          video_vs;
17 wire          video_de;
18 wirevideo_rgb;
19
20 //*****************************************************
21 //**                  main code
22 //*****************************************************
23
24 //例化视频显示驱动模块
25 video_driver u_video_driver(
26   .pixel_clk      (tx_pclk),
27   .sys_rst_n      (sys_rst_n),
28
29   .video_hs       (video_hs),      //行信号
30   .video_vs       (video_vs),      //场信号
31   .video_de       (video_de),      //数据使能
32   .video_rgb      (video_rgb),   //像素点颜色数据输出
33
34   .pixel_xpos   (pixel_xpos_w),//像素点横坐标
35   .pixel_ypos   (pixel_ypos_w),//像素点纵坐标
36   .pixel_data   (pixel_data_w)   //像素点颜色数据输入
37   );
38
39 //例化视频显示模块
40 video_displayu_video_display(
41   .pixel_clk      (tx_pclk),
42   .sys_rst_n      (sys_rst_n),
43
44   .pixel_xpos   (pixel_xpos_w),
45   .pixel_ypos   (pixel_ypos_w),
46   .pixel_data   (pixel_data_w)
47   );
48
49 rgbtodvi_top u_rgbtodvi_top (
50   .sys_clk   (sys_clk),
51   .blue_din    (video_rgb),
52   .green_din   (video_rgb),
53   .red_din   (video_rgb),
54   .hsync       (video_hs),
55   .vsync       (video_vs),
56   .de          (video_de),   
57
58   .pclk      (tx_pclk),
59   .TMDS_CLK    (tmds_clk_p),          // TMDS 时钟通道
60   .TMDS_CLKB   (tmds_clk_n),   
61   .TMDS      (tmds_data_p),         // TMDS 数据通道
62   .TMDSB       (tmds_data_n)
63);
64
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顶层模块的设计图如下所示:

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

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

图 23.4.4 BUFFPLL信号的含义
当BUFPLL连接到ISERDES2或者OSERDES2时,其波形如下图所示:

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

图 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   module encode (
2   input            clkin,    // pixel clock input
3   input            rstin,    // async. reset input (active high)
4   input       din,      // data inputs: expect registered
5   input            c0,       // c0 input
6   input            c1,       // c1 input
7   input            de,       // de input
8   output reg dout      // data outputs
9   );
10
11    ////////////////////////////////////////////////////////////
12    // Counting number of 1s and 0s for each incoming pixel
13    // component. Pipe line the result.
14    // Register Data Input so it matches the pipe lined adder
15    // output
16    ////////////////////////////////////////////////////////////
17    reg n1d; //number of 1s in din
18    reg din_q;
19
20    always @ (posedge clkin) begin
21      n1d <=#1 din + din + din + din + din + din + din + din;
22
23      din_q <=#1 din;
24    end
25
26    ///////////////////////////////////////////////////////
27    // Stage 1: 8 bit -> 9 bit
28    // Refer to DVI 1.0 Specification, page 29, Figure 3-5
29    ///////////////////////////////////////////////////////
30    wire decision1;
31
32    assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q == 1'b0));
33/*
34    reg q_m;
35    always @ (posedge clkin) begin
36      q_m <=#1 din_q;
37      q_m <=#1 (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
38      q_m <=#1 (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
39      q_m <=#1 (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
40      q_m <=#1 (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
41      q_m <=#1 (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
42      q_m <=#1 (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
43      q_m <=#1 (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
44      q_m <=#1 (decision1) ? 1'b0 : 1'b1;
45    end
46*/
47    wire q_m;
48    assign q_m = din_q;
49    assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
50    assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
51    assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
52    assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
53    assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
54    assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
55    assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
56    assign q_m = (decision1) ? 1'b0 : 1'b1;
57
58    /////////////////////////////////////////////////////////
59    // Stage 2: 9 bit -> 10 bit
60    // Refer to DVI 1.0 Specification, page 29, Figure 3-5
61    /////////////////////////////////////////////////////////
62    reg n1q_m, n0q_m; // number of 1s and 0s for q_m
63    always @ (posedge clkin) begin
64      n1q_m<=#1 q_m + q_m + q_m + q_m + q_m + q_m + q_m + q_m;
65      n0q_m<=#1 4'h8 - (q_m + q_m + q_m + q_m + q_m + q_m + q_m + q_m);
66    end
67
68    parameter CTRLTOKEN0 = 10'b1101010100;
69    parameter CTRLTOKEN1 = 10'b0010101011;
70    parameter CTRLTOKEN2 = 10'b0101010100;
71    parameter CTRLTOKEN3 = 10'b1010101011;
72
73    reg cnt; //disparity counter, MSB is the sign bit
74    wire decision2, decision3;
75
76    assign decision2 = (cnt == 5'h0) | (n1q_m == n0q_m);
77    /////////////////////////////////////////////////////////////////////////
78    // [(cnt > 0) and (N1q_m > N0q_m)] or [(cnt < 0) and (N0q_m > N1q_m)]
79    /////////////////////////////////////////////////////////////////////////
80    assign decision3 = (~cnt & (n1q_m > n0q_m)) | (cnt & (n0q_m > n1q_m));
81
82    ////////////////////////////////////
83    // pipe line alignment
84    ////////////////////////////////////
85    reg       de_q, de_reg;
86    reg       c0_q, c1_q;
87    reg       c0_reg, c1_reg;
88    reg q_m_reg;
89
90    always @ (posedge clkin) begin
91      de_q    <=#1 de;
92      de_reg<=#1 de_q;
93      
94      c0_q    <=#1 c0;
95      c0_reg<=#1 c0_q;
96      c1_q    <=#1 c1;
97      c1_reg<=#1 c1_q;
98
99      q_m_reg <=#1 q_m;
100   end
101
102   ///////////////////////////////
103   // 10-bit out
104   // disparity counter
105   ///////////////////////////////
106   always @ (posedge clkin or posedge rstin) begin
107   if(rstin) begin
108       dout <= 10'h0;
109       cnt <= 5'h0;
110   end else begin
111       if (de_reg) begin
112         if(decision2) begin
113         dout   <=#1 ~q_m_reg;
114         dout   <=#1 q_m_reg;
115         dout <=#1 (q_m_reg) ? q_m_reg : ~q_m_reg;
116
117         cnt <=#1 (~q_m_reg) ? (cnt + n0q_m - n1q_m) : (cnt + n1q_m - n0q_m);
118         end else begin
119         if(decision3) begin
120             dout   <=#1 1'b1;
121             dout   <=#1 q_m_reg;
122             dout <=#1 ~q_m_reg;
123
124             cnt <=#1 cnt + {q_m_reg, 1'b0} + (n0q_m - n1q_m);
125         end else begin
126             dout   <=#1 1'b0;
127             dout   <=#1 q_m_reg;
128             dout <=#1 q_m_reg;
129
130             cnt <=#1 cnt - {~q_m_reg, 1'b0} + (n1q_m - n0q_m);
131         end
132         end
133       end else begin
134         case ({c1_reg, c0_reg})
135         2'b00:   dout <=#1 CTRLTOKEN0;
136         2'b01:   dout <=#1 CTRLTOKEN1;
137         2'b10:   dout <=#1 CTRLTOKEN2;
138         default: dout <=#1 CTRLTOKEN3;
139         endcase
140
141         cnt <=#1 5'h0;
142       end
143   end
144   end
145   
146 endmodule
encode模块按照DVI接口规范中TMDS编码算法对输入的8位像素数据以及2位行场同步信号进行编码。该模块是Xilinx应用笔记XAPP460中所提供的编码模块,其具体实现的编码算法如下图所示:

图 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来实现的。算法中各个参数的含义如下图所示:

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

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

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

图 23.5.2 实验结果

rayt2012 发表于 2021-3-26 16:09:26

例程是1280X720的,想改成1920X1080 的,但是PLL时钟锁定了,如何修改?
页: [1]
查看完整版本: 【正点原子FPGA连载】第二十三章HDMI彩条显示实验