FPGA实现TMDS编码

news2024/11/17 15:32:56

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需要进行一个时钟周期的同步,可能会导致后续条件判断出错。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2066443.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

适用于 Windows 的典型 PDF 编辑器程序

尽管 PDF 文件已经存在了很长时间&#xff0c;但我们仍然希望使用此类文件完成一些任务。其中一项任务是在我们的计算机上编辑它们&#xff0c;尤其是 Windows。但是&#xff0c;考虑到 PDF 文件是复杂数据的集合&#xff0c;它不会那么简单。因此&#xff0c;您将需要第三方应…

5分钟就能搭建 AI 聊天室场景!内含源代码,码住了!

近期奥运会的赛事观看火爆全网&#xff0c;大家在赛事直播间的聊天更是异常活跃&#xff0c;小编作为一个非足球爱好者&#xff0c;经常对直播解说中的「专有名词」充满迷惑。这时候特别想有一个 AI 数字人帮忙解惑。 这里&#xff0c;我们就利用云信的 AI 数字人&#xff0c;…

GraphRAG + Ollama 本地部署全攻略:避坑实战指南

—1— 为什么要对 GraphRAG 本地部署&#xff1f; 微软开源 GraphRAG 后&#xff0c;热度越来越高&#xff0c;目前 GraphRAG 只支持 OpenAI 的闭源大模型&#xff0c;导致部署后使用范围大大受限&#xff0c;本文通过 GraphRAG 源码的修改&#xff0c;来支持更广泛的 Embedd…

springboot之项目搭建并say hi

写在前面 本文看下如何搭建一个最简单的支持http接口的hello程序。 1&#xff1a;正文 接着引入springboot依赖&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><v…

4.7重复的子字符串(LC_459-E)

给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。示例 2: 输入: s "aba" 输出: false示例 3: 输入: s "abcabcabcabc"…

无线终端ZWS云应用(一)—1分钟快速接入CATCOM-100 DTU上云

环境监测设备&#xff08;如温湿度传感器&#xff09;可以通过DTU终端CATCOM-100接入ZWS云平台&#xff0c;实现远程监控和管理。 准备工作 准备一个温湿度传感器和一个致远电子的DTU终端CATCOM-100。准备一张SIM卡&#xff0c;用于4G联网。 操作步骤 1. 云平台设备创建 1.1 …

PCIe563XD系列多功能异步数据采集卡64路AD信号采集500K采样频率

阿尔泰科技 型号&#xff1a;PCIe5630D/5631D/5632D/5633Dhttps://item.taobao.com/item.htm?spma1z10.3-c-s.w4002-265216876.12.84513350msbilC&id589158158140&piskf6qstfsYFCA6dK09z-BERdlfDjobG5szWKMYE-KwHcntcqeoOlla3juYGWce0OmNomNjOScZ7chwcmwbiSuY0jrXIkN…

nodejs发送邮件给多个收件人如何实现群发?

node.js发送邮件的方法&#xff1f;如何用nodejs自动发送邮件&#xff1f; Node.js发送邮件是一种高效而灵活的解决方案&#xff0c;尤其是在需要群发邮件时。AokSend将探讨如何使用Node.js发送邮件给多个收件人&#xff0c;帮您实现邮件的批量发送。 nodejs发送邮件&#xf…

2024年3款精选工具+谷歌翻译:发现那些你不知道的高级功能!

现在这世界变得越来越像一个村了&#xff0c;语言不应该是我们聊天的绊脚石。但是在工作的时候&#xff0c;碰到不同语言的文件、邮件和会议&#xff0c;翻译还是挺考验人的。好在有谷歌翻译这个牛气的工具&#xff0c;还有其他几个好用的软件帮忙&#xff0c;让我们在工作上翻…

前端常见**MS题 [3]

css部分 1、简单说明一下盒模型 CSS盒模型定义了盒的每个部分包含&#xff1a; margin, border, padding, content 。根据盒子大小的计算方式不同盒模型分成了两种&#xff0c;标准盒模型和怪异盒模型。 标准模型&#xff0c;给盒设置 width 和 height&#xff0c;实际设置的是…

