HDMI接口的全称是高清多媒体接口(High Definition Multimedia Interface),最初在2004年出现,能够同时传输视频与音频信号,而且质量很高。比起VGA的模拟信号传输,HDMI传输数字信号可提供质量更好的图像信号;比起DVI仅传输视频信号,HDMI可以传输除视频信号之外的音频信号、字幕信号或其他的控制信号。目前的常用HDMI接口有HDMI1.4、2.0和2.1,分别支持最高2k60hz、4K60hz和4K120hz(8K60hz)。
使用FPGA实现HDMI接口可以采用芯片实现,如ADV7513,CH7301,采用这种方案只需要将RGB信号接入到芯片就可以,芯片自动完成编码和电平转换等工作,有一个缺陷是占用的管脚过多(大概需要30根左右)。从Xilinx 在 Spartan-3A 系列之后的器件中,加入了对TMDS接口标准的支持,用于在FPGA内部实现DVI和HDMI接口,使得仅需9根线即可完成HDMI的最简应用,由此TMDS编码的工作自然需要FPGA电路实现了。需要注意的一点是,当作为输出设备时,应通过HDMI接口输出5V电源;作为输入设备时,接口上的5V电源将由外部连接设备提供,同时,HDMI_HPD 将输出高电平,用于指示外部设备接入时HDMI连接的状态。
标准的HDMI接口有以下四种,分别称为ABCD四种接口,如图所示,也称作standard HDMI、Mini HDMI、Micro HDMI。
常见的A型接口有19根线,如图所示,1-9九根线是三个数据差分对加一个数据屏蔽线,10-12是时钟差分线和时钟屏蔽线。13是CEC管脚,指的是用户电气控制(Consumer Electronics Control),它用于 HDMI 连接线上的设备之间进行信息交换。当一个设备的状态发生变化时,CEC可以使用远程控制或自动改变设置来命令连接的关联设备的状态发生相应的变化。19是HPD管脚,指的是热拔插检测(Hot Plug Detect),当视频设备与接收设备通过 HDMI 连接时,接收设备将 HPD 置为高电平,通知发送设备。当发送设备检测到HPD为低电平时,表明断开连接。18是5V电压管脚,作为输出设备时,将为接收设备提供5V电压。15 16是DDC管脚即HDMI接口的显示数据通道(DDC,Display Data Channel),用于HDMI发送端和接收端之间交换一些配置信息,通过I2C协议通信,一根是SDA线一根是SCL线。发送端通过DDC通道,读取接收端保存在EEPROM中的EDID数据(EDID有其特定的规范格式),获取接收端的信息,确认接收端终端显示的设置和功能,决定跟接收端之间以什么格式传输音/视频数据。
使用FPGA驱动HDMI设备,构建一个场景是,用显示屏通过HDMI连接线与带有HDMI接口FPGA板卡相连,使显示屏以1280*720@60Hz显示一个彩条,彩条的顺序是绿蓝红黑白。该应用可用作新制板卡的接口测试,也可以换做其他的数据源做摄像头类应用,也可以换做其他格式的分辨率。
从“简介”中可以看到,驱动HDMI接口就是与HDMI相连的FPGA管脚输出要求的数据即可。主要是通过三对差分数据线,和一对时钟差分线。将FPGA驱动HDMI显示彩条分为以下几个步骤,即①准备源数据,即按照VESA规定的标准视频时序参数,生成行场同步信号、数据使能信号以及彩条的RGB像素数据;②根据数据使能信号为像素数据和行场同步信号编码,编码规则为TMDS编码(具体随后介绍);③将编码后的数据进行10:1的串并转换,随后进行单端转差分的操作,即可发送到数据线上,并在对端相连的屏幕上看到彩色条纹的显示。关于什么是TMDS编码?为啥要做10:1的串并转换,将在下一小节结合代码做阐述。
其工程的代码结构如上图所示,Top模块完成时钟管理模块、v_fer、v_driver三个子模块的例化。代码如下。时钟管理模块是通过clk ip核完成的,板卡上的晶振输入为50MHz,生成74.25Mhz(绿色),和371.25Mhz(蓝色),前者用于像素时钟,这是由标准的时序参数决定的,不同分辨率和帧率对应不同的像素时钟,可以在http://tinyvga.com/vga-timing查询,时序参数包括了像素时钟和行显示后沿,显示有效时间等参数。后者用于串并转换,这是由于10:1的串并转换关系加上使用DDR模式决定该时钟是五倍于像素时钟。
module hdmi_top(
input sys_clk,
output tmds_clk_p, // TMDS 时钟通道
output tmds_clk_n,
output [2:0] tmds_data_p, // TMDS 数据通道
output [2:0] tmds_data_n
// output tmds_oen // TMDS 输出使能(用于HDMI输入输出选择)
);
wire pix_clk;
wire sys_rst_n;
clk_wiz_0 u0_clk
(
// Clock out ports
.clk_out1(pix_clk), // output clk_out1
.clk_out2(pix_5clk), // output clk_out2
.reset(1'b0), // input reset
.locked(sys_rst_n), // output locked
.clk_in1(sys_clk)); // input clk_in1
wire video_hs;
wire video_vs;
wire video_de;
wire[23:0]video_rgb;
//根据标准视频1280*720@60Hz时序参数, 产生行场同步信号
v_driver u1_driver(
.pix_clk (pix_clk),
.sys_rst_n (sys_rst_n),
.video_hs (video_hs),
.video_vs (video_vs),
.video_de (video_de),
.video_rgb (video_rgb)
);
v_xfer u2_xfer(
.pix_clk (pix_clk),
.pix_5clk (pix_5clk),
.sys_rst_n (sys_rst_n),
.video_rgb (video_rgb),
.video_hs (video_hs),
.video_vs (video_vs),
.video_de (video_de),
.tmds_clk_p (tmds_clk_p),
.tmds_clk_n (tmds_clk_n),
.tmds_data_p (tmds_data_p),
.tmds_data_n (tmds_data_n)
// .tmds_oen (tmds_oen)
);
endmodule
v_driver模块用来产生彩条数据及按钮标准视频时序参数产生行场同步信号和数据使能信号,使用parameter定义各个视频显示时序参数,可以在显示其他分辨率时仅修改此处定义。该模块逻辑简单清晰,由像素时钟上升沿触发,计h_cnt和v_cnt两个数,分别由这两个数和时序参数比较得到行场同步信号video_hs和video_vs,视频显示使能信号video_de,以及当前扫描坐标pos_x和pos_y。根据当前扫描坐标将彩条的RGB数据赋值给像素数据pix_data,传出模块做下一步处理。
module v_driver(
input pix_clk,
input sys_rst_n,
output video_hs,
output video_vs,
output video_de,
output [23:0]video_rgb
// output [10:0] pos_x,
// output [10:0] pos_y
);
//标准时序参数
parameter HH_SYNC = 11'd40; //行同步
parameter HH_BACK = 11'd220; //行显示后沿,靠近行同步
parameter HH_VALID= 11'd1280;//行有效数据
parameter HH_FRONT= 11'd110; //行显示前沿
parameter HH_TOTAL= 11'd1650;//行扫描周期
parameter VV_SYNC = 11'd5; //场同步
parameter VV_BACK = 11'd20; //场显示后沿
parameter VV_VALID= 11'd720; //场有效数据
parameter VV_FRONT= 11'd5; //场显示前沿
parameter VV_TOTAL= 11'd750; //场扫描周期
//颜色数据
localparam WHITE = 24'b11111111_11111111_11111111; //RGB888 白色
localparam BLACK = 24'b00000000_00000000_00000000; //RGB888 黑色
localparam RED = 24'b11111111_00001100_00000000; //RGB888 红色
localparam GREEN = 24'b00000000_11111111_00000000; //RGB888 绿色
localparam BLUE = 24'b00000000_00000000_11111111; //RGB888 蓝色
reg [10:0]h_cnt;
reg [10:0]v_cnt;
always @(posedge pix_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
h_cnt <= 1'd0;
end else if(h_cnt < HH_TOTAL - 1'd1)begin
h_cnt <= h_cnt + 11'd1;
end else
h_cnt <= 11'd0;
end
always @(posedge pix_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
v_cnt <= 1'd0;
end else if(h_cnt == HH_TOTAL - 1'd1)begin
if(v_cnt < VV_TOTAL - 1'd1)
v_cnt <= v_cnt + 1'd1;
else
v_cnt <= 11'd0;
end else
v_cnt <= v_cnt;
end
//产生行场同步信号
assign video_hs= (h_cnt < HH_SYNC) ? 1'b0 : 1'b1;
assign video_vs= (v_cnt < VV_SYNC) ? 1'b0 : 1'b1;
//产生HDMI数据使能信号
assign video_de= (h_cnt>=HH_SYNC + HH_BACK)&&(h_cnt=VV_SYNC + VV_BACK)&&(v_cnt=0)&&(pos_x < (HH_VALID/5)*1)) //把屏幕分成5份,这是第一份
pix_data = GREEN;
else if((pos_x >= (HH_VALID/5)*1)&&pos_x < (HH_VALID/5)*2)
pix_data = BLUE;
else if((pos_x >= (HH_VALID/5)*2)&&pos_x < (HH_VALID/5)*3)
pix_data = RED;
else if((pos_x >= (HH_VALID/5)*3)&&pos_x < (HH_VALID/5)*4)
pix_data = BLACK;
else
pix_data = WHITE;
end
ila_0 my_monitor (
.clk(pix_clk), // input wire clk
.probe0(video_rgb[23:0]), // input wire [23:0] probe0
.probe1({video_de,video_hs,video_vs}), // input wire [2:0] probe1
.probe2({pos_x,pos_y}) // input wire [21:0] probe2
);
endmodule
v_xfer模块由top模块例化,接收v_driver模块产生的像素数据和行场同步信号,通过对v_encoder、serializer_10_to_1模块以及OBUFDS原语的例化,完成数据的编码、串并转换、单端转差分,最终将数据发送到管脚,完成HDMI彩条显示,代码如下。
module v_xfer(
input pix_clk,
input pix_5clk,
input sys_rst_n,
input[23:0]video_rgb,
input video_hs,
input video_vs,
input video_de,
output tmds_clk_p,
output tmds_clk_n,
output[2:0]tmds_data_p,
output[2:0]tmds_data_n
);
//对三个颜色通道进行编码
//并行数据
wire [9:0] red_10bit;
wire [9:0] green_10bit;
wire [9:0] blue_10bit;
wire [9:0] clk_10bit;
v_encoder encoder_b (
.clk_in (pix_clk),
.reset_n (sys_rst_n),
.din (video_rgb[7:0]),
.tmds_c0 (video_hs),
.tmds_c1 (video_vs),
.tmds_de (video_de),
.dout (blue_10bit)
) ;
v_encoder encoder_g (
.clk_in (pix_clk),
.reset_n (sys_rst_n),
.din (video_rgb[15:8]),
.tmds_c0 (1'b0),
.tmds_c1 (1'b0),
.tmds_de (video_de),
.dout (green_10bit)
) ;
v_encoder encoder_r (
.clk_in (pix_clk),
.reset_n (sys_rst_n),
.din (video_rgb[23:16]),
.tmds_c0 (1'b0),
.tmds_c1 (1'b0),
.tmds_de (video_de),
.dout (red_10bit)
) ;
wire [2:0] tmds_data_serial;
wire tmds_clk_serial;
assign clk_10bit = 10'b1111100000;
// assign clk_10bit = 10'b1010101010;
serializer_10_to_1 serializer_b(
.reset (!sys_rst_n), // 复位,高有效
.paralell_clk (pix_clk), // 输入并行数据时钟
.serial_clk_5x (pix_5clk), // 输入串行数据时钟
.paralell_data (blue_10bit), // 输入并行数据
.serial_data_out (tmds_data_serial[0]) // 输出串行数据
);
serializer_10_to_1 serializer_g(
.reset (!sys_rst_n),
.paralell_clk (pix_clk),
.serial_clk_5x (pix_5clk),
.paralell_data (green_10bit),
.serial_data_out (tmds_data_serial[1])
);
serializer_10_to_1 serializer_r(
.reset (!sys_rst_n),
.paralell_clk (pix_clk),
.serial_clk_5x (pix_5clk),
.paralell_data (red_10bit),
.serial_data_out (tmds_data_serial[2])
);
serializer_10_to_1 serializer_clk(
.reset (!sys_rst_n),
.paralell_clk (pix_clk),
.serial_clk_5x (pix_5clk),
.paralell_data (clk_10bit),
.serial_data_out (tmds_clk_serial)
);
//转换差分信号
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
) TMDS0 (
.I (tmds_data_serial[0]),
.O (tmds_data_p[0]),
.OB (tmds_data_n[0])
);
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
) TMDS1 (
.I (tmds_data_serial[1]),
.O (tmds_data_p[1]),
.OB (tmds_data_n[1])
);
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
) TMDS2 (
.I (tmds_data_serial[2]),
.O (tmds_data_p[2]),
.OB (tmds_data_n[2])
);
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
) TMDS3 (
.I (tmds_clk_serial),
.O (tmds_clk_p),
.OB (tmds_clk_n)
);
endmodule
v_encoder模块由v_xfer模块例化,理清这段代码的逻辑,先要对TMDS编码规则有了解。TMDS 编码包含两个大的内容,传输控制信号的控制段和用来传输图像像素数据的数据段。一个完整的图像传输 TMDS 模块包含三个相同的编码和发送模块。每个发送模块包含 8 位的像素数据,对应 24 位像素数据中单个颜色的 8 位数据。2 个控制信号,这两个控制信号可以分别接行同步(HSYNC)、场同步(VSYNC)信号,也可以空置接 0。另外还有一个数据有效信号 DE(video_de信号),该信号用来区分控制数据和像素数据。当 DE 信号为高电平的时候,表明当前数据有效,编码器对 8 位的 Data 数据进行编码。当 DE 信号为低电平的时候,则对 2 位的控制信号进行编码。
TMDS(Transition-minimized differential signaling)最小化传输差分信号,所谓最小化传输,其本质就是要通过对输入数据的变换,得到一个跳变次数最少的新数据。这样做的好处有两点,第一,信号跳变过程中就必然会出现信号线高低电平的变化,信号的变化就会产生磁场。对信号线产生电磁干扰。所以,越少的信号翻转次数,就会产生越小的电磁干扰。第二,TMDS 数据通道传送的是一个连续的10bit TMDS字符流,在行场消隐期间,传送4个有显著特征的字符,它们直接对应编码器的2个控制信号的4个可能的状态。在数据有效期间,10bit的字符包含8bit的像素数据,编码的字符提供近似的DC平衡,所谓直流平衡,就是指信号在传输中0和1的数据个数相同,则发送方和接收方之间就不会有直流电流的传递,在通信系统中,直流平衡可以有效避免由于收发两端电压不稳引起的问题。
实现TMDS编码按照一定的算法即可。如下图所示,为编码器的示意图。三个相同的编码器,均为输入8bit像素数据,一个数据使能信号DE,以及两个控制数据。对于行场消隐期间(即DE信号为低电平时)传输的传输的控制信号(含行场同步信号、配音信号、字幕信息或其他控制信号)的编码规则为,因为只有两位,因此对应四种可能,“00”对应10’b110101;0100;“01”对应10’b0010101011;“10”对应10’b0101010100;“11”对应10’b1010101011。
对于DE信号为高电平时,传输的像素数据的编码规则,在 Xilinx 应用笔记 XAPP460 中会提供编码模块,或者在网上资源很多的,下面我简单概括和描述一下。TMDS 通过逻辑算法将 8 位字符数据通过最小转换编码为 10 位字符数据。首先,计算8bit像素数据中1的个数,并根据像素数据中1的个数做出编码算法的第一次选择(异或或同或,第一个数保留,从第二个数开始,与前一位数同或或异或得出当前位数的值),同时据此得出bit9的值。然后计算得出当前9bit中的1的个数n1q_m和0的个数n0q_m以及上一次的状态值cnt,根据此做第二次算法选择,得出bit[9:0]的值,完成编码,输出编码后的数据dout。
module v_encoder(
input clk_in,
input reset_n,
input [7:0]din,
input tmds_c0,//控制数据1
input tmds_c1,//控制数据2
input tmds_de,//图像数据和控制数据指示信号
output reg[9:0]dout//编码后数据
);
//步骤1:计算像素数据中1的个数
reg [3:0] n1_cnt; //number of 1s in din
reg [7:0] din_q;
always @ (posedge clk_in) begin
n1_cnt <=#1 din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
din_q <=#1 din;
end
//步骤2:根据像素数据中1的个数做出编码方向判断
wire d1_direct;
assign d1_direct = (n1_cnt >4'd4) | ((n1_cnt == 4'h4) & (din_q[0] == 1'b0));
//步骤3: 根据已经做出的决断,计算bit9
wire [8:0]q_m;
assign q_m[0] = din_q[0];
assign q_m[1] = (d1_direct) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
assign q_m[2] = (d1_direct) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
assign q_m[3] = (d1_direct) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
assign q_m[4] = (d1_direct) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
assign q_m[5] = (d1_direct) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
assign q_m[6] = (d1_direct) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
assign q_m[7] = (d1_direct) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);
assign q_m[8] = (d1_direct) ? 1'b0 : 1'b1;
//步骤4: 计算9bit中的一的个数和0的个数
reg [3:0] n1q_m, n0q_m; // number of 1s and 0s for q_m
always @ (posedge clk_in) begin
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];
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]);
end
//步骤5:根据bit9中的01个数以及上一次的状态cnt标识,提供编码方向
reg [4:0] cnt; //disparity counter, MSB is the sign bit
wire d2_direct,d3_direct;
assign d2_direct = (cnt == 5'h0) | (n1q_m == n0q_m);//cnt可以理解为上一次的状态 01个数相等
//步骤6:zuochu bit10的决断
assign d3_direct = (~cnt[4] & (n1q_m > n0q_m)) | (cnt[4] & (n0q_m > n1q_m));
reg de_q, de_reg;
reg c0_q, c1_q;
reg c0_reg, c1_reg;
reg [8:0] q_m_reg;
always @ (posedge clk_in) begin//所有信号全部同步
de_q <=#1 tmds_de;
de_reg <=#1 de_q;
c0_q <=#1 tmds_c0;
c0_reg <=#1 c0_q;
c1_q <=#1 tmds_c1;
c1_reg <=#1 c1_q;
q_m_reg <=#1 q_m;
end
parameter CTRLTOKEN0 = 10'b1101010100;
parameter CTRLTOKEN1 = 10'b0010101011;
parameter CTRLTOKEN2 = 10'b0101010100;
parameter CTRLTOKEN3 = 10'b1010101011;
//10bit数据输出
always @ (posedge clk_in or negedge reset_n) begin
if(!reset_n) begin
dout <= 10'h0;
cnt <= 5'h0;
end else begin
if (de_reg) begin//de信号为高指示像素数据
if(d2_direct) begin
dout[9] <=#1 ~q_m_reg[8];
dout[8] <=#1 q_m_reg[8];
dout[7:0] <=#1 (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(d3_direct) begin
dout[9] <=#1 1'b1;
dout[8] <=#1 q_m_reg[8];
dout[7:0] <=#1 ~q_m_reg[7:0];
cnt <=#1 cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);
end else begin
dout[9] <=#1 1'b0;
dout[8] <=#1 q_m_reg[8];
dout[7:0] <=#1 q_m_reg[7:0];
cnt <=#1 cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);
end
end
end else begin//de为低指示控制数据
case ({c1_reg, c0_reg})
2'b00: dout <=#1 CTRLTOKEN0;
2'b01: dout <=#1 CTRLTOKEN1;
2'b10: dout <=#1 CTRLTOKEN2;
default: dout <=#1 CTRLTOKEN3;
endcase
cnt <=#1 5'h0;
end
end
end
endmodule
Serializer_10_to_1模块用于进行10:1串并转换,由于一个OSERDESE2原语最多只能实现8:1串并转化,因此需要两个原语级联实现10:1的串并转换,采用DDR模式则可以选择5倍于像素时钟的串行时钟即可。
module serializer_10_to_1(
input reset, // 复位,高有效
input paralell_clk, // 输入并行数据时钟
input serial_clk_5x, // 输入串行数据时钟
input [9:0] paralell_data, // 输入并行数据
output serial_data_out // 输出串行数据
);
//wire define
wire cascade1; //用于两个OSERDESE2级联的信号
wire cascade2;
//例化OSERDESE2原语,实现并串转换,Master模式
OSERDESE2 #(
.DATA_RATE_OQ ("DDR"), // 设置双倍数据速率
.DATA_RATE_TQ ("SDR"), // DDR, BUF, SDR
.DATA_WIDTH (10), // 输入的并行数据宽度为10bit
.SERDES_MODE ("MASTER"), // 设置为Master,用于10bit宽度扩展
.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_Master (
.CLK (serial_clk_5x), // 串行数据时钟,5倍时钟频率
.CLKDIV (paralell_clk), // 并行数据时钟
.RST (reset), // 1-bit input: Reset
.OCE (1'b1), // 1-bit input: Output data clock enable
.OQ (serial_data_out), // 串行输出数据
.D1 (paralell_data[0]), // D1 - D8: 并行数据输入
.D2 (paralell_data[1]),
.D3 (paralell_data[2]),
.D4 (paralell_data[3]),
.D5 (paralell_data[4]),
.D6 (paralell_data[5]),
.D7 (paralell_data[6]),
.D8 (paralell_data[7]),
.SHIFTIN1 (cascade1), // SHIFTIN1 用于位宽扩展
.SHIFTIN2 (cascade2), // SHIFTIN2
.SHIFTOUT1 (),
.OFB (), // 以下是未使用信号
.T1 (1'b0),
.T2 (1'b0),
.T3 (1'b0),
.T4 (1'b0),
.TBYTEIN (1'b0),
.TCE (1'b0),
.TBYTEOUT (),
.TFB (),
.TQ ()
);
//例化OSERDESE2原语,实现并串转换,Slave模式
OSERDESE2 #(
.DATA_RATE_OQ ("DDR"), // 设置双倍数据速率
.DATA_RATE_TQ ("SDR"), // DDR, BUF, SDR
.DATA_WIDTH (10), // 输入的并行数据宽度为10bit
.SERDES_MODE ("SLAVE"), // 设置为Slave,用于10bit宽度扩展
.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_Slave (
.CLK (serial_clk_5x), // 串行数据时钟,5倍时钟频率
.CLKDIV (paralell_clk), // 并行数据时钟
.RST (reset), // 1-bit input: Reset
.OCE (1'b1), // 1-bit input: Output data clock enable
.OQ (), // 串行输出数据
.D1 (1'b0), // D1 - D8: 并行数据输入
.D2 (1'b0),
.D3 (paralell_data[8]),
.D4 (paralell_data[9]),
.D5 (1'b0),
.D6 (1'b0),
.D7 (1'b0),
.D8 (1'b0),
.SHIFTIN1 (), // SHIFTIN1 用于位宽扩展
.SHIFTIN2 (), // SHIFTIN2
.SHIFTOUT1 (cascade1), // SHIFTOUT1: 用于位宽扩展
.SHIFTOUT2 (cascade2), // SHIFTOUT2
.OFB (), // 以下是未使用信号
.T1 (1'b0),
.T2 (1'b0),
.T3 (1'b0),
.T4 (1'b0),
.TBYTEIN (1'b0),
.TCE (1'b0),
.TBYTEOUT (),
.TFB (),
.TQ ()
);
endmodule
然后数据在逻辑上返回到v_xfer模块,通过调用OBUFDS原语实现信号单端转差分的变化。这便是最终的差分信号。
①IO约束、OBUFDS原语调用是TMDS33电平标准;
②调试时请注意选择屏幕的信号源为HDMI;
③使用其他的数据源只需要把v_driver模块换成其他的数据源接入即可;
④在约束中差分信号仅对p端子约束即可;
⑤该程序没有使用音频或其他控制信号,是向下兼容DVI接口的;
⑥注意给HDMI接口的差分信号的时钟频率是像素时钟,而不是五倍频时钟。