FPGA学习日记(七)HDMI图像数据传输

2023-10-27

一、实现目标

将像素数据通过HDMI传输,在显示器上显示。

二、数据流传输

HDMI常采用TMDS传输(上升沿复位)方式:

(1)通过三个通道分别可传入8位的rgb视频信号,2位的控制信号,4位的音频信号或其他数据信号,其中行场同步信号在blue的控制信号中传输;

(2)在encode编码器这端,将输入的8位视频信号通过TMDS算法转换为10位的视频信号输出到并串转换模块;

(3)在并串转换模块中将输入的10位数据转为串行数发送到接受设备。

三、数据流传输实现

顶层模块,使用差分输出原语OBUFDS,p端口输出数据与输入数据相同,n端口输出相同时钟下的反相数据。

// Descriptions:        DVI发送端顶层模块


module dvi_transmitter_top(
    input        pclk,           // pixel clock
    input        pclk_x5,        // pixel clock x5,ddr模式只需*5
    input        reset_n,        // reset
    
    input [23:0] video_din,      // RGB888 video in
    input        video_hsync,    // hsync data
    input        video_vsync,    // vsync data
    input        video_de,       // data enable
    
    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 输出使能
    );
    
//wire define    
wire        reset;
    
//并行数据
wire [9:0]  red_10bit;
wire [9:0]  green_10bit;
wire [9:0]  blue_10bit;
wire [9:0]  clk_10bit;  
  
//串行数据
wire [2:0]  tmds_data_serial;
wire        tmds_clk_serial;

//*****************************************************
//**                    main code
//***************************************************** 
assign tmds_oen  = 1'b1;  
assign clk_10bit = 10'b1111100000;

//异步复位,同步释放,TMDS协议中复位信号为高
asyn_rst_syn reset_syn(
    .reset_n    (reset_n),
    .clk        (pclk),
    
    .syn_reset  (reset)    //高有效
    );
  
//对三个颜色通道进行编码,分别输出
dvi_encoder encoder_b (
    .clkin      (pclk),
    .rstin	    (reset),
    
    .din        (video_din[7:0]),
    .c0			(video_hsync),
    .c1			(video_vsync),
    .de			(video_de),
    .dout		(blue_10bit)
    ) ;