【吊打面试官系列-Memcached面试题】memcached 的多线程是什么?如何使用它们?

大家好&#xff0c;我是锋哥。今天分享关于 【memcached 的多线程是什么&#xff1f;如何使用它们&#xff1f; 】面试题&#xff0c;希望对大家有帮助&#xff1b; memcached 的多线程是什么&#xff1f;如何使用它们&#xff1f; 线程就是定律&#xff08;threads rule&#…

linux 第一个命令的编写

1. 命令的概念 命令就是可执行程序。 比如说输入“ls -al”命令&#xff0c;ls 就是可执行程序的的名字。-al 就是要传递进去的参数。 ps 命令&#xff1a; 功能&#xff1a;显示进程的动态。 输入 ps 命令 当 shell 接收到命令以后&#xff0c;会根据输入的字符到环境变量和默…

UDP/TCP --- Socket编程

本篇将使用 Linux 中的系统调用来实现模拟 TCP 和 UDP 的通信过程&#xff0c;其中只对 UDP 和 TCP 进行了简单的介绍&#xff0c;本篇主要实现的是代码&#xff0c;至于 UDP 和 TCP 的详细讲解将会在之后的文章中给出。 本篇给出的 tcp 和 udp 的代码中的 echo 都是测试连接是…

电脑死机之后强制关机重启,只能进入到Bios,不能进入到系统?

前言 最近遇到好几件比较有意思的事情&#xff0c;粉丝过来求助咨询&#xff1a;电脑不知怎的就黑屏死机了&#xff0c;重启之后&#xff0c;电脑只能进入到Bios&#xff0c;无论怎么重启都没用。 把电脑拆出来看了看&#xff0c;线路一切正常。感觉上可能是内存条的问题&…

NRK3301语音识别芯片在汽车内饰氛围灯上的应用方案解析

随着智能汽车的快速发展&#xff0c;车载语音交互技术逐渐成为提升驾驶体验的关键技术之一。传统的汽车内饰氛围灯语音识别系统往往依赖于手动操作&#xff0c;不仅操作繁琐&#xff0c;而且在驾驶过程中容易分散驾驶员的注意力。因此&#xff0c;开发一种高效、便捷的汽车内饰…

OpenAI gym: when is reset required?

题意&#xff1a;“OpenAI Gym: 什么时候需要重置&#xff1f;” 问题背景&#xff1a; Although I can manage to get the examples and my own code to run, I am more curious about the real semantics / expectations behind OpenAI gym API, in particular Env.reset() …

基于网格尺度的上海市人口分布空间聚集特征分析与冷热点识别

在上篇文章提到了同一研究空间在不同尺度下的观察可能会带来不同的见解和发现&#xff0c;这次我们把尺度缩放到网格&#xff0c;来看网格尺度下的空间自相关性、高/低聚类&#xff0c;这些&#xff0c;因为尺度缩放到网格尺度了&#xff0c;全国这个行政区范围就显的太大了&am…

Python采集网页数据:八招全解

在信息时代&#xff0c;海量的数据日益成为企业和个人获取商业价值的重要手段。而获取这些数据的方式之一就是通过网络爬虫技术采集网络上的各种信息&#xff0c;对于 Python 程序员来说&#xff0c;这项工作并不困难。本文将从八个方面&#xff0c;带你了解如何使用 Python 采…

查找技术与平衡查找树

目录 引言 查找技术的重要性 顺序查找 顺序查找的优缺点对比 二分查找 二分查找的步骤总结 哈希查找 哈希函数设计与冲突解决 平衡查找树 二叉搜索树、AVL树与红黑树 平衡查找树的插入与删除操作 平衡查找树的应用场景 总结与应用 综合实例分析 引言 查找是计算机…

算法训练营三刷(Java) | 第六天~第十一天

算法训练营三刷&#xff08;Java&#xff09; | 第六天~第十一天 第六天 LeetCode 242 有效的字母异位词 解题思路&#xff1a; 数组哈希记录每个字幕出现的次数&#xff0c;然后进行比较。Java中字符串取下标i处字符可以使用charAt成员函数也可以转化为字符数组之后用数组的…