1.TMDS编码
TMDS(Transition Minimized Differential Signaling),即最小化差分传输信号,在DVI(数字视频接口,只能传输视频)和HDMI(音视频均可传输)协议中用于传输音视频数据,使用差分信号传输高速串行数据。
TMDS连接从逻辑功能上可以划分成两个阶段:编码和并串转换。在编码阶段,编码器将视频源中的像素数据、HDMI的音频/附加数据,以及行同步和场同步信号分别编码成10位的字符流。然后在并串转换阶段将上述的字符流转换成串行数据流,并将其从三个差分输出通道发送出去。
HDMI系统架构由Source端和Sink端组成:Source是指发送HDMI信号的一侧,Sink是指接收HDMI信号的一侧。
HDMI信号四路差分信号组成,包括三路TMDS Data信号和一路TMDS Clock信号。TMDS信号不仅仅用于传输video信号,还传输audio和辅助信息。
HDMI提供一个DDC通道用于在source端和sink端交换状态。
HDP用于热插拔检测,CEC是用户电气控制,一般用作遥控,HEAC以太网和音频返回。CEC和HEAC是HDMI可选协议。
要支持热插拔,需要实现HPD
TMDS传输系统分为两个部分:发送端source和接收端sink。TMDS链路包括3个传输数据的通道和1个传输时钟信号的通道。发送端对这些数据进行编码和并/串转换,再将数据分别分配到独立的传输通道发送出去。接收端接收来自发送端的串行信号,对其进行解码和串/并转换,然后发送到显示器的控制端。
TMDS不仅仅是对video进行编码,还包括audio和控制信号:
- 对于video周期采用的是video data encoding,将8bits数据转换成10bits数据
- 对于control周期采用的是CTL encoding,将2bits数据转换成10bits数据
- 对于Data Island周期采用的是TERC4encoding,将4bits数据转换成10bits数据
HDMI传输由三组TMDS通道和一组TMDS clock通道组成,TMDS clock的运行频率是video信号的pixel频率,在每个TMDS时钟通道周期,每个TMDS data通道都发送10bit数据。
- channel0传输B数据和HSync、VSync
- channel1传输G数据和CTL0、CTL1
- channel2传输R数据和CTL2、CTL3
HDMI总体传输流程如下:
-
2.TMDS编码流程
TMDS编码流程如下图所示,其中使用的信号含义如下表所示。
首先D是需要进行编码的8比特原始像素数据,蓝色通道对应的是蓝色通道的8比特数据,在另外两个颜色通道中,对应的就是另外两个颜色通道的8比特数据。而C0、C1是两个控制信号,在蓝色通道中C0、C1是行、场同步信号。DE是像素有效使能信号。
cnt用来存储上一次编码过程中1的个数比0的个数多多少,在编码时会对每个8比特像素数据都进行编码。cnt就是用来寄存上一次编码当中1的个数比0的个数多多少。如果在上一次编码的10比特数据当中,如果0的个数太多了,那么在当前编码中将0的个数进行取反,适当增加1的个数。N1{X}、N0{X}是指待编码的视频像素数据中1的个数、0的个数,q_out就是最终TMDS编码后的10比特数据,给其他模块进行使用。
进行编码时,首先要判断输入的像素数据1的个数是否大于4或者输入像素等于4且最低位为0,即(N1{D}>4)|| (N1{D}==4 && D[0]==0)为真,则执行右边的运算,如果上述条件为假,则执行左边的运算。q_m是一个临时寄存器,用来寄存中间数据。右边的运算是对输入的像素数据进行同或运算(将输入数据最低位寄存在q_m最低位,然后q_m的低位与输入数据的下一位数据同或得到q_m当前位数据),并且第9比特q_m[8]赋值为0,左边的运算是对输入的像素数据进行异或运算,并且第9比特q_m[8]赋值为1。第9位用来表示TMDS对输入数据采用异或还是同或运算。然后判断DE信号是否拉高,如果DE信号没有拉高,接下来就要对控制信号C0、C1进行编码,通过查找表实现即可,总共四种情况。
并且需要把cnt(t)赋值为0的,因为由C0、C1编码的结果可知,q_out输出数据中1的个数和0的个数是相等的,控制信号一般会存在一行或者一帧数据的结尾,在对这种信号进行编码时,也表示一行或一帧数据传输结束,此时需要把cnt(t)清零,方便用于下一行数据的计数。
如果DE信号拉高,则继续对数据进行编码,如下图所示。
首先判断上次编码的10位数据中1的个数与0的个数是否相等,或本次编码的8为数据中1的个数与0的个数是否相等,若二者满足其一,即(cnt{t-1}==0) || (N1{q_m[0:7]}== N0{q_m[0:7]}) 为真,q_m[8]取反得到数据的第10位q_out[9],
第9位保持不变,而低8位则根据运算方式进行取值,若第一步是异或运算,则q_out[8]等于1,将q_m[0:7]作为编码结果的低8位;反之,若第一步是同或运算,则q_out[8]等于0,将q_m[0:7]取反作为编码结果的低8位。编码结束后还要计算本次编码的10位数据中1的个数比0的个数多多少。若q_m[8]等于0,即进行同或运算,q_m[0:7]8位数据中0的个数多余1的个数,将其进行取反输出则1的个数多余0的个数;若q_m[8]等于1,即进行异或运算,q_m[0:7]8位数据中1的个数多余0的个数。因此N0{q_m[0:7]}- N1{q_m[0:7]}表示本次编码输出数据q_out中1的个数比0的个数多多少。 (q_m[8]==1),N1{q_m[0:7]}- N0{q_m[0:7]}也表示本次编码输出数据q_out中1的个数比0的个数多多少。
由此也可得到cnt(t-1)其实就是表示一行已经编码数据中1的个数比0的个数多多少,而不是最开始定义的上一次编码数据中1的个数比0的个数多多少。
若(cnt{t-1}==0) || (N1{q_m[0:7]}== N0{q_m[0:7]}) 为假,则代表前一次编码中1的个数与0的个数并不相等。执行FALSE部分,若一行编码中1的个数大于0且本次编码数据1的个数大于0或者一行编码中0的个数大于1且本次编码数据0的个数大于1,即(cnt(t-1)>0 && N1{q_m[0:7]}>N0{q_m [0:7]}) || (cnt(t-1)<0 && N0{q_m[0:7]}>N1{q_m[0:7]})为真,为了保持1和0的数量保持相对平衡,需要将本次8位编码数据进行取反,以增加0或1的个数;否则,将q_m[0:7]输出作为本次编码数据的低8位
至于2*q_m[8]则是用于计算一行数据中1的个数比0的个数多多少,当q_out[9]=1,q_out[8]=0时,N0{q_m[0:7]}- N1{q_m[0:7]}即可表示1的个数比0的个数多多少;当q_out[9]=1,q_out[8]=1时,N0{q_m[0:7]}- N1{q_m[0:7]}还需要再加上两个1,因此就有2*q_m[8];q_out[9]=0时也是同理。
3.TMDS代码实现
`timescale 1ns / 1ps
module TMDS_encode(
input clk, //系统时钟
input rst_n, //系统复位
input c0, //控制信号0
input c1, //控制信号1
input de, //有效信号
input [7:0] data_in, //输入8位信号
output reg [9:0] q_out //输出10位编码信号
);
parameter CTRLTOKEN0 = 10'b11_0101_0100;
parameter CTRLTOKEN1 = 10'b00_1010_1011;
parameter CTRLTOKEN2 = 10'b01_0101_0100;
parameter CTRLTOKEN3 = 10'b10_1010_1011;
reg [3:0] n1_data_in; //对本次编码数据中的1的个数进行计算,最大为8
reg [7:0] data_in_r; //暂存输入的8为数据
reg c0_r; //暂存c0;
reg c1_r; //暂存c1;
reg de_r; //暂存de
reg [5:0] cnt; //对一行中1比0多的个数计数,最高位为符号位
wire [8:0] q_m; //对数据进行异或或者同或运算后暂存数据
reg [8:0] q_m_r;
reg [3:0] n1_qm; //对q_m中1的个数进行计数
reg [3:0] n0_qm; //对q_m中0的个数进行计数
wire condition1; //第一个判断
wire condition2; //第二个判断
wire condition3; //第三个判断
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
n1_data_in <= 4'd0;
else if(de) //de为高电平统计数据中1的个数
n1_data_in <= data_in[0] + data_in[1] + data_in[2] + data_in[3] + data_in[4] + data_in[5] + data_in[6] + data_in[7];
else //de为低电平对控制信号进行编码
n1_data_in <= 4'd0;
end
always @(posedge clk ) begin
c0_r <= c0; //将数据暂存,以和后续信号对齐
c1_r <= c1;
de_r <= de;
data_in_r <= data_in;
q_m_r <= q_m;
end
assign condition1 = (n1_data_in > 4'd4) || (n1_data_in == 4'd4 && data_in_r[0] == 0);
//对输入的信号进行异或运算
assign q_m[0] = data_in_r[0];
assign q_m[1] = condition1 ? ~((q_m[0] ^ data_in_r[1])) : (q_m[0] ^ data_in_r[1]);
assign q_m[2] = condition1 ? ~((q_m[1] ^ data_in_r[2])) : (q_m[1] ^ data_in_r[2]);
assign q_m[3] = condition1 ? ~((q_m[2] ^ data_in_r[3])) : (q_m[2] ^ data_in_r[3]);
assign q_m[4] = condition1 ? ~((q_m[3] ^ data_in_r[4])) : (q_m[3] ^ data_in_r[4]);
assign q_m[5] = condition1 ? ~((q_m[4] ^ data_in_r[5])) : (q_m[4] ^ data_in_r[5]);
assign q_m[6] = condition1 ? ~((q_m[5] ^ data_in_r[6])) : (q_m[5] ^ data_in_r[6]);
assign q_m[7] = condition1 ? ~((q_m[6] ^ data_in_r[7])) : (q_m[6] ^ data_in_r[7]);
assign q_m[8] = ~condition1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
n1_qm <= 4'd0;
n0_qm <= 4'd0;
end
else if(de)begin
n1_qm <= q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7]; //计算q_m_r中1的个数
n0_qm <= 4'd8 - (q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7]); //计算q_m_r中0的个数
end
else begin
n1_qm <= 4'd0;
n0_qm <= 4'd0;
end
end
assign condition2 = (cnt == 6'd0 || n1_qm == n0_qm); //判断条件真假2
assign condition3 = (((~cnt[5]) && (n1_qm > n0_qm)) || ((cnt[5]) && (n1_qm < n0_qm))); //判断条件真假3
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
q_out <= 10'd0; //复位清零
cnt <= 6'd0;
end
else if(de_r)begin //de_r拉高表示数据有效
if(condition2)begin
q_out[9] <= ~q_m_r[8];
q_out[8] <= q_m_r[8];
q_out[7:0] <= q_m_r[8] ? q_m_r[7:0] : ~q_m_r[7:0]; //根据q_m_r[8]确定是否取反
cnt <= q_m_r[8] ? (cnt + n1_qm - n0_qm) : (cnt + n0_qm - n1_qm);
end
else if(condition3)begin
q_out[9] <= 1'b1;
q_out[8] <= q_m_r[8];
q_out[7:0] <= ~q_m_r[7:0];
cnt <= cnt + {q_m_r[8],1'b0} + n0_qm - n1_qm; //计算cnt
end
else begin
q_out[9] <= 1'b0;
q_out[8] <= q_m_r[8];
q_out[7:0] <= q_m_r[7:0];
cnt <= cnt - {~q_m_r[8],1'b0} + n1_qm - n0_qm; //计算cnt
end
end
else begin //数据有效信号拉低
cnt <= 6'd0; //计数器清零
case({c1_r,c0_r}) //对控制信号进行编码
2'b00 : q_out <= CTRLTOKEN0;
2'b01 : q_out <= CTRLTOKEN1;
2'b10 : q_out <= CTRLTOKEN2;
2'b11 : q_out <= CTRLTOKEN3;
endcase
end
end
endmodule
`timescale 1ns / 1ps
module tb_TMDS_encode;
// TMDS_encode Parameters
parameter PERIOD = 10 ;
parameter CTRLTOKEN0 = 10'b11_0101_0100;
parameter CTRLTOKEN1 = 10'b00_1010_1011;
parameter CTRLTOKEN2 = 10'b01_0101_0100;
parameter CTRLTOKEN3 = 10'b10_1010_1011;
// TMDS_encode Inputs
reg clk = 0 ;
reg rst_n = 0 ;
reg c0 = 0 ;
reg c1 = 0 ;
reg de = 0 ;
reg [7:0] data_in = 0 ;
// TMDS_encode Outputs
wire [9:0] q_out ;
initial
begin
forever #(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2) rst_n = 1;
end
TMDS_encode #(
.CTRLTOKEN0 ( CTRLTOKEN0 ),
.CTRLTOKEN1 ( CTRLTOKEN1 ),
.CTRLTOKEN2 ( CTRLTOKEN2 ),
.CTRLTOKEN3 ( CTRLTOKEN3 ))
u_TMDS_encode (
.clk ( clk ),
.rst_n ( rst_n ),
.c0 ( c0 ),
.c1 ( c1 ),
.de ( de ),
.data_in ( data_in ),
.q_out ( q_out )
);
initial
begin
#100;
de = 1'b1;
repeat(256)begin
@(posedge clk);
data_in = {$random % 255};
end
de = 1'b0;
#100
de = 1'b1;
repeat(128)begin
@(posedge clk);
data_in = {$random % 255};
end
de = 1'b0;
#100;
de = 1'b1;
data_in = 8'hc6;
#60;
de = 1'b0;
$stop;
end
endmodule
4.仿真结果
5.问题总结
虽然本次代码较为简单,但在实现过程中也遇到了一些问题,首先就是条件1的真假判断我最开始写的时候使用时序逻辑写的,用if-else来判断条件是否成立然后再对q_m进行同或或者异或运算,但在仿真的时候发现q_m延后了好几个时钟周期,因此此处推荐用组合逻辑来实现,然后就是cnt的位宽设置得太大,导致条件3出现了未知态,因此cnt的位宽不应定义过大,合适即可。最后,n1_qm核n0_qm的计算应该用q_m来计算,而不是q_m_r,q_m_r需要进行一个时钟周期的同步,可能会导致后续条件判断出错。