dvi_encoder encoder_g (
    .clkin      (pclk),
    .rstin	    (reset),
    
    .din		(video_din[15:8]),
    .c0			(1'b0),
    .c1			(1'b0),
    .de			(video_de),
    .dout		(green_10bit)
    ) ;
    
dvi_encoder encoder_r (
    .clkin      (pclk),
    .rstin	    (reset),
    
    .din		(video_din[23:16]),
    .c0			(1'b0),
    .c1			(1'b0),
    .de			(video_de),
    .dout		(red_10bit)
    ) ;
    
//对编码后的数据进行并串转换,引用原语快速实现并串转换
serializer_10_to_1 serializer_b(
    .reset              (reset),                // 复位,高有效
    .paralell_clk       (pclk),                 // 输入并行数据时钟
    .serial_clk_5x      (pclk_x5),              // 输入串行数据时钟
    .paralell_data      (blue_10bit),           // 输入并行数据

    .serial_data_out    (tmds_data_serial[0])   // 输出串行数据
    );    
    
serializer_10_to_1 serializer_g(
    .reset              (reset),
    .paralell_clk       (pclk),
    .serial_clk_5x      (pclk_x5),
    .paralell_data      (green_10bit),

    .serial_data_out    (tmds_data_serial[1])
    );
    
serializer_10_to_1 serializer_r(
    .reset              (reset),
    .paralell_clk       (pclk),
    .serial_clk_5x      (pclk_x5),
    .paralell_data      (red_10bit),

    .serial_data_out    (tmds_data_serial[2])
    );
            
serializer_10_to_1 serializer_clk(
    .reset              (reset),
    .paralell_clk       (pclk),
    .serial_clk_5x      (pclk_x5),
    .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

编码器模块(TMDS算法):

 `timescale 1 ps / 1ps

module dvi_encoder (
  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
);


  reg [3:0] n1d; //number of 1 in din
  reg [7:0] din_q;//暂存输入数据

//计算当前数据中“1”的个数,并寄存当前din的数据
  always @ (posedge clkin) begin
    n1d <=#1 din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];

    din_q <=#1 din;
  end

/*8位转9位,通过判断数据中1的个数,决定进行逐位异或还是逐位同或操作,新增最高位为判定位取反
decision1=1,进行逐位同或,decision1=0,进行逐位异或*/
  wire decision1;

  assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q[0] == 1'b0));//判定翻转次数

  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;

/*9位转10位,分别对当前数据的0,1进行计数;并设置累加器保存每次0,1个数的差值;
四个状态:1.当当前数据num0 = mum1或累加器值为0,不翻转,最高位为次高位取反,低八位有次高位决定是否取反;2.当前数据num0>num1,且累加器数据为负,翻转;3.当前数据num0<num1,且累加器数据为正,翻转;4.其他情况保持
若使能信号不为1,则由控制位输入的特定数据决定输出相应的数据
*/


  reg [3:0] n1q_m, n0q_m; // number of 1s and 0s for q_m
  always @ (posedge clkin) 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

  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);

  assign decision3 = (~cnt[4] & (n1q_m > n0q_m)) | (cnt[4] & (n0q_m > n1q_m));

  // pipe line alignment与din对齐

  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    <=#1 de;
    de_reg  <=#1 de_q;
    
    c0_q    <=#1 c0;
    c0_reg  <=#1 c0_q;
    c1_q    <=#1 c1;
    c1_reg  <=#1 c1_q;

    q_m_reg <=#1 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]   <=#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(decision3) 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
        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 

TMDS编码算法流程图如下:

TMDS 通过逻辑算法将 8 位字符数据通过最小转换编码为 10 位字符数据,前 8 位数据由原始信号经运后获得,第 9 位表示运算的方式, 1 表示异或 0 表示异或非。经过 DC 平衡后(第 10 位),采用差分信号传输数据。第 10 位实际是一个反转标志位, 1表示进行了反转而 0 表示没有反转,从而达到 DC 平衡。接收端在收到信号后,再进行相反的运算。 TMDS 和 LVDS、 TTL 相比有较好的电磁兼容性能。这种算法可以减小传输信号过程的上冲和下冲,而 DC 平衡使信号对传输线的电磁干扰减少,可以用低成本的专用电缆实现长距离、高质量的数字信号传输。

TMDS算法有接口规范定义,调用时可对以上模块进行修改后使用,在接收端通过译码器输出RGB888数据。

并串转换模块,可调用原语OSERDESE2实现:

//并串转换
module serializer_10_to_1(
    input           reset,              // 复位,高有效
    input           paralell_clk,       // 输入并行数据时钟
    input           serial_clk_5x,      // 输入串行数据时钟
    input   [9:0]   paralell_data,      // 输入并行数据

    output 			serial_data_out     // 输出串行数据
    );

wire		connect1;
wire		connect2;



// OSERDESE2: Output SERial/DESerializer with bitslip
// 7 Series
// Xilinx HDL Language Template, version 2019.1
OSERDESE2 #(
			.DATA_RATE_OQ("DDR"), // DDR, SDR
			.DATA_RATE_TQ("SDR"), // DDR, BUF, SDR.DATA_WIDTH(4), // Parallel data width (2-8,10,14)
			.DATA_WIDTH(10),
			.SERDES_MODE("MASTER"), // MASTER, SLAVE
			.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 (
			.OQ(serial_data_out), // 1-bit output: Data path output
			// SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each)
			.SHIFTOUT1(connect1),
			.SHIFTOUT2(connect2),

			.CLK(serial_clk_5x), // 1-bit input: High speed clock
			.CLKDIV(paralell_clk), // 1-bit input: Divided clock
			// D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
			.D1(paralell_data[0]),
			.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]),
			.OCE(1'b1), // 1-bit input: Output data clock enable
			.RST(reset), // 1-bit input: Reset
			// SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each)
			.SHIFTIN1(connect1),
			.SHIFTIN2(connect2),
			// 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(3 state)
			.TCE(1'b0) // 1-bit input: 3-state clock enable
);
// End of OSERDESE2_inst instantiation
OSERDESE2 #(
			.DATA_RATE_OQ("DDR"), // DDR, SDR
			.DATA_RATE_TQ("SDR"), // DDR, BUF, SDR.DATA_WIDTH(4), // Parallel data width (2-8,10,14)

			.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(4) // 3-state converter width (1,4)
)
OSERDESE2_Slave (
			.OFB(OFB), // 1-bit output: Feedback path for data
			.OQ(OQ), // 1-bit output: Data path output
			// SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each)
			.SHIFTOUT1(connect1),
			.SHIFTOUT2(connect2),
			.TBYTEOUT(TBYTEOUT), // 1-bit output: Byte group tristate
			.TFB(), // 1-bit output: 3-state control
			.TQ(), // 1-bit output: 3-state control
			.CLK(serial_clk_5x), // 1-bit input: High speed clock
			.CLKDIV(paralell_clk), // 1-bit input: Divided clock
			// D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
			.D1(1'b0)),
			.D2(1'b0)),
			.D3(paralell_data[8]),
			.D4(paralell_data[9]),
			.D5(1'b0)),
			.D6(1'b0)),
			.D7(1'b0)),
			.D8(1'b0)),
			.OCE(1'b1), // 1-bit input: Output data clock enable
			.RST(reset), // 1-bit input: Reset
			// SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each)
			.SHIFTIN1(connect1),
			.SHIFTIN2(connect2),
			// 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
);

原语可搜索xilinx官方文档,其中有verilog示例代码,只需按提示添加信号即可,减少了代码量。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FPGA学习日记(七)HDMI图像数据传输 的相关文章

  • 下载安装和汉化Eclipse(详细)

    文章目录 前言 Eclipse的下载安装 总结 前言 本文详细叙述了 Eclipse的下载安装 在使用Eclipse之前需要准备好Java环境 可参考这里 JDK的下载安装和配置 提示 以下是本篇文章正文内容 下面案例可供参考 Eclips

随机推荐

  • mysql报错代码10051_socket error 10061/11004/10053/10051等错误总结

    Socket是应用层与TCP IP协议族通信的中间软件抽象层 它是一组接口 在设计模式中 Socket其实就是一个门面模式 它把复杂的TCP IP协议族隐藏在Socket接口后面 对用户来说 一组简单的接口就是全部 让Socket去组织数据
  • 正点原子IM6XULL阿尔法USB摄像头的远程调用(一)硬件连接

    正点原子IMX6ULL阿尔法USB摄像头的远程调用 一 硬件连接 话不多说 直接上货 1 用电源线连接电源 如图所示 2 利用USB接串口线将板子和电脑相连 电脑用U口 板子用USB UART口 如图所示 3 按开机键开机 如图所示 4 从
  • 无向图G的广度优先搜索和深度优先搜索以及完整程序

    图的遍历算法有两种 广度优先搜索和深度优先搜索 一 广度优先搜索类似于层次遍历 需要借助辅助队列 空间复杂度为O V 空间复杂度由辅助队列大小决定 时间复杂度为O V E 为避免同一顶点被多次访问 设计visited 来标记顶点 二 深度优
  • selenium家族介绍(四大组件)

    1 selenium RC selenium Server Client Libraries Selenium Server负责控制浏览器的行为 Client Libraries则是给测试人员编写测试案例时用来控制selenium Serv
  • 理解Docker跨多主机容器网络

    理解Docker跨多主机容器网络 二月 15 2016 2条评论 在Docker 1 9 出世前 跨多主机的容器通信方案大致有如下三种 1 端口映射 将宿主机A的端口P映射到容器C的网络空间监听的端口P 上 仅提供四层及以上应用和服务使用
  • 记录次数

    版本内容 1 新增词条数据报告 统计累计次数 最早时间 最晚时间等等 2 词条加入内容文本审核功能 创建 修改词条先经过微信文本安全接口审查 审查通过的才能分享公开数据 否则只能自己可见 3 新增分享 版本思考 这个小程序是有自然流量的 就
  • 8.python发送邮箱验证码——使用zmail发送邮件验证用户信息

    1 邮件工具类封装 1 1概述 在我们的注册页面时 会需要一个发送邮件的功能去验证邮箱号是否正确 同样的 可以避免一些用户恶意的注册导致数据库出现问题 这里将使用zmail的第三方库去实现发送邮件的功能 zmail官方网址 1 2实现功能流
  • yuv,rgb,hsv比较

    1 YUV和RGB互相转换的公式如下 RGB取值范围均为0 255 Y 0 299R 0 587G 0 114B U 0 147R 0 289G 0 436B V 0 615R 0 515G 0 100B R Y 1 14V G Y 0 3
  • 解决方案:QObject::moveToThread: Current thread (***) is not the object‘s thread

    报错信息 QObject moveToThread Current thread 0x5651ebdaa180 is not the object s thread 0x5651eba7e2a0 Cannot move to target
  • 【npm第6期】通过vue中npm run build --report来介绍npm传参

    最近发现vue中可以通过npm run build report来调用webpack bundle analyzer插件 具体相关代码如下 webpack prod conf js if config build bundleAnalyze
  • [HDLBits] Edgecapture

    For each bit in a 32 bit vector capture when the input signal changes from 1 in one clock cycle to 0 the next Capture me
  • 使用docker报错 :Error response from daemon: Get https://index.docker.io/v1/search?q=java&n=25: dial tcp:

    最近在学习docker时 在安装完成后去搜索镜像资源出了这个问题 开始以为是镜像加速器没配好 就试着重新配置镜像加速器 重装了docker 折腾了一番依然没有解决 具体错误信息 1 2 root localhost docker searc
  • MySQL实现删除重复数据行仅保留一行

    首先创建测试环境 创建数据库表并加入数据 接下来研究如何做到删除重读的数据行并且仅保留第一条id最小的记录 首先需要知道哪一列是重复的 所以首先筛选出重复的列 select email from user group by email ha
  • 数据库创建函数_达梦数据库创建UUID函数

    数据库创建函数 达梦数据库创建UUID函数 接触达梦数据库有一段时间了 整理了一些资料 今天分享一下达梦数据UUID自定义函数 UUID函数定义 很多数据库都有提供UUID函数 可是接触达梦数据库后 发现达梦数据库并没有UUID函数定义 不
  • C#对txt文件进行读写操作

    C 中对txt文件进行读写操作包括两种方式 一种是基于FileInfo类 调用该类的Read方法 但是该方法读出来的数据是byte格式 需要对其进行解码 将相应的字节数转换为字符 而C 中System Text引用就包含的解码的方法 相应代
  • Nginx做代理时X-Forwarded-For信息头的处理

    如今利用nginx做负载均衡的实例已经很多了 针对不同的应用场合 还有很多需要注意的地方 本文要说的就是在通过CDN 后到达nginx做负载均衡时请求头中的X Forwarded For项到底发生了什么变化 下图为简单的web架构图 先来看
  • MySQL笔记——数据库与数据表(内附例子)

    目录 创建数据库 查看数据库 查看所有数据库 查看数据库使用的字符编码 修改数据库 删除数据库 选择 打开 数据库 创建数据表 基础创建 用select查询的结果创建数据表 将表2复制给表1 修改数据表 增加列 修改列 修改字段名称 修改字
  • “字节一年,人间三年!”

    关注后回复 进群 拉你进程序员交流群 大家好 我在网上冲浪的时候看到一个问题 图片 看到这个问题的时候我就想起了在江湖上流传已久的一句话 字节一年 人间三年 然后我看到了这个问题下的一个高赞回答 分享给了我一位曾经在字节工作过接近三年时间的
  • 新系统申请软件著作权详细操作说明

    关于其他问题 请参见之前的文章 1 软件著作权申请时源程序 文档和其他文件的编写说明 本文主要对源程序 文档和其他文件的格式进行说明 2 计算机软件著作权申请表中开发运行环境 技术特点等项目的编写指南 本文主要对申请表中主要功能 技术特点
  • FPGA学习日记(七)HDMI图像数据传输

    一 实现目标 将像素数据通过HDMI传输 在显示器上显示 二 数据流传输 HDMI常采用TMDS传输 上升沿复位 方式 1 通过三个通道分别可传入8位的rgb视频信号 2位的控制信号 4位的音频信号或其他数据信号 其中行场同步信号在blue