一、HDMI 基本介绍
HDMI 是新一代的多媒体接口标准,英文全称是 High-Definition Multimedia Interface,即高清多媒体接口。它能够同时传输视频和音频,简化了设备的接口和连线;同时提供了更高的数据传输带宽,可以传输无压缩的数字音频及高分辨率视频信号。HDMI 1.0 版本于 2002 年发布,最高数据传输速度为 5Gbps;而2017 年发布的 HDMI 2.1 标准的理论带宽可达 48Gbps。
HDMI 向下兼容 DVI,但是 DVI(数字视频接口)只能用来传输视频,而不能同时传输音频,这是两者最主要的差别。此外,DVI 接口的尺寸明显大于 HDMI 接口,如下图所示:
HDMI 的引脚定义如下:
TMDS(Transition Minimized Differential Signaling,最小化传输差分信号)是美国 Silicon Image 公司开发的一项高速数据传输技术,在 DVI 和 HDMI 视频接口中使用差分信号传输高速串行数据。
TMDS 差分传输技术使用两个引脚(如图中的“数据 2+”和“数据 2-”)来传输一路信号,利用这两个引脚间的电压差的正负极性和大小来决定传输数据的数值(0 或 1)。TMDS 传输系统分为两个部分:发送端和接收端。 TMDS 链路包括 3 个传输 RGB 信号的数据通道和 1 个传输时钟信号的通道。TMDS 发送端对这些数据进行编码和并/串转换,再将数据分别分配到独立的传输通道发送出去。接收端接收来自发送端的串行信号,对其进行解码和串/并转换,然后发送到显示器的控制端。与此同时也接收时钟信号,以实现同步。每一个数据通道都通过编码算法,将 8 位数据转换成最小化传输、直流平衡的 10 位数据。这使得数据的传输和恢复更加可靠。最小化传输差分信号是通过异或及异或非等逻辑算法将原始 8 位信号数据转换成 10 位,前 8 为数据由原始信号经运算后获得,第 9 位指示运算的方式,第 10 位用来对应直流平衡。如下图所示:
一般来说,HDMI 传输的编码格式中要包含视频数据、控制数据和数据包(数据包中包括音频数据和附加信息数据,例如纠错码等)。 TMDS 每个通道在传输时要包含一个 2bit 的控制数据、 8bit 的视频数据或者 4bit 的数据包即可。在 HDMI 信息传输过程中,可以分为三个阶段:视频数据传输周期、控制数据传输周期和数据岛传输周期,分别对应上述的三种数据类型。
1、传输最小化
8 位数据经过编码和直流平衡得到 10 位最小化数据,这仿佛增加了冗余位,对传输链路的带宽要求更高,但事实上,通过这种算法得到的 10 位数据在更长的同轴电缆中传输的可靠性增强了。下图是一个例子,说明对一个 8 位的并行 RED 数据编码、并/串转换。
(1)将 8 位并行 RED 数据发送到 TMDS 发送端。
(2)并/串转换。
(3)进行最小化传输处理,加上第 9 位,即编码过程。第 9 位数据称为编码位。
2、直流平衡
直流平衡(DC-balanced)就是指在编码过程中保证信道中直流偏移为零。方法是在原来的 9 位数据癿后面加上第 10 位数据,返样,传输的数据趋于直流平衡,使信号对传输线的电磁干扰减少,提高信号传输的可靠性。
3. 差分信号
TMDS 差分传动技术是一种利用2个引脚间电压差来传送信号的技术。传输数据的数值(“0”或者“1”)由两脚间电压正负极性和大小决定。即,采用 2 根线来传输信号,一根线上传输原来的信号,另一根线上传输与原来信号相反的信号。这样接收端就可以通过让一根线上的信号减去另一根线上的信号的方式来屏蔽电磁干扰,从而得到正确的信号。
另外,还有一个显示数据通道(DDC),是用于读取表示接收端显示器的清晰度等显示能力的扩展显示标识数据(EDID)的信号线。搭载 HDCP(High-bandwidth Digital Content Protection,高带宽数字内容保护技术)的发送、接收设备之间也利用 DDC 线进行密码键的认证。
二、DVI 和 HDMI
上图是 TMDS 发送端和接收端的连接示意图。DVI 或 HDMI 视频传输所使用的 TMDS 连接通过四个串行通道实现。对于DVI来说,其中三个通道分别用于传输视频中每个像素点的红、绿、蓝三个颜色分量(RGB4:4:4格式)。HDMI 默认也是使用三个 RGB 通道,但是它同样可以选择传输像素点的亮度和色度信息(YCrCb4:4:4或YCrCb 4:2:2格式)。第四个通道是时钟通道,用于传输像素时钟。独立的 TMDS 时钟通道为接收端提供接收的参考频率,保证数据在接收端能够正确恢复。
在传输视频图像的过程中,数据通道上传输的是编码后的有效像素字符。而在每一帧图像的行与行之间,以及视频中不同帧之间的时间间隔(消隐期)内,数据通道上传输的则是控制字符。每个通道上有两位控制信号的输入接口,共对应四种不同的控制字符。这些控制字符提供了视频的行同步(HZYNC)以及帧同步(VSYNC)信息,也可以用来指定所传输数据的边界(用于同步)。对于 DVI 传输,整个视频的消隐期都用来传输控制字符。而 HDMI 传输的消隐期除了控制字符之外,还可以用于传输音频或者其他附加数据(例如字母信息),4-bit 音频和附加数据将通过 TERC4 编码机制转换成 10-bit TERC4 字符,然后再绿色和红色通道上传输。从上图也可以看出这一差别,即“Auxiliary Data”接口标有“HDMI Olny”,即它是 HDMI 所独有的接口。如果我们不需要附加数据,只传输视频数据的话,完全可以把 HDMI 接口当做 DVI 接口进行驱动。关于其他区别,可以查看这个表:
下图是 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 在输入附加数据的同时,还需要输入 ADE(Aux/Audio Data Enable)信号,其作用和 VDE 是类似的:当 ADE 为高电平时,表明输入端的附加数据或者音频数据有效,DIV 是不能传音频的。想了解更多有关 HDMI的细节,可以参考HDMI 接口规范——《High-Definition Multimedia Interface Specification Version 1.3a》,英语不好的也可以查看文档《HDMI1.4规范中文版》。
三、HDMI 电路原理
这里直接引用正点原子领航者 ZYNQ 底板的 HDMI 接口原理图来说明:
HDMI 的三个数据通道 HDMI_D[2:0] 只和一个时钟通道 HDMI_CLK 直接与 TMDS 差分引脚相连。
- HDMI_CEC 指的是用户电气控制(Consumer Electronics Control),它用于 HDMI 连接线上的设备之间进行信息交换。当一个设备的状态发生变化时,CEC 可以使用远程控制或自动改变设置来命令连接的关联设备的状态发生相应的变化。例如,如果用户放置一张碟片到蓝光播放器并开始播放,那么高清电视机将会自动打开电源,设置正确的视频输入格式和打开环绕声设备等等,这种关联通信提供了一个更好的客户体验。
- HDMI_HPD 指的是热拔插检测(Hot Plug Detect),当视频设备与接收设备通过 HDMI 连接时,接收设备将 HPD 置为高电平,通知发送设备。当发送设备检测到 HPD 为低电平时,表明断开连接。
- HDMI_OUT_EN 信号,用于设置 HDMI 接口的输入输出模式,当其为高电平时作为输出端,此时由 FPGA 底板输出 HDMI 接口的 5V 电源。同时,HDMI_HPD 将作为输入信号使用。反之,当 HDMI_OUT_EN 为低电平时,HDMI_HPD 将输出高电平,用于指示 HDMI 连接状态。
- HDMI_SCL 和 HDMI_SDA信号, 是 HDMI 接口的显示数据通道(DDC,Display Data Channel),用于 HDMI 发送端和接收端之间交换一些配置信息,通过 I2C 协议通信。发送端通过 DDC 通道,读取接收端保存在 EEPROM 中的 EDID 数据,获取接收端的信息,确认接收端终端显示的设置和功能,决定跟接收端之间以什么格式传输音/视频数据。
本次实验只使用到了 TMDS 数据、TMDS 时钟以及 HDMI 输出使能等信号,相关的管脚约束如下所示:
set_property IOSTANDARD TMDS_33 [get_ports HDMI_clk_p]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_clk_n]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_d0_p]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_d0_n]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_d1_p]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_d1_n]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_d2_p]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_d2_n]
set_property PACKAGE_PIN C18 [get_ports HDMI_clk_p]
set_property PACKAGE_PIN D20 [get_ports HDMI_d0_p]
set_property PACKAGE_PIN C22 [get_ports HDMI_d1_p]
set_property PACKAGE_PIN B21 [get_ports HDMI_d2_p]
需要注意的是,TMDS 数据和时钟信号需要在约束文件中指定电平标准为 TMDS_33。另外,对于差分信号我们只需要指定正极的引脚位置,工具会自动对负极进行管脚分配。
四、HDMI 程序设计
TMDS 连接从逻辑功能上可以划分成两个阶段:编码和并串转换。在编码阶段,编码器将视频源中的像素数据、HDMI 的音频/附加数据、行同步和场同步信号分别编码成 10 位的字符流,然后在并串转换阶段将上述的字符流转换成串行数据流,并将其从三个差分输出通道发送出去。示意图如下所示:
将其放大,可以得到下面这个框图:
Encoder 模块负责对数据进行编码,Serializer 模块对编码后的数据进行并串转换,最后通过 OBUFDS 转化成 TMDS 差分信号传输。 图中左下脚 HDMI 的音频/附加数据输入在本次实验中并未用到,因此以虚线表示。
1、encoder:8B/10B
8B10B 编码是可以从 Xilinx 官网下载到此模块,模块名为 encode,此模块把 8 比特的数据进行从新映射为 10bit 数据,防止连续的 0 和 1 出现导致直流不平衡造成误码率升高。
`timescale 1 ps / 1ps
module encode (
input clkin, // pixel clock input
input rstin, // async. reset input (active high)
input [7:0] din, // data inputs: expect registered
input c0, // c0 input
input c1, // c1 input
input de, // de input
output reg [9:0] dout // data outputs
);
// Counting number of 1s and 0s for each incoming pixel
// component. Pipe line the result.
// Register Data Input so it matches the pipe lined adder
// output
reg [3:0] n1d; //number of 1s in din
reg [7:0] din_q;
always @ (posedge clkin) begin
n1d <= din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
din_q <= din;
end
///
// Stage 1: 8 bit -> 9 bit
// Refer to DVI 1.0 Specification, page 29, Figure 3-5
///
wire decision1;
assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q[0] == 1'b0));
/*
reg [8:0] q_m;
always @ (posedge clkin) begin
q_m[0] <=#1 din_q[0];
q_m[1] <=#1 (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
q_m[2] <=#1 (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
q_m[3] <=#1 (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
q_m[4] <=#1 (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
q_m[5] <=#1 (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
q_m[6] <=#1 (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
q_m[7] <=#1 (decision1) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);
q_m[8] <=#1 (decision1) ? 1'b0 : 1'b1;
end
*/
wire [8:0] q_m;
assign q_m[0] = din_q[0];
assign q_m[1] = (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
assign q_m[2] = (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
assign q_m[3] = (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
assign q_m[4] = (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
assign q_m[5] = (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
assign q_m[6] = (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
assign q_m[7] = (decision1) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);
assign q_m[8] = (decision1) ? 1'b0 : 1'b1;
/
// Stage 2: 9 bit -> 10 bit
// Refer to DVI 1.0 Specification, page 29, Figure 3-5
/
reg [3:0] n1q_m, n0q_m; // number of 1s and 0s for q_m
always @ (posedge clkin) begin
n1q_m <= q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7];
n0q_m <= 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]);
end
parameter CTRLTOKEN0 = 10'b1101010100;
parameter CTRLTOKEN1 = 10'b0010101011;
parameter CTRLTOKEN2 = 10'b0101010100;
parameter CTRLTOKEN3 = 10'b1010101011;
reg [4:0] cnt; //disparity counter, MSB is the sign bit
wire decision2, decision3;
assign decision2 = (cnt == 5'h0) | (n1q_m == n0q_m);
/
// [(cnt > 0) and (N1q_m > N0q_m)] or [(cnt < 0) and (N0q_m > N1q_m)]
/
assign decision3 = (~cnt[4] & (n1q_m > n0q_m)) | (cnt[4] & (n0q_m > n1q_m));
// pipe line alignment
reg de_q, de_reg;
reg c0_q, c1_q;
reg c0_reg, c1_reg;
reg [8:0] q_m_reg;
always @ (posedge clkin) begin
de_q <= de;
de_reg <= de_q;
c0_q <= c0;
c0_reg <= c0_q;
c1_q <= c1;
c1_reg <= c1_q;
q_m_reg <= q_m;
end
///
// 10-bit out
// disparity counter
///
always @ (posedge clkin or posedge rstin) begin
if(rstin) begin
dout <= 10'h0;
cnt <= 5'h0;
end else begin
if (de_reg) begin
if(decision2) begin
dout[9] <= ~q_m_reg[8];
dout[8] <= q_m_reg[8];
dout[7:0] <= (q_m_reg[8]) ? q_m_reg[7:0] : ~q_m_reg[7:0];
cnt <=#1 (~q_m_reg[8]) ? (cnt + n0q_m - n1q_m) : (cnt + n1q_m - n0q_m);
end else begin
if(decision3) begin
dout[9] <= 1'b1;
dout[8] <= q_m_reg[8];
dout[7:0] <= ~q_m_reg[7:0];
cnt <=#1 cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);
end else begin
dout[9] <= 1'b0;
dout[8] <= q_m_reg[8];
dout[7:0] <= q_m_reg[7:0];
cnt <= cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);
end
end
end else begin
case ({c1_reg, c0_reg})
2'b00: dout <= CTRLTOKEN0;
2'b01: dout <= CTRLTOKEN1;
2'b10: dout <= CTRLTOKEN2;
default: dout <= CTRLTOKEN3;
endcase
cnt <= 5'h0;
end
end
end
endmodule
encoder 模块按照 DVI 接口规范中 TMDS 编码算法对输入的 8 位像素数据以及 2 位行场同步信号进 行编码.该模块是 Xilinx 应用笔记 XAPP460 中所提供的编码模块,其具体实现的编码算法如下图所示:
TMDS 通过逻辑算法将 8 位字符数据通过最小转换编码为 10 位字符数据,前 8 位数据由原始信号经运算后获得,第 9 位表示运算的方式,1 表示异或 0 表示异或非。经过 DC 平衡后(第 10 位),采用差分信号传输数据。第 10 位实际是一个反转标志位,1 表示进行了反转而 0 表示没有反转,从而达到 DC 平衡。接收端在收到信号后,再进行相反的运算。TMDS 和 LVDS、TTL 相比有较好的电磁兼容性能。这种算法可以减小传输信号过程的上冲和下冲,而 DC 平衡使信号对传输线的电磁干扰减少,可以用低成本的专用电缆实现长距离、高质量的数字信号传输。图中所描述的算法是 DVI 接口规范所定义的,我们不作深入研究,算法中各个参数的含义如下图所示:
D | 视频信号 |
C0 C1 | 控制信号 |
DE | 使能信号 |
Cnt | 寄存器参数 |
N0{X} | 输入视频信号“1”的个数 |
N1{X} | 输入视频信号“0”的个数 |
q_out | 编码输出 |
encoder模块的调用代码如下所示,注意 u_encode_blue 里的 c0 和 c1 输入了 hsync 和 vsync,另两个颜色通道则不需要。
//==========================================================================
//== encode 例化,RGB三个通道
//==========================================================================
encode u_encode_red
(
.clkin (HDMI_clk1x ),
.rstin (HDMI_rst ),
.din (VGA_r ),
.c0 (1'b0 ),
.c1 (1'b0 ),
.de (VGA_de ),
.dout (dout_r )
);
encode u_encode_green
(
.clkin (HDMI_clk1x ),
.rstin (HDMI_rst ),
.din (VGA_g ),
.c0 (1'b0 ),
.c1 (1'b0 ),
.de (VGA_de ),
.dout (dout_g )
);
encode u_encode_blue
(
.clkin (HDMI_clk1x ),
.rstin (HDMI_rst ),
.din (VGA_b ),
.c0 (VGA_hsync ), //hsync
.c1 (VGA_vsync ), //vsync
.de (VGA_de ),
.dout (dout_b )
);
2、Serializer:并转串
Serializer10_1 模块通过调用 OSERDESE2 原语来实现 10:1 的并串转换。原语是 Xilinx 器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于 IP 核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。OSERDESE2 详细介绍见 Xilinx 官方提供的UG768手册。OSERDESE2 原语模型如下所示:
一个 OSERDESE2 只能实现最多 8:1 的转换率,在这里我们通过位宽扩展实现了 10:1 的并串转换,如下图所示:
OSERDESE2原语的文档中介绍有:该原语可以进行8:1、10:1、14:1转换,当进行8:1转换的时候,采用一个原语即可,当采用10:1或者14:1转换的时候,需要采用两个原语级联的方式。我们需要将 10bit 数据进行串行化,因此根据文档说明,我们需要使用两个OSERDESE2原语进行级联,级联后的框图如下图所示。OSERDESE2 原语的 Master 端承接 10bit 数据的低 8 位,OSERDESE2 原语的 Slave 端的 D1、D2 不使用,D3 和 D4 承接10bit数据的高 2bit,(若是14:1则可以继续使用D5,D6,D7,D8,依然不使用D1,D2)。并将 Slave 的输出 SHIFTOUT1, SHIFTOUT2 连接到 master 的 SHIFTIN1, SHIFTIN2,输入的 10bit 数据将会按照由低位到高位的顺序从 DataOut 端输出。
OSERDESE2原语获取方法如下所示:
整个系统需要两个输入时钟,一个是视频的像素时钟 div_clk,另外一个时钟 ser_clk 的频率是像素时钟的五倍。由前面的简介部分我们知道,并串转换过程的实现的是 10:1 的转换率,理论上转换器需要一个 10 倍像素频率的时钟。这里我们只需要一个 5 倍的时钟频率,这是因为 OSERDESE2 模块可以实现 DDR 的功能,即它在五倍时钟频率的基础上又实现了双倍数据速率。
注意,OSERDESE2 模块要求复位信号高电平有效,并且需要将异步复位信号同步到串行时钟域,因此还要加入一个异步复位信号处理代码,完整的 OSERDESE2 模块的调用代码如下所示:
//==========================================================================
//== 时钟同步
//==========================================================================
always @(posedge divclk or posedge rst) begin
if (rst) begin
ini_rst <= 1'b1;
end
else begin
ini_rst <= 1'b0;
end
end
//==========================================================================
//== OSERDESE2 master 原语例化1:0-7
//==========================================================================
OSERDESE2
#(
.DATA_RATE_OQ ("DDR" ), // 最终输出的数据类型:DDR,SDR
.DATA_RATE_TQ ("SDR" ), // 输出的buffer:DDR, BUF, SDR
.DATA_WIDTH (10 ), // Parallel data width (2-8,10,14),数据位宽
.INIT_OQ (1'b0 ), // Initial value of OQ output (1'b0,1'b1)
.INIT_TQ (1'b0 ), // Initial value of TQ output (1'b0,1'b1)
.SERDES_MODE ("MASTER" ), // MASTER, SLAVE,主机模式
.SRVAL_OQ (1'b0 ), // OQ output value when SR is used (1'b0,1'b1)
.SRVAL_TQ (1'b0 ), // TQ output value when SR is used (1'b0,1'b1)
.TBYTE_CTL ("FALSE" ), // Enable tristate byte operation (FALSE, TRUE),三态控制
.TBYTE_SRC ("FALSE" ), // Tristate byte source (FALSE, TRUE),三态控制
.TRISTATE_WIDTH (1 ) // 3-state converter width (1,4)
)
OSERDESE2_inst_master
(
.OFB ( ), // 1-bit output: Feedback path for data,未用到,删除即关闭
.OQ (do ), // 1-bit output: Data path output 输出路径
// SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each)
.SHIFTOUT1 ( ), // 未用到,删除即关闭
.SHIFTOUT2 ( ), // 未用到,删除即关闭
.TBYTEOUT ( ), // 1-bit output: Byte group tristate,未用到,删除即关闭
.TFB ( ), // 1-bit output: 3-state control,未用到,删除即关闭
.TQ ( ), // 1-bit output: 3-state control,未用到,删除即关闭
.CLK (serclk ), // 1-bit input: High speed clock
.CLKDIV (divclk ), // 1-bit input: Divided clock
// D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each),数据
.D1 (din[0] ),
.D2 (din[1] ),
.D3 (din[2] ),
.D4 (din[3] ),
.D5 (din[4] ),
.D6 (din[5] ),
.D7 (din[6] ),
.D8 (din[7] ),
.OCE (1'b1 ), // 1-bit input: Output data clock enable
.RST (ini_rst ), // 1-bit input: Reset,高电平有效的同步时钟
// SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each)
.SHIFTIN1 (cascade_di1 ), // 连接到SLAVE上
.SHIFTIN2 (cascade_di2 ), // 连接到SLAVE上
// T1 - T4: 1-bit (each) input: Parallel 3-state inputs
.T1 (1'b0 ),
.T2 (1'b0 ),
.T3 (1'b0 ),
.T4 (1'b0 ),
.TBYTEIN (1'b0 ), // 1-bit input: Byte group tristate
.TCE (1'b0 ) // 1-bit input: 3-state clock enable
);
//==========================================================================
//== OSERDESE2 slave 原语例化:8-9
//==========================================================================
OSERDESE2
#(
.DATA_RATE_OQ ("DDR" ), // DDR, SDR
.DATA_RATE_TQ ("SDR" ), // DDR, BUF, SDR
.DATA_WIDTH (10 ), // Parallel data width (2-8,10,14)
.INIT_OQ (1'b0 ), // Initial value of OQ output (1'b0,1'b1)
.INIT_TQ (1'b0 ), // Initial value of TQ output (1'b0,1'b1)
.SERDES_MODE ("SLAVE" ), // MASTER, SLAVE
.SRVAL_OQ (1'b0 ), // OQ output value when SR is used (1'b0,1'b1)
.SRVAL_TQ (1'b0 ), // TQ output value when SR is used (1'b0,1'b1)
.TBYTE_CTL ("FALSE" ), // Enable tristate byte operation (FALSE, TRUE)
.TBYTE_SRC ("FALSE" ), // Tristate byte source (FALSE, TRUE)
.TRISTATE_WIDTH (1 ) // 3-state converter width (1,4)
)
OSERDESE2_inst_slave
(
.OFB ( ), // 1-bit output: Feedback path for data
.OQ ( ), // 1-bit output: Data path output,主机输出,从机关闭
// SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each)
.SHIFTOUT1 (cascade_di1 ),
.SHIFTOUT2 (cascade_di2 ),
.TBYTEOUT ( ), // 1-bit output: Byte group tristate
.TFB ( ), // 1-bit output: 3-state control
.TQ ( ), // 1-bit output: 3-state control
.CLK (serclk ), // 1-bit input: High speed clock
.CLKDIV (divclk ), // 1-bit input: Divided clock
// D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
.D1 ( ), //为什么要空?看框图
.D2 ( ), //为什么要空?看框图
.D3 (din[8] ),
.D4 (din[9] ),
.D5 (1'b0 ),
.D6 (1'b0 ),
.D7 (1'b0 ),
.D8 (1'b0 ),
.OCE (1'b1 ), // 1-bit input: Output data clock enable
.RST (ini_rst ), // 1-bit input: Reset
// SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each)
.SHIFTIN1 ( ),
.SHIFTIN2 ( ),
// T1 - T4: 1-bit (each) input: Parallel 3-state inputs
.T1 (1'b0 ),
.T2 (1'b0 ),
.T3 (1'b0 ),
.T4 (1'b0 ),
.TBYTEIN (1'b0 ), // 1-bit input: Byte group tristate
.TCE (1'b0 ) // 1-bit input: 3-state clock enable
);
3、OBUFDS:差分输出
OBUFDS原语用于将三路数据和一路时钟信号转换成差分信号输出,OBUFDS 是差分输出缓冲器,用于将来自 FPGA 内部逻辑的信号转换成差分信号输出,支持 TMDS 电平标准。OBUFDS 原语示意图如下所示:
OBUFDS获取途径如下所示:
为了程序简单化,可以将此模块也放入上面 Serializer10_1 模块的 OSERDESE2 调用后面,如下所示:
//==========================================================================
//== OBUFDS 原语例化,将串行数据转为一对差分信号
//==========================================================================
OBUFDS
#(
.IOSTANDARD ("DEFAULT" ), // Specify the output I/O standard
.SLEW ("SLOW" ) // Specify the output slew rate
)
OBUFDS_inst
(
.O (do_p ), // Diff_p output (connect directly to top-level port)
.OB (do_n ), // Diff_n output (connect directly to top-level port)
.I (do ) // Buffer input
);
4、HDMI_trans:模块连接
创建一个 HDMI_trans 模块,将上述三个模块连接到一起,有几点需要说明:
(1)u_encode_blue 的例化中的 c0 和 c1 包括了 HSYNC 和 VSYNC,而 red 和 blue 中给的是 0,原因见上文。
(2)Serializer10_1 里包含了 OSERDESE2 原语和 OBUFDS 原语。
(3)上面说过,整个系统需要两个输入时钟,一个是视频的像素时钟 HDMI_clk1x(div_clk) ,另外一个时钟 HDMI_clk5x(ser_clk)的频率是像素时钟的五倍。TMDS 连接的时钟通道我们采用与数据通道相同的并转串逻辑来实现。通过对 10 位二进制序列 10’b11111_00000 在 10 倍像素时钟频率下进行并串转换,就可以得到像素时钟频率下的 TMDS 参考时钟 ser_clk。
//**************************************************************************
// *** 名称 : HDMI_trans.v
// *** 作者 : 木子
// *** 日期 : 2024-09-12
// *** 描述 :
//**************************************************************************
module HDMI_trans
//========================< 端口 >==========================================
(
input wire HDMI_clk1x ,
input wire HDMI_clk5x ,
input wire HDMI_rst ,
//VGA -----------------------------------------------
input wire VGA_de ,
input wire VGA_vsync ,
input wire VGA_hsync ,
input wire [7:0] VGA_r ,
input wire [7:0] VGA_g ,
input wire [7:0] VGA_b ,
//HDMI ----------------------------------------------
output wire HDMI_clk_p ,
output wire HDMI_clk_n ,
output wire HDMI_chn0_p ,
output wire HDMI_chn0_n ,
output wire HDMI_chn1_p ,
output wire HDMI_chn1_n ,
output wire HDMI_chn2_p ,
output wire HDMI_chn2_n
);
//========================< 信号 >==========================================
wire [9:0] dout_r ; //红
wire [9:0] dout_g ; //绿
wire [9:0] dout_b ; //蓝
//==========================================================================
//== encode 例化,RGB三个通道进行8B转10B转换
//==========================================================================
encode u_encode_red
(
.clkin (HDMI_clk1x ),
.rstin (HDMI_rst ),
.din (VGA_r ),
.c0 (1'b0 ),
.c1 (1'b0 ),
.de (VGA_de ),
.dout (dout_r )
);
encode u_encode_green
(
.clkin (HDMI_clk1x ),
.rstin (HDMI_rst ),
.din (VGA_g ),
.c0 (1'b0 ),
.c1 (1'b0 ),
.de (VGA_de ),
.dout (dout_g )
);
encode u_encode_blue
(
.clkin (HDMI_clk1x ),
.rstin (HDMI_rst ),
.din (VGA_b ),
.c0 (VGA_hsync ),
.c1 (VGA_vsync ),
.de (VGA_de ),
.dout (dout_b )
);
//==========================================================================
//== 并转串模块例化:时钟,给固定值
//==========================================================================
Serializer10_1 u_Serializer_clk
(
.divclk (HDMI_clk1x ),
.serclk (HDMI_clk5x ),
.rst (HDMI_rst ),
.din (10'b11111_00000 ), //固定值
.do_p (HDMI_clk_p ),
.do_n (HDMI_clk_n )
);
//==========================================================================
//== 并转串模块例化:数据
//==========================================================================
Serializer10_1 u_Serializer_red
(
.divclk (HDMI_clk1x ),
.serclk (HDMI_clk5x ),
.rst (HDMI_rst ),
.din (dout_r ),
.do_p (HDMI_chn2_p ),
.do_n (HDMI_chn2_n )
);
Serializer10_1 u_Serializer_green
(
.divclk (HDMI_clk1x ),
.serclk (HDMI_clk5x ),
.rst (HDMI_rst ),
.din (dout_g ),
.do_p (HDMI_chn1_p ),
.do_n (HDMI_chn1_n )
);
Serializer10_1 u_Serializer_blue
(
.divclk (HDMI_clk1x ),
.serclk (HDMI_clk5x ),
.rst (HDMI_rst ),
.din (dout_b ),
.do_p (HDMI_chn0_p ),
.do_n (HDMI_chn0_n )
);
endmodule
5、HDMI_top:模块完整封装
可以看出 HDMI 模块还是比较复杂的,因此我们添加 VGA 时序模块,和 HDMI_trans 模块一起封装成 HDMI_top 模块,以后可以直接把这个模块拿来用。HDMI_top 的框图如下所示:
HDMI_top 模块代码如下所示:
`timescale 1ns / 1ps
//**************************************************************************
// *** 名称 : HDMI_top.v
// *** 作者 : 木子
// *** 日期 : 2024-09-12
// *** 描述 : HDMI顶层文件
//**************************************************************************
module HDMI_top
//========================< 端口 >==========================================
(
//system --------------------------------------------
input wire HDMI_clk1x ,
input wire HDMI_clk5x ,
input wire rst ,
//HDMI in -------------------------------------------
output wire HDMI_req ,
input wire [15:0] HDMI_data ,
//HDMI out ------------------------------------------
output wire HDMI_clk_p ,
output wire HDMI_clk_n ,
output wire HDMI_d0_p ,
output wire HDMI_d0_n ,
output wire HDMI_d1_p ,
output wire HDMI_d1_n ,
output wire HDMI_d2_p ,
output wire HDMI_d2_n
);
//========================< 连线 >==========================================
wire VGA_req ;
wire [15:0] VGA_data ;
wire VGA_de ;
wire VGA_vsync ;
wire VGA_hsync ;
wire [ 7:0] VGA_r ;
wire [ 7:0] VGA_g ;
wire [ 7:0] VGA_b ;
//==========================================================================
//== VGA控制器
//==========================================================================
VGA_driver u_VGA_driver
(
.clk (HDMI_clk1x ),
.rst (rst ),
.VGA_data (HDMI_data ),
.VGA_req (HDMI_req ),
.VGA_de (VGA_de ),
.VGA_vsync (VGA_vsync ),
.VGA_hsync (VGA_hsync ),
.VGA_r (VGA_r ),
.VGA_g (VGA_g ),
.VGA_b (VGA_b )
);
//==========================================================================
//== HDMI转换
//==========================================================================
HDMI_trans u_HDMI_trans
(
.HDMI_clk1x (HDMI_clk1x ),
.HDMI_clk5x (HDMI_clk5x ),
.HDMI_rst (rst ),
//-----------------------------------------------
.VGA_de (VGA_de ),
.VGA_vsync (VGA_vsync ),
.VGA_hsync (VGA_hsync ),
.VGA_r (VGA_r ),
.VGA_g (VGA_g ),
.VGA_b (VGA_b ),
//-----------------------------------------------
.HDMI_clk_p (HDMI_clk_p ),
.HDMI_clk_n (HDMI_clk_n ),
.HDMI_chn0_p (HDMI_d0_p ),
.HDMI_chn0_n (HDMI_d0_n ),
.HDMI_chn1_p (HDMI_d1_p ),
.HDMI_chn1_n (HDMI_d1_n ),
.HDMI_chn2_p (HDMI_d2_p ),
.HDMI_chn2_n (HDMI_d2_n )
);
endmodule