FPGA实现TMDS编码

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和控制信号:

  1. 对于video周期采用的是video data encoding,将8bits数据转换成10bits数据
  2. 对于control周期采用的是CTL encoding,将2bits数据转换成10bits数据
  3. 对于Data Island周期采用的是TERC4encoding,将4bits数据转换成10bits数据

HDMI传输由三组TMDS通道和一组TMDS clock通道组成,TMDS clock的运行频率是video信号的pixel频率,在每个TMDS时钟通道周期,每个TMDS data通道都发送10bit数据。

  1. channel0传输B数据和HSync、VSync
  2. channel1传输G数据和CTL0、CTL1
  3. channel2传输R数据和CTL2、CTL3

HDMI总体传输流程如下:

  1. 2.TMDS编码流程

TMDS编码流程如下图所示,其中使用的信号含义如下表所示。

首先D是需要进行编码的8比特原始像素数据,蓝色通道对应的是蓝色通道的8比特数据,在另外两个颜色通道中,对应的就是另外两个颜色通道的8比特数据。而C0C1是两个控制信号,在蓝色通道中C0C1是行、场同步信号。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]赋值为19位用来表示TMDS对输入数据采用异或还是同或运算。然后判断DE信号是否拉高,如果DE信号没有拉高,接下来就要对控制信号C0C1进行编码,通过查找表实现即可,总共四种情况。

并且需要把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]取反得到数据的第10q_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]})为真,为了保持10的数量保持相对平衡,需要将本次8位编码数据进行取反,以增加01的个数;否则,将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需要进行一个时钟周期的同步,可能会导致后续条件判断出错。

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
***和LVDS是两种不同的差分传输线标准。TMDS(Transition Minimized Differential Signaling)是一种用于数字视频传输的差分信号传输标准,主要用于HDMI和DVI等接口。而LVDS(Low-Voltage Differential Signaling)是一种用于通用数据传输的差分信号传输标准,主要用于FPGA和其他高速数据传输应用中。 对于XILINX ARTIX7系列的FPGA,通常会提供LVDS接收和发送的功能。在编写LVDS接收和发送程序时,需要根据LVDS的电平标准进行配置和操作。LVDS信号的电平标准包括LVDS和LVDS_25两种。其中LVDS_25是指差分电压为2.5V的LVDS信号,而LVDS是指差分电压为1.2V的LVDS信号。在使用XILINX ARTIX7系列的FPGA时,需要根据具体的应用需求选择合适的LVDS电平标准进行配置和编程。 另外,FPGA还可以支持其他类型的差分传输线标准,比如用于HDMI通信的标准。不同的差分传输线标准对应不同的电平要求和信号处理方式,因此在设计和编写FPGA程序时,需要根据具体的应用需求选择合适的差分传输线标准,并进行相应的配置和编程。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [LVDS AD 布线 和 FPGA收发程序(硬软)](https://blog.csdn.net/weixin_44329079/article/details/110010129)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [基于FPGA的HDMI转LVDS应用案例](https://blog.csdn.net/xh_24/article/details/117153366)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值