【正点原子FPGA连载】第二十一章HDMI彩条显示实验
本帖最后由 正点原子 于 2021-1-25 18:29 编辑1)实验平台:正点原子达芬奇FPGA开发板
2)购买链接:https://detail.tmall.com/item.htm?id=624335496505
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
4) 正点原子官方B站:https://space.bilibili.com/394620890
5)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入:
第二十一章HDMI彩条显示实验
HDMI接口在消费类电子行业,如电脑、液晶电视、投影仪等产品中得到了广泛的应用。一些专业的视频设备如摄像机、视频切换器等也都集成了HDMI接口。本章我们将学习如何驱动达芬奇开发板上的HDMI接口。
本章包括以下几个部分:
2121.1简介
21.2实验任务
21.3硬件设计
21.4程序设计
21.5下载验证
21.1简介
HDMI是新一代的多媒体接口标准,英文全称是High-Definition Multimedia Interface,即高清多媒体接口。它能够同时传输视频和音频,简化了设备的接口和连线;同时提供了更高的数据传输带宽,可以传输无压缩的数字音频及高分辨率视频信号。HDMI 1.0版本于2002年发布,最高数据传输速度为5Gbps;而2017年发布的HDMI 2.1标准的理论带宽可达48Gbps。
HDMI向下兼容DVI,但是DVI(数字视频接口)只能用来传输视频,而不能同时传输音频,这是两者最主要的差别。此外,DVI接口的尺寸明显大于HDMI接口,如下图所示:
图 21.1.1 DVI接口(左)和HDMI接口(右)实物图
图 21.1.1右侧是生活中最常见的A型HDMI接口,其引脚定义如下图所示:
图 21.1.2 HDMI接口引脚定义
DVI和HDMI接口协议在物理层使用TMDS标准传输音视频数据。TMDS(Transition Minimized Differential Signaling,最小化传输差分信号)是美国Silicon Image公司开发的一项高速数据传输技术,在DVI和HDMI视频接口中使用差分信号传输高速串行数据。TMDS差分传输技术使用两个引脚(如图 21.1.2中的“数据2+”和“数据2-”)来传输一路信号,利用这两个引脚间的电压差的正负极性和大小来决定传输数据的数值(0或1)。
Xilinx在Spartan-3A系列之后的器件中,加入了对TMDS接口标准的支持,用于在FPGA内部实现DVI和HDMI接口。
由于本次实验只是使用HDMI接口来显示图像,不需要传输音频,因此我们只需要实现DVI接口的驱动逻辑即可。不过在此之前我们还需要简单地了解一下TMDS视频传输协议。
图 21.1.3是TMDS发送端和接收端的连接示意图。DVI或HDMI视频传输所使用的TMDS连接通过四个串行通道实现。对于DVI来说,其中三个通道分别用于传输视频中每个像素点的红、绿、蓝三个颜色分量(RGB 4:4:4格式)。HDMI默认也是使用三个RGB通道,但是它同样可以选择传输像素点的亮度和色度信息(YCrCb 4:4:4或YCrCb 4:2:2格式)。第四个通道是时钟通道,用于传输像素时钟。独立的TMDS时钟通道为接收端提供接收的参考频率,保证数据在接收端能够正确恢复。
图 21.1.3 TMDS连接示意图
如果每个像素点的颜色深度为24位,即RGB每个颜色分量各占8位,那么每个通道上的颜色数据将通过一个8B/10B的编码器(Encoder)来转换成一个10位的像素字符。然后这个10位的字符通过并串转换器(Serializer)转换成串行数据,最后由TMDS数据通道发送出去。这个10:1的并转串过程所生成的串行数据速率是实际像素时钟速率的10倍。
在传输视频图像的过程中,数据通道上传输的是编码后的有效像素字符。而在每一帧图像的行与行之间,以及视频中不同帧之间的时间间隔(消隐期)内,数据通道上传输的则是控制字符。每个通道上有两位控制信号的输入接口,共对应四种不同的控制字符。这些控制字符提供了视频的行同步(HZYNC)以及帧同步(VSYNC)信息,也可以用来指定所传输数据的边界(用于同步)。
对于DVI传输,整个视频的消隐期都用来传输控制字符。而HDMI传输的消隐期除了控制字符之外,还可以用于传输音频或者其他附加数据,比如字幕信息等。这就是DVI和HDMI协议之间最主要的差别。从图 21.1.3中也可以看出这一差别,即“Auxiliary Data”接口标有“HDMI Olny”,即它是HDMI所独有的接口。
从前面的介绍中我们可以看出,TMDS连接从逻辑功能上可以划分成两个阶段:编码和并串转换。在编码阶段,编码器将视频源中的像素数据、HDMI的音频/附加数据,以及行同步和场同步信号分别编码成10位的字符流。然后在并串转换阶段将上述的字符流转换成串行数据流,并将其从三个差分输出通道发送出去。
DVI编码器在视频有效数据段输出像素数据,在消隐期输出控制数据,如图 21.1.4所示。其中VDE(Video Data Enable)为高电平时表示视频数据有效,为低电平代表当前处于视频消隐期。
图 21.1.4 DVI编码输出示意图
图 21.1.5给出了三个通道的DVI编码器示意图。对于像素数据的RGB三个颜色通道,编码器的逻辑是完全相同的。VDE用于各个通道选择输出视频像素数据还是控制数据。HSYNC和VSYNC信号在蓝色通道进行编码得到10位字符,然后在视频消隐期传输。绿色和红色通道的控制信号C0和C1同样需要进行编码,并在消隐期输出。但是DVI规范中这两个通道的控制信号是预留的(未用到),因此将其置为2’b00。
图 21.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盘)/8_FPGA参考资料/HDMI/《HDMI Specification 13a》。为了简单起见,我们在这里把HDMI接口当作DVI接口进行驱动。
在编码之后,3个通道的10-bit字符将进行并串转换,这一过程是使用7系列FPGA中专用的硬件资源来实现的。7系列的FPGA提供了专用的并串转换器——OSERDESE2。单一的OSERDESE2模块可以实现8:1的并串转换,通过位宽扩展可以实现10:1和14:1的转换率。
21.2实验任务
本章的实验任务是驱动达芬奇开发板上的HDMI接口B(达芬奇开发板上有两个HDMI接口,HDMIA和HDMIB),在显示器上显示彩条图案。
21.3硬件设计
图 21.3.1 HDMI接口B原理图1
图 21.3.1是达芬奇底板HDMI接口B原理图的一部分,其中HDMI的三个数据通道HDMI_D和一个时钟通道HDMI_CLK直接与TMDS差分引脚相连。
HDMI_CEC指的是用户电气控制(Consumer Electronics Control),它用于HDMI连接线上的设备之间进行信息交换。当一个设备的状态发生变化时,CEC可以使用远程控制或自动改变设置来命令连接的关联设备的状态发生相应的变化。例如,如果用户放置一张碟片到蓝光播放器并开始播放,那么高清电视机将会自动打开电源,设置正确的视频输入格式和打开环绕声设备等等,这种关联通信提供了一个更好的客户体验。
HDMI_HPD指的是热拔插检测(Hot Plug Detect),当视频设备与接收设备通过HDMI连接时,接收设备将HPD置为高电平,通知发送设备。当发送设备检测到HPD为低电平时,表明断开连接。
图 21.3.2 HDMI接口B原理图2
在图 21.3.2中,HDMI_OUT_EN信号用于设置HDMI接口的输入输出模式,当其为高电平时作为输出端,此时由达芬奇底板输出HDMI接口的5V电源。同时,图 21.3.1中HDMI_HPD将作为输入信号使用;反之,当HDMI_OUT_EN为低电平时,开发板上的HDMI接口作为输入端,接口上的5V电源将由外部连接设备提供。同时,HDMI_HPD将输出高电平,用于指示HDMI连接状态。
图 21.3.3 HDMI接口A原理图3
在图 21.3.3中,HDMI_SCL_LS和HDMI_SDA_LS是HDMI接口的显示数据通道(DDC,Display Data Channel),用于HDMI 发送端和接收端之间交换一些配置信息,通过I2C协议通信。发送端通过 DDC通道,读取接收端保存在 EEPROM中的EDID数据,获取接收端的信息,确认接收端终端显示的设置和功能,决定跟接收端之间以什么格式传输音/视频数据。
本次实验只使用了HDMI接口的TMDS数据、TMDS时钟以及HDMI输出使能等信号,各端口的管脚分配如下表所示:
表 21.3.1 HDMI彩条显示实验管脚分配
需要注意的是,TMDS数据和时钟信号需要在约束文件中指定电平标准为TMDS_33。另外,对于差分信号我们只需要指定正极的引脚位置,工具会自动对负极进行管脚分配。
相关的管脚约束如下所示:
set_property -dict {PACKAGE_PIN R4IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN U2IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN A13IOSTANDARD TMDS_33 } }]
set_property -dict {PACKAGE_PIN A15IOSTANDARD TMDS_33 } }]
set_property -dict {PACKAGE_PIN A18IOSTANDARD TMDS_33 } }]
set_property -dict {PACKAGE_PIN B17IOSTANDARD TMDS_33 }
set_property -dict {PACKAGE_PIN C20IOSTANDARD LVCMOS15}
21.4程序设计
由于本次实验只需要通过HDMI接口显示图像,因此将其当成DVI接口进行驱动。另外我们只需要实现图像的发送功能。由此得出本次实验的系统框图如下所示:
图 21.4.1 系统框图
本次实验在LCD彩条显示实验的基础上添加一个RGB2DVI模块,将RGB888格式的视频图像转换成TMDS数据输出。本章的重点是介绍RGB2DVI模块,其余模块的介绍请参考LCD彩条显示实验。
RGB2DVI顶层模块的设计框图如下所示:
图 21.4.2 RGB2DVI模块框图
图 21.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 `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 din, // data inputs: expect registered
7 input c0, // c0 input
8 input c1, // c1 input
9 input de, // de input
10 output reg 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 n1d; //number of 1s in din
20 reg din_q;
21
22//计算像素数据中“1”的个数
23 always @ (posedge clkin) begin
24 n1d <=#1 din + din + din + din + din + din + din + din;
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 == 1'b0));
36
37 wire q_m;
38 assign q_m = din_q;
39 assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
40 assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
41 assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
42 assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
43 assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
44 assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
45 assign q_m = (decision1) ? (q_m ^~ din_q) : (q_m ^ din_q);
46 assign q_m = (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 n1q_m, n0q_m; // number of 1s and 0s for q_m
53 always @ (posedge clkin) begin
54 n1q_m<=#1 q_m + q_m + q_m + q_m + q_m + q_m + q_m + q_m;
55 n0q_m<=#1 4'h8 - (q_m + q_m + q_m + q_m + q_m + q_m + q_m + q_m);
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 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 & (n1q_m > n0q_m)) | (cnt & (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 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 <=#1 ~q_m_reg;
104 dout <=#1 q_m_reg;
105 dout <=#1 (q_m_reg) ? q_m_reg : ~q_m_reg;
106
107 cnt <=#1 (~q_m_reg) ? (cnt + n0q_m - n1q_m) : (cnt + n1q_m - n0q_m);
108 end else begin
109 if(decision3) begin
110 dout <=#1 1'b1;
111 dout <=#1 q_m_reg;
112 dout <=#1 ~q_m_reg;
113
114 cnt <=#1 cnt + {q_m_reg, 1'b0} + (n0q_m - n1q_m);
115 end else begin
116 dout <=#1 1'b0;
117 dout <=#1 q_m_reg;
118 dout <=#1 q_m_reg;
119
120 cnt <=#1 cnt - {~q_m_reg, 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
dvi_encoder模块按照DVI接口规范中TMDS编码算法对输入的8位像素数据以及2位行场同步信号进行编码。该模块是Xilinx应用笔记XAPP460中所提供的编码模块,其具体实现的编码算法如下图所示:
图 21.4.3 TMDS编码算法
TMDS通过逻辑算法将8位字符数据通过最小转换编码为10位字符数据,前8位数据由原始信号经运算后获得,第9位表示运算的方式,1表示异或0表示异或非。经过DC平衡后(第10位),采用差分信号传输数据。第10位实际是一个反转标志位,1表示进行了反转而0表示没有反转,从而达到 DC 平衡。
接收端在收到信号后,再进行相反的运算。TMDS和LVDS、TTL相比有较好的电磁兼容性能。这种算法可以减小传输信号过程的上冲和下冲,而DC平衡使信号对传输线的电磁干扰减少,可以用低成本的专用电缆实现长距离、高质量的数字信号传输。
图 21.4.3所描述的算法是DVI接口规范所定义的,我们不作深入研究,大家有兴趣的话也可以对照dvi_encoder模块中的代码来分析整个算法流程是如何使用Verilog来实现的。算法中各个参数的含义如下图所示:
图 21.4.4 TMDS编码算法的参数
TMDS编码之后的数据由Serializer模块进行并串转换,代码如下所示:
1 `timescale 1ns / 1ps
2
3 module serializer_10_to_1(
4 input reset, // 复位,高有效
5 input paralell_clk, // 输入并行数据时钟
6 input serial_clk_5x, // 输入串行数据时钟
7 input paralell_data, // 输入并行数据
8
9 output serial_data_out // 输出串行数据
10 );
11
12//wire define
13wire cascade1; //用于两个OSERDESE2级联的信号
14wire cascade2;
15
16//*****************************************************
17//** main code
18//*****************************************************
19
20//例化OSERDESE2原语,实现并串转换,Master模式
21OSERDESE2 #(
22 .DATA_RATE_OQ ("DDR"), // 设置双倍数据速率
23 .DATA_RATE_TQ ("SDR"), // DDR, BUF, SDR
24 .DATA_WIDTH (10), // 输入的并行数据宽度为10bit
25 .SERDES_MODE ("MASTER"), // 设置为Master,用于10bit宽度扩展
26 .TBYTE_CTL ("FALSE"), // Enable tristate byte operation (FALSE, TRUE)
27 .TBYTE_SRC ("FALSE"), // Tristate byte source (FALSE, TRUE)
28 .TRISTATE_WIDTH (1) // 3-state converter width (1,4)
29)
30OSERDESE2_Master (
31 .CLK (serial_clk_5x), // 串行数据时钟,5倍时钟频率
32 .CLKDIV (paralell_clk), // 并行数据时钟
33 .RST (reset), // 1-bit input: Reset
34 .OCE (1'b1), // 1-bit input: Output data clock enable
35
36 .OQ (serial_data_out),// 串行输出数据
37
38 .D1 (paralell_data), // D1 - D8: 并行数据输入
39 .D2 (paralell_data),
40 .D3 (paralell_data),
41 .D4 (paralell_data),
42 .D5 (paralell_data),
43 .D6 (paralell_data),
44 .D7 (paralell_data),
45 .D8 (paralell_data),
46
47 .SHIFTIN1 (cascade1), // SHIFTIN1 用于位宽扩展
48 .SHIFTIN2 (cascade2), // SHIFTIN2
49 .SHIFTOUT1(), // SHIFTOUT1: 用于位宽扩展
50 .SHIFTOUT2(), // SHIFTOUT2
51
52 .OFB (), // 以下是未使用信号
53 .T1 (1'b0),
54 .T2 (1'b0),
55 .T3 (1'b0),
56 .T4 (1'b0),
57 .TBYTEIN (1'b0),
58 .TCE (1'b0),
59 .TBYTEOUT (),
60 .TFB (),
61 .TQ ()
62);
63
64//例化OSERDESE2原语,实现并串转换,Slave模式
65OSERDESE2 #(
66 .DATA_RATE_OQ ("DDR"), // 设置双倍数据速率
67 .DATA_RATE_TQ ("SDR"), // DDR, BUF, SDR
68 .DATA_WIDTH (10), // 输入的并行数据宽度为10bit
69 .SERDES_MODE ("SLAVE"), // 设置为Slave,用于10bit宽度扩展
70 .TBYTE_CTL ("FALSE"), // Enable tristate byte operation (FALSE, TRUE)
71 .TBYTE_SRC ("FALSE"), // Tristate byte source (FALSE, TRUE)
72 .TRISTATE_WIDTH (1) // 3-state converter width (1,4)
73)
74OSERDESE2_Slave (
75 .CLK (serial_clk_5x), // 串行数据时钟,5倍时钟频率
76 .CLKDIV (paralell_clk), // 并行数据时钟
77 .RST (reset), // 1-bit input: Reset
78 .OCE (1'b1), // 1-bit input: Output data clock enable
79
80 .OQ (), // 串行输出数据
81
82 .D1 (1'b0), // D1 - D8: 并行数据输入
83 .D2 (1'b0),
84 .D3 (paralell_data),
85 .D4 (paralell_data),
86 .D5 (1'b0),
87 .D6 (1'b0),
88 .D7 (1'b0),
89 .D8 (1'b0),
90
91 .SHIFTIN1 (), // SHIFTIN1 用于位宽扩展
92 .SHIFTIN2 (), // SHIFTIN2
93 .SHIFTOUT1(cascade1), // SHIFTOUT1: 用于位宽扩展
94 .SHIFTOUT2(cascade2), // SHIFTOUT2
95
96 .OFB (), // 以下是未使用信号
97 .T1 (1'b0),
98 .T2 (1'b0),
99 .T3 (1'b0),
100 .T4 (1'b0),
101 .TBYTEIN (1'b0),
102 .TCE (1'b0),
103 .TBYTEOUT (),
104 .TFB (),
105 .TQ ()
106 );
107
108 endmodule
serdes_10_to_1模块通过调用OSERDESE2原语来实现10:1的并串转换。原语是Xilinx器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于IP核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。
需要注意的是,一个OSERDESE2只能实现最多8:1的转换率,在这里我们通过位宽扩展实现了10:1的并串转换,如下图所示:
图 21.4.5 OSERDESE2位宽扩展
如图 21.4.5所示,OSERDESE2位宽扩展通过两个OSERDESE2模块来实现,其中一个作为Master,另一个作为Slave,通过这种方式最多可实现14:1的并串转换。需要注意的是,在位宽扩展时,Slave模块的数据输入端只能使用D3至D8。
最后,我们在RGB2DVI顶层模块中调用上述两个模块,其代码如下所示:
1 module dvi_transmitter_top(
2 input pclk, // pixel clock
3 input pclk_x5, // pixel clock x5
4 input reset_n, // reset
5
6 input video_din, // RGB888 video in
7 input video_hsync, // hsync data
8 input video_vsync, // vsync data
9 input video_de, // data enable
10
11 output tmds_clk_p, // TMDS 时钟通道
12 output tmds_clk_n,
13 output tmds_data_p, // TMDS 数据通道
14 output tmds_data_n,
15 output tmds_oen // TMDS 输出使能
16 );
17
18//wire define
19wire reset;
20
21//并行数据
22wire red_10bit;
23wire green_10bit;
24wire blue_10bit;
25wire clk_10bit;
26
27//串行数据
28wire tmds_data_serial;
29wire tmds_clk_serial;
30
31//*****************************************************
32//** main code
33//*****************************************************
34assign tmds_oen = 1'b1;
35assign clk_10bit = 10'b1111100000;
36
37//异步复位,同步释放
38asyn_rst_syn reset_syn(
39 .reset_n (reset_n),
40 .clk (pclk),
41
42 .syn_reset(reset) //高有效
43 );
44
45//对三个颜色通道进行编码
46dvi_encoder encoder_b (
47 .clkin (pclk),
48 .rstin (reset),
49
50 .din (video_din),
51 .c0 (video_hsync),
52 .c1 (video_vsync),
53 .de (video_de),
54 .dout (blue_10bit)
55 ) ;
56
57dvi_encoder encoder_g (
58 .clkin (pclk),
59 .rstin (reset),
60
61 .din (video_din),
62 .c0 (1'b0),
63 .c1 (1'b0),
64 .de (video_de),
65 .dout (green_10bit)
66 ) ;
67
68dvi_encoder encoder_r (
69 .clkin (pclk),
70 .rstin (reset),
71
72 .din (video_din),
73 .c0 (1'b0),
74 .c1 (1'b0),
75 .de (video_de),
76 .dout (red_10bit)
77 ) ;
78
79//对编码后的数据进行并串转换
80serializer_10_to_1 serializer_b(
81 .reset (reset), // 复位,高有效
82 .paralell_clk (pclk), // 输入并行数据时钟
83 .serial_clk_5x (pclk_x5), // 输入串行数据时钟
84 .paralell_data (blue_10bit), // 输入并行数据
85
86 .serial_data_out (tmds_data_serial) // 输出串行数据
87 );
88
89serializer_10_to_1 serializer_g(
90 .reset (reset),
91 .paralell_clk (pclk),
92 .serial_clk_5x (pclk_x5),
93 .paralell_data (green_10bit),
94
95 .serial_data_out (tmds_data_serial)
96 );
97
98serializer_10_to_1 serializer_r(
99 .reset (reset),
100 .paralell_clk (pclk),
101 .serial_clk_5x (pclk_x5),
102 .paralell_data (red_10bit),
103
104 .serial_data_out (tmds_data_serial)
105 );
106
107 serializer_10_to_1 serializer_clk(
108 .reset (reset),
109 .paralell_clk (pclk),
110 .serial_clk_5x (pclk_x5),
111 .paralell_data (clk_10bit),
112
113 .serial_data_out (tmds_clk_serial)
114 );
115
116 //转换差分信号
117 OBUFDS #(
118 .IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
119 ) TMDS0 (
120 .I (tmds_data_serial),
121 .O (tmds_data_p),
122 .OB (tmds_data_n)
123 );
124
125 OBUFDS #(
126 .IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
127 ) TMDS1 (
128 .I (tmds_data_serial),
129 .O (tmds_data_p),
130 .OB (tmds_data_n)
131 );
132
133 OBUFDS #(
134 .IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
135 ) TMDS2 (
136 .I (tmds_data_serial),
137 .O (tmds_data_p),
138 .OB (tmds_data_n)
139 );
140
141 OBUFDS #(
142 .IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
143 ) TMDS3 (
144 .I (tmds_clk_serial),
145 .O (tmds_clk_p),
146 .OB (tmds_clk_n)
147 );
148
149 endmodule
在dvi_transmitter_top模块中,不仅例化了编码模块和并转串模块,同时还例化了四个OBUFDS原语,用于将三路数据和一路时钟信号转换成差分信号输出,如程序第116至147行所示。
OBUFDS是差分输出缓冲器,用于将来自FPGA内部逻辑的信号转换成差分信号输出,支持TMDS电平标准。OBUFDS原语示意图如下所示:
图 21.4.6 OBUFDS原语示意图
在程序的第37至43行,例化了asyn_rst_syn模块。OSERDESE2模块要求复位信号高电平有效,并且需要将异步复位信号同步到串行时钟域。因此,我们在asyn_rst_syn模块中将低电平有效的异步复位信号转换成高有效,同时对其进行异步复位,同步释放处理。
asyn_rst_syn模块的代码如下所示:
1module asyn_rst_syn(
2 input clk, //目的时钟域
3 input reset_n, //异步复位,低有效
4
5 output syn_reset //高有效
6 );
7
8//reg define
9reg 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
可以看出,该模块的代码非常简单,相当于在需要同步的时钟域下对输入的异步复位信号连接寄存了两次,这是一种非常常用的对异步信号进行同步的方法。需要注意的是,在程序第18行的always块中,还实现了将低电平有效的复位信号转换成高电平有效的功能。
到这里RGB2DVI模块的程序设计就介绍完了,整个模块的原理图如下所示:
图 21.4.7 RGB2DVI模块原理图
在图 21.4.2中,我们用红/绿/蓝三种颜色分别标识出了输入的视频数据video_din中三个不同的颜色通道。从图中也可以看出,每个颜色通道的处理过程都是一样的,都是先经过dvi_encoder进行编码,然后经过serializer_10_to_1模块进行并串转换,最后通过OBUFDS原语转换成TMDS差分信号。
接下来,我们在整个系统的顶层模块中调用RGB2DVI模块,通过HDMI接口输出彩条图案。
系统的顶层模块为hdmi_colorbar_top,其代码如下所示:
1modulehdmi_colorbar_top(
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 output tmds_oen // TMDS 输出使能
10 );
11
12 //wire define
13 wire pixel_clk;
14 wire pixel_clk_5x;
15 wire clk_locked;
16
17 wirepixel_xpos_w;
18 wirepixel_ypos_w;
19 wirepixel_data_w;
20
21 wire video_hs;
22 wire video_vs;
23 wire video_de;
24 wirevideo_rgb;
25
26 //*****************************************************
27 //** main code
28 //*****************************************************
29
30 //例化MMCM/PLL IP核
31 clk_wiz_0clk_wiz_0(
32 .clk_in1 (sys_clk),
33 .clk_out1 (pixel_clk), //像素时钟
34 .clk_out2 (pixel_clk_5x), //5倍像素时钟
35
36 .reset (~sys_rst_n),
37 .locked (clk_locked)
38 );
39
40 //例化视频显示驱动模块
41 video_driver u_video_driver(
42 .pixel_clk (pixel_clk),
43 .sys_rst_n (sys_rst_n),
44
45 .video_hs (video_hs),
46 .video_vs (video_vs),
47 .video_de (video_de),
48 .video_rgb (video_rgb),
49
50 .pixel_xpos (pixel_xpos_w),
51 .pixel_ypos (pixel_ypos_w),
52 .pixel_data (pixel_data_w)
53 );
54
55 //例化视频显示模块
56 video_displayu_video_display(
57 .pixel_clk (pixel_clk),
58 .sys_rst_n (sys_rst_n),
59
60 .pixel_xpos (pixel_xpos_w),
61 .pixel_ypos (pixel_ypos_w),
62 .pixel_data (pixel_data_w)
63 );
64
65 //例化HDMI驱动模块
66 dvi_transmitter_top u_rgb2dvi_0(
67 .pclk (pixel_clk),
68 .pclk_x5 (pixel_clk_5x),
69 .reset_n (sys_rst_n & clk_locked),
70
71 .video_din (video_rgb),
72 .video_hsync (video_hs),
73 .video_vsync (video_vs),
74 .video_de (video_de),
75
76 .tmds_clk_p (tmds_clk_p),
77 .tmds_clk_n (tmds_clk_n),
78 .tmds_data_p (tmds_data_p),
79 .tmds_data_n (tmds_data_n),
80 .tmds_oen (tmds_oen)
81 );
82
83 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的原理图如下所示:
图 21.4.8 顶层模块原理图
在图 21.4.8中,橙色的线条为像素时钟pixel_clk,该时钟信号连接到了其他所有模块。而蓝色的线条为串行数据时钟pixel_clk_5x,该时钟信号仅连接到了RGB2DVI模块。
21.5下载验证
首先我们将下载器与达芬奇底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用HDMI连接线将HDMI显示器连接到达芬奇底板上的HDMI接口。最后连接开发板的电源,并打开电源开关,如下图所示:
图 21.5.1 达芬奇开发板连接示意图
然后我们将本次实验生成的BIT文件下载下开发板中,下载完成之后HDMI显示器上显示彩条图案,说明本次实验在达芬奇开发板上面下载验证成功。
实验结果如下图所示:
图 21.5.2 实验结果
页:
[1]