HDMI 显示器驱动设计

发布时间 2023-11-13 17:18:22作者: 阿风小子

1.理论
HDMI简介

      VGA 接口体积较大;且传输的模拟信号易受外界干扰。因此在VGA 接口之后,首先推出的是 DVI 接口, DVI 是基于 TMDS(Transition MinimizedDifferential Signaling, 最小化传输差分信号)技术来传输数字信号。

      DVI 接口设计之初考虑的对象是 PC,对于平板电视的兼容能力一般;只支持计算机领域的 RGB 数字信号,而对数字化的色差信号无法支持;只支持 8bit 的 RGB 信号传输,不能让广色域的显示终端发挥出最佳性能;出于兼容性考虑,预留了不少引脚以支持模拟设备,造成接口体积较大(比VGA还大);只能传输图像信号,对于数字音频信号完全没有考虑。
       由于以上种种缺陷, 促使了 HDMI 标准的诞生。
       HDMI 全称“High Definition Multimedia Interface 高清多媒体接口”。 2002 年 4 月,来自电子电器行业的 7 家公司——日立、松下、飞利浦、 等七家共同组建了 HDMI 高清多媒体接口接口组织 HDMI Founders(HDMI 论坛),开始着手制定一种符合高清时代标准的全新数字化视频/音频接口技术。

      HDMI 标准的制定,没有抛弃 DVI 标准中相对成熟且较易实现的部分技术标准,整个传输原理依然是基于 TMDS 编码技术。 针对 DVI 的诸多问题, HDMI 做了大幅改进具有众多优点。

HDMI接口及引脚定义

      在一些便携设备上, HDMI 接口都成了标准化的配置。 HDMI 接口具体见下图。

 

       HDMI 规格书中规定了 HDMI 的 4 种接口类型,但其中 HDMI B Type 接口类型未在市场中出现过,市面上流通最广的是 HDMI A Type、 HDMI C Type 和 HDMI D Type 接口类型。

 

                                                            三种 HDMI 接口图
       HDMI 接口之间使用 HDMI 信号线连接,不同类型的 HDMI 接口之间也可以使用连接线进行转接。 HDMI 连接线如下图所示。

 

      上面看来接口的外观,下面了解串口的引脚定义。

 

 表一: 接口引脚定义

 

       HDMI 接口共有 19 个引脚,分上下两排,奇数在上,偶数在下,穿插排布。根据其功能,可以将引脚分为 4 类。

      TMDS 通道:引脚 1-引脚 12。负责发送音频、视频及各种辅助数据
       DDC 通道:引脚 15、 16、 17。 DDC 译文为 Display Data Channel,译为“显示数据通道”; 发送端与接收端可利用 DDC 沟道得知彼此的发送与接收能力(HDMI 仅需单向获知接收端器)。

        CEC 通道:引脚 13、 17。 CEC 译文为 Consumer Electronics Control, CEC 通道为必须
预留线路,但可以不必实现,作用是用来发送工业规格的 AV Link 协议信号。

        其他通道:引脚 14 位保留引脚,无连接;引脚 18 为+5V 电源;引脚 19 位热插拔检测引脚。

        注:另外两类型的HDMI c Type 接口、HDMI D Type 接口与HDMI A Type 接口的各引脚名称、功能相同,只是引脚线序不同。

HDMI显示原理   

       HDMI 系统架构由信源端(发送端)和接收端组成。

 

                                                             HDMI 数据传输框图

       如上图所示, HDMI 线缆和连接器提供四个差线对,组成 TMDS 数据和时钟通道, 这些通道用于传递视频,音频和辅助数据; 另外, HDMI 提供一个 VESA DDC 通道, DDC 是用于配置和在一个单独的信源端和一个单独的接收端交换状态; 可选择的 CEC在用户的各种不同的音视频产品中, 提供高水平的控制功能; 可选择的 HDMI 以太网和音频返回(HEAC),在连接的设备中提供以太网兼容的网络数据和一个和 TMDS 相对方向的音频回返通道;此外还有热插拔检测信号 HDP, 当显示器等 HDMI 接口的显示设备通过 HDMI 接口与 HDMI 信源端相连或断开连接时, HDMI 信源端能够通过 HPD 引脚检测出这一事件,并做出响应。
       HDMI 采用和 DVI 相同的传输原理 ——TMDS( Transition Minimized Differential signal),最小化传输差分信号。HDMI 中的 TMDS 传输系统分为两个部分:发送端和接收端。(我们需要完成的是发送端的功能) TMDS 发送端表示 RGB 信号的 24 位并行数据(TMDS 对每个像素的 RGB 三原色分别按8bit 编码,即 R 信号有 8 位, G 信号有 8 位, B 信号有 8 位),然后对这些数据和时钟信号进编码和并/串转换,再将表示 3 个 RGB 信号的数据和时钟信号分别分配到独立的传输通道发送出去。接收端接收来自发送端的串行信号,对其进行解码和串/并转换,然后发送到显示器的控制端。与此同时也接收时钟信号,以实现同步。 流程框图如下图所示。

 

信源端内部流程框图:

 

                                                           TMDS 信道连接图 
      TMDS 通道包括 3 个 RGB 数据传输通道和 1 个时钟信号传输通道。每一通道都通过编码算法,将 8 位的视频、音频数据转换成最小化传输、直流平衡的 10 位数据, 8 位数据经过编码和直流平衡得到 10 位最小化数据,看似增加了冗余位,对传输链路的带宽要求会更高,但事实上,通过这种算法得到的 10 位数据在更长的同轴电缆中传输的可靠性增强了。最小化传输差分信号是通过异或及异或非等逻辑算法将原始 8 位数据转换成 10 位数据,前8 位数据由原始信号经逻辑运算后逻辑得到,第 9 位指示运算的方式,第 10 位用来对应直流平衡。

      要实现 TMDS 通道传输,首先要将传入的 8 位并行数据进行编码、并/串转换。

      编码过程: 将 8 位并行数据发送到 TMDS 接收端,随后进行最小化传输处理,加上第 9 位,然后进行直流均衡处理,这样8bit数据就编码为10bit。 直流平衡(DC-balanced)就是指在编码过程中保证信道中直流偏移为零,使信道中传输数据包含的 1 与 0 的个数相同。方法是在添加编码位的 9位数据的后面加上第 10 位数据, 保证 10 位数据中 1 与 0 个数相同。这样传输的数据趋于直流平衡,使信号对传输线的电磁干扰减少,提高信号传输的可性。

      并/串转换:直流均衡处理后的 10 位数据需要进行并/串转换、单端转差分处理。 TMDS 差分传动技术是一种利用 2 个引脚间电压差来传送信号的技术。传输数据的数值(“0”或者“1”)由两脚间电压正负极性和大小决定。即采用 2 根线来传输信号,一根线上传输原来的信号,另一根线上传输与原来信号相反的信号。这样接收端就可以通过让一根线上的信号减去另一根线上的信号的方式来屏蔽电磁干扰,从而得到正确的信号。 原理图如下图所示。

 

      使用上面的方式对 24位图像数据(8 位 R 信号、 8 位 G 信号、 8 位 B 信号)和时钟信号进行处理,将 4 对差分信号通过HDMI 接口发到接收设备;接收设备通过解码读取初始的8bit数据等一系列操作,实现图像和音频再现。

2.实操
       实验目标:编写 HDMI 驱动, 使用 FPGA 开发板驱动 HDMI 显示器显示十色等宽彩条, HDMI 显示模式为 640*480@60。实验效果,具体见下图。

 

 硬件资源:

      HDMI 接口部分位于板卡的中下部,HDMI 原理图如下图所示。

 

 

 HDMI 部分原理图

2.1 顶层模块


 HDMI 彩条显示实验整体框图

     由上图可知,本实验工程包括 5 个模块,各模块简介,具体见下表格。

表格 41-2 HDMI 彩条显示工程模块简介

 

       观察上图可知,HDMI 的彩条显示是基于 VGA 彩条显示的基础上的,是在 VGA 彩条显示工程的基础上修改的得到的故vga_pic与vga_ctrl模块不重复说明。其中改动较大的有两部分:一是时钟生成模块的输出时钟频率和时钟个数做了改动;二是增加了 HDMI 驱动控制模块 hdmi_ctr。

RTL代码编写

`timescale  1ns/1ns
 
module  hdmi_colorbar
(
    input   wire            sys_clk     ,   
    input   wire            sys_rst_n   ,   
 
    output  wire            ddc_scl     ,
    output  wire            ddc_sda     ,
    output  wire            tmds_clk_p  ,
    output  wire            tmds_clk_n  ,   //HDMI时钟差分信号
    output  wire    [2:0]   tmds_data_p ,
    output  wire    [2:0]   tmds_data_n     //HDMI图像差分信号
 
);
 
wire            vga_clk ;   //VGA工作时钟,频率25MHz
wire            clk_5x  ;
wire            locked  ;   //PLL locked信号
wire            rst_n   ;   //VGA模块复位信号
wire    [11:0]  pix_x   ;   //VGA有效显示区域X轴坐标
wire    [11:0]  pix_y   ;   //VGA有效显示区域Y轴坐标
wire    [15:0]  pix_data;   //VGA像素点色彩信息
wire            hsync   ;   //输出行同步信号
wire            vsync   ;   //输出场同步信号
wire    [15:0]  rgb     ;   //输出像素信息
wire            rgb_valid;
 
assign  rst_n   = (sys_rst_n & (locked));
assign  ddc_scl = 1'b1;
assign  ddc_sda = 1'b1;
 
clk_gen clk_gen_inst
(
    .RESET      (~sys_rst_n ),  //输入复位信号,高电平有效,1bit
    .CLK_IN1    (sys_clk    ),  //输入50MHz晶振时钟,1bit
 
    .CLK_OUT1   (vga_clk    ), //输出VGA工作时钟,频率25Mhz,1bit
    .CLK_OUT2   (clk_5x     ), //输出hdmi工作时钟,频率125M,1bit
    .LOCKED     (locked     )  //输出pll locked信号,1bit
);
 
vga_ctrl  vga_ctrl_inst
(
    .vga_clk    (vga_clk    ),  //输入工作时钟,频率25MHz,1bit
    .sys_rst_n  (rst_n      ),  //输入复位信号,低电平有效,1bit
    .pix_data   (pix_data   ),  //输入像素点色彩信息,16bit
 
    .pix_x      (pix_x      ),  //输出VGA有效显示区域像素点X轴坐标,10bit
    .pix_y      (pix_y      ),  //输出VGA有效显示区域像素点Y轴坐标,10bit
    .hsync      (hsync      ),  //输出行同步信号,1bit
    .vsync      (vsync      ),  //输出场同步信号,1bit
    .rgb_valid  (rgb_valid  ),
    .rgb        (rgb        )   //输出像素点色彩信息,16bit
);
 
vga_pic vga_pic_inst
(
    .vga_clk    (vga_clk    ),  //输入工作时钟,频率25MHz,1bit
    .sys_rst_n  (rst_n      ),  //输入复位信号,低电平有效,1bit
    .pix_x      (pix_x      ),  //输入VGA有效显示区域像素点X轴坐标,10bit
    .pix_y      (pix_y      ),  //输入VGA有效显示区域像素点Y轴坐标,10bit
 
    .pix_data   (pix_data   )   //输出像素点色彩信息,16bit
 
);
 
hdmi_ctrl   hdmi_ctrl_inst
(
    .clk_1x      (vga_clk           ),   //输入系统时钟
    .clk_5x      (clk_5x            ),   //输入5倍系统时钟
    .sys_rst_n   (rst_n             ),   //复位信号,低有效
    .rgb_blue    ({rgb[4:0],3'b0}   ),   //蓝色分量
    .rgb_green   ({rgb[10:5],2'b0}  ),   //绿色分量
    .rgb_red     ({rgb[15:11],3'b0} ),   //红色分量
    .hsync       (hsync             ),   //行同步信号
    .vsync       (vsync             ),   //场同步信号
    .de          (rgb_valid         ),   //使能信号
    .hdmi_clk_p  (tmds_clk_p        ),
    .hdmi_clk_n  (tmds_clk_n        ),   //时钟差分信号
    .hdmi_r_p    (tmds_data_p[2]    ),
    .hdmi_r_n    (tmds_data_n[2]    ),   //红色分量差分信号
    .hdmi_g_p    (tmds_data_p[1]    ),
    .hdmi_g_n    (tmds_data_n[1]    ),   //绿色分量差分信号
    .hdmi_b_p    (tmds_data_p[0]    ),
    .hdmi_b_n    (tmds_data_n[0]    )    //蓝色分量差分信号
);
endmodule
2.2 时钟生成模块
       HDMI 显示模式为 640*480@60,时钟频率为 25MHz,而板卡晶振传入时钟频率为50MHz时钟生成模块的作用就是将 50MHz 晶振时钟分频为 25MHz 的 HDMI 工作时钟;除此之外,还要生成 25MHz 时钟的 5 倍频 125MHz 时钟,具体原因后面讲。

 

      具体实现方法是使用PLL IP核,之前文章有讲解。

2.3 HDMI 驱动控制模块
      HDMI 驱动控制模块 hdmi_ctrl 是 HDMI 彩条显示的核心模块,功能是将 VGA 控制模块传入的行场同步信号、图像信息转换为 HDMI 能读取的差分信号,也可以说成是实现 VGA 图像信息到 HDMI 图像信息的转化。实现这一功能的转化,需要对输入的VGA 图像信息进行编码、并行到串行转换、单端信号转差分信号、单沿采样转双沿采样。

 

                                                      HDMI 驱动控制模块框图

HDMI 驱动控制模块简介:

 

        由上可知HDMI 驱动控制模块共有 17 路输入输出信号,输入信号 9 路,输出信号 8 路信号。

RTL代码:

`timescale  1ns/1ns
 
module  hdmi_ctrl
(
    input   wire            clk_1x      ,   //输入系统时钟
    input   wire            clk_5x      ,   //输入5倍系统时钟
    input   wire            sys_rst_n   ,   //复位信号,低有效
    input   wire    [7:0]   rgb_blue    ,   //蓝色分量
    input   wire    [7:0]   rgb_green   ,   //绿色分量
    input   wire    [7:0]   rgb_red     ,   //红色分量
    input   wire            hsync       ,   //行同步信号
    input   wire            vsync       ,   //场同步信号
    input   wire            de          ,   //使能信号
 
    output  wire            hdmi_clk_p  ,
    output  wire            hdmi_clk_n  ,   //时钟差分信号
    output  wire            hdmi_r_p    ,
    output  wire            hdmi_r_n    ,   //红色分量差分信号
    output  wire            hdmi_g_p    ,
    output  wire            hdmi_g_n    ,   //绿色分量差分信号
    output  wire            hdmi_b_p    ,
    output  wire            hdmi_b_n        //蓝色分量差分信号
);
 
wire    [9:0]   red     ;   //8b转10b后的红色分量
wire    [9:0]   green   ;   //8b转10b后的绿色分量
wire    [9:0]   blue    ;   //8b转10b后的蓝色分量
 
//------------- encode_inst0 -------------
encode  encode_inst0
(
    .sys_clk    (clk_1x     ),
    .sys_rst_n  (sys_rst_n  ),
    .data_in    (rgb_blue   ),
    .c0         (hsync      ),
    .c1         (vsync      ),
    .de         (de         ),
    .data_out   (blue       )
);
 
//------------- encode_inst1 -------------
encode  encode_inst1
(
    .sys_clk    (clk_1x     ),
    .sys_rst_n  (sys_rst_n  ),
    .data_in    (rgb_green  ),
    .c0         (hsync      ),
    .c1         (vsync      ),
    .de         (de         ),
    .data_out   (green      )
);
 
//------------- encode_inst2 -------------
encode  encode_inst2
(
    .sys_clk    (clk_1x     ),
    .sys_rst_n  (sys_rst_n  ),
    .data_in    (rgb_red    ),
    .c0         (hsync      ),
    .c1         (vsync      ),
    .de         (de         ),
    .data_out   (red        )
);
 
//------------- par_to_ser_inst0 -------------
par_to_ser  par_to_ser_inst0
(
    .clk_5x      (clk_5x    ),
    .par_data    (blue      ),
 
    .ser_data_p  (hdmi_b_p  ),
    .ser_data_n  (hdmi_b_n  )
);
 
//------------- par_to_ser_inst1 -------------
par_to_ser  par_to_ser_inst1
(
    .clk_5x      (clk_5x    ),
    .par_data    (green     ),
 
    .ser_data_p  (hdmi_g_p  ),
    .ser_data_n  (hdmi_g_n  )
);
 
//------------- par_to_ser_inst2 -------------
par_to_ser  par_to_ser_inst2
(
    .clk_5x      (clk_5x    ),
    .par_data    (red       ),
 
    .ser_data_p  (hdmi_r_p  ),
    .ser_data_n  (hdmi_r_n  )
);
 
//------------- par_to_ser_inst3 -------------
par_to_ser  par_to_ser_inst3
(
    .clk_5x      (clk_5x        ),
    .par_data    (10'b1111100000),
    .ser_data_p  (hdmi_clk_p    ),
    .ser_data_n  (hdmi_clk_n    )
);
endmodule
2.3.1 编码模块
      完成 VGA 图像数据 8bit 转 10bit 的编码。

 

编码模块输入输出信号信号功能描述:

 

 编码模块参考流程图中各参数说明:

 

 依据上面流程图编写编码代码,编码模块wei。

RTL代码:

`timescale  1ns/1ns
 
 
module  encode
(
    input   wire            sys_clk     ,   //时钟信号
    input   wire            sys_rst_n   ,   //复位信号,低有效
    input   wire    [7:0]   data_in     ,   //输入8bit待编码数据
    input   wire            c0          ,   //控制信号c0
    input   wire            c1          ,   //控制信号c1
    input   wire            de          ,   //使能信号
    output  reg     [9:0]   data_out        //输出编码后的10bit数据
);
 
parameter   DATA_OUT0   =   10'b1101010100,
            DATA_OUT1   =   10'b0010101011,
            DATA_OUT2   =   10'b0101010100,
            DATA_OUT3   =   10'b1010101011;
 
//wire  define
wire            condition_1 ;   //条件1
wire            condition_2 ;   //条件2
wire            condition_3 ;   //条件3
wire    [8:0]   q_m         ;   //第一阶段转换后的9bit数据
 
//reg   define
reg     [3:0]   data_in_n1  ;   //待编码数据中1的个数
reg     [7:0]   data_in_reg ;   //待编码数据打一拍
reg     [3:0]   q_m_n1      ;   //转换后9bit数据中1的个数
reg     [3:0]   q_m_n0      ;   //转换后9bit数据中0的个数
reg     [4:0]   cnt         ;   //视差计数器,0-1个数差别,最高位为符号位
reg             de_reg1     ;   //使能信号打一拍
reg             de_reg2     ;   //使能信号打两拍
reg             c0_reg1     ;   //控制信号c0打一拍
reg             c0_reg2     ;   //控制信号c0打两拍
reg             c1_reg1     ;   //控制信号c1打一拍
reg             c1_reg2     ;   //控制信号c1打两拍
reg     [8:0]   q_m_reg     ;   //q_m信号打一拍
 
//data_in_n1:待编码数据中1的个数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_in_n1  <=  4'd0;
    else
        data_in_n1  <=  data_in[0] + data_in[1] + data_in[2]
                        + data_in[3] + data_in[4] + data_in[5]
                        + data_in[6] + data_in[7];
 
//data_in_reg:待编码数据打一拍
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_in_reg <=  8'b0;
    else
        data_in_reg <=  data_in;
 
//condition_1:条件1
assign  condition_1 = ((data_in_n1 > 4'd4) || ((data_in_n1 == 4'd4)
                        && (data_in_reg[0] == 1'b0)));
 
//q_m:第一阶段转换后的9bit数据
assign q_m[0] = data_in_reg[0];
assign q_m[1] = (condition_1) ? (q_m[0] ^~ data_in_reg[1]) : (q_m[0] ^ data_in_reg[1]);
assign q_m[2] = (condition_1) ? (q_m[1] ^~ data_in_reg[2]) : (q_m[1] ^ data_in_reg[2]);
assign q_m[3] = (condition_1) ? (q_m[2] ^~ data_in_reg[3]) : (q_m[2] ^ data_in_reg[3]);
assign q_m[4] = (condition_1) ? (q_m[3] ^~ data_in_reg[4]) : (q_m[3] ^ data_in_reg[4]);
assign q_m[5] = (condition_1) ? (q_m[4] ^~ data_in_reg[5]) : (q_m[4] ^ data_in_reg[5]);
assign q_m[6] = (condition_1) ? (q_m[5] ^~ data_in_reg[6]) : (q_m[5] ^ data_in_reg[6]);
assign q_m[7] = (condition_1) ? (q_m[6] ^~ data_in_reg[7]) : (q_m[6] ^ data_in_reg[7]);
assign q_m[8] = (condition_1) ? 1'b0 : 1'b1;
 
//q_m_n1:转换后9bit数据中1的个数
//q_m_n0:转换后9bit数据中0的个数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            q_m_n1  <=  4'd0;
            q_m_n0  <=  4'd0;
        end
    else
        begin
            q_m_n1  <=  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_n0  <=  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]);
        end
 
//condition_2:条件2
assign  condition_2 = ((cnt == 5'd0) || (q_m_n1 == q_m_n0));
 
//condition_3:条件3
assign  condition_3 = (((~cnt[4] == 1'b1) && (q_m_n1 > q_m_n0))
                        || ((cnt[4] == 1'b1) && (q_m_n0 > q_m_n1)));
 
//数据打拍,为了各数据同步
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            de_reg1 <=  1'b0;
            de_reg2 <=  1'b0;
            c0_reg1 <=  1'b0;
            c0_reg2 <=  1'b0;
            c1_reg1 <=  1'b0;
            c1_reg2 <=  1'b0;
            q_m_reg <=  9'b0;
        end
    else
        begin
            de_reg1 <=  de;
            de_reg2 <=  de_reg1;
            c0_reg1 <=  c0;
            c0_reg2 <=  c0_reg1;
            c1_reg1 <=  c1;
            c1_reg2 <=  c1_reg1;
            q_m_reg <=  q_m;
        end
 
//data_out:输出编码后的10bit数据
//cnt:视差计数器,0-1个数差别,最高位为符号位
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            data_out    <=  10'b0;
            cnt         <=  5'b0;
        end
    else
        begin
            if(de_reg2 == 1'b1)
                begin
                    if(condition_2 == 1'b1)
                        begin
                            data_out[9]     <=  ~q_m_reg[8]; 
                            data_out[8]     <=  q_m_reg[8]; 
                            data_out[7:0]   <=  (q_m_reg[8]) ? q_m_reg[7:0] : ~q_m_reg[7:0];
                            cnt <=  (~q_m_reg[8]) ? (cnt + q_m_n0 - q_m_n1) : (cnt + q_m_n1 - q_m_n0);
                        end
                    else
                        begin
                            if(condition_3 == 1'b1)
                                begin
                                    data_out[9]     <= 1'b1;
                                    data_out[8]     <= q_m_reg[8];
                                    data_out[7:0]   <= ~q_m_reg[7:0];
                                    cnt <=  cnt + {q_m_reg[8], 1'b0} + (q_m_n0 - q_m_n1);
                                end
                            else
                                begin
                                    data_out[9]     <= 1'b0;
                                    data_out[8]     <= q_m_reg[8];
                                    data_out[7:0]   <= q_m_reg[7:0];
                                    cnt <=  cnt - {~q_m_reg[8], 1'b0} + (q_m_n1 - q_m_n0);
                                end
                            
                        end
                end
            else
                begin
                    case    ({c1_reg2, c0_reg2})
                        2'b00:  data_out <= DATA_OUT0;
                        2'b01:  data_out <= DATA_OUT1;
                        2'b10:  data_out <= DATA_OUT2;
                        default:data_out <= DATA_OUT3;
                    endcase
                    cnt <=  5'b0;
                end
        end
 
endmodule

说明:这里本人也没看懂仅贴图供大家学习,同时推荐观看B站野火视频 149-第四十三讲-HDMI显示器驱动设计与验证(五)_哔哩哔哩_bilibili。

2.3.2 并行转串行模块
     实现并行到串行转换、单端信号转差分信号、单沿采样转双沿采样。

 

并行转串行模块输入输出信号信号功能描述:

 

       注意: 时钟信号 clk_1x 与 clk_5x,时钟频率为 5 倍关系,因为并行数据信号 par_data 位宽 10bit,若转换为串行信号,需要在时钟信号 clk_1x 的一个时钟周期内完成数据转换,故转换后的串行数据信号的同步时钟频率必须为 clk_1x 的 10 倍,使用双沿采样则为 5 倍。

      不直接采用 10 倍时钟而是使用 5 倍时钟加上 ODDR2 双边沿转换的原因,是对于 10 倍信号内部逻辑约束难以达到要求,而且使用 5 倍时钟发热量也远低于 10 倍。

     如何实现5 倍时钟双边沿输出数据以及单端转差分呢?

     在软件中打开原语的模板,进行调用双数据速率原语 ODDR2 和单端转差分的原语,具体步骤如下图所示。 原语可以看成是一种 IP 核,只不过实现的功能较为简单,也更加接近硬件,能直接调用 FPGA 内部的一些器件,实现一些基础功能。

 

 

 

 右边可看到 ODDR2 和OBUFDS 原语,选择复制添加到工程文件即可例化。

 波形图绘制

       模块输入的是位宽 10bit 的并行数据 par_data, clk_1x 时钟信号同步下的 par_data 数据是如何转换为 clk_5x 时钟信号下的 datain_h、 datain_l 数据信号。

 

      第一步:将输入的 10bit 并行数据 par_data 拆分为两个位宽 5bit 的数据信号,变量 data_rise[4:0]、变量 data_fall[4:0]。拆分规则 : 将 会 在 时 钟 上 升 沿 输 出 的 par_data[8] 、 par_data[6] 、 par_data[4] 、 par_data[2] 、par_data[0]赋值给变量 data_rise[4:0];将会在时钟下降沿输出的 par_data[9]、 par_data[7]、par_data[5]、 par_data[3]、 par_data[1]赋值给变量 data_fall[4:0]。

     第二步:声明计数器 cnt,以 clk_5x 为计数时钟进行循环计数,计数范围 0-4,每个时钟周期自加 1。当 cnt 计数值为最大值 4 时,将拆分得到的变量 data_rise、 data_fall 分别赋值给 data_rise_s、 data_fall_s;

    第三步:将 data_rise_s[0]、 data_fall_s[0]分别写入 ODDR2 原语的 D0, D1 接口;同时,每个时钟周期将 data_rise_s、 data_fall_s 右移一位,进行输出。同时再调用 OBUFDS 原语,将串行双沿采样信号 data 写入 OBUFDS 原语的输入接口 I,输出的串行双沿采样信号 O 与 OB 与之前生成的串行双沿采样信号,构成差分信号对。

RTL代码编写

`timescale  1ns/1ns
 
module par_to_ser
(
    input   wire            clk_5x      ,   //输入系统时钟
    input   wire    [9:0]   par_data    ,   //输入并行数据
    output  wire            ser_data_p  ,   //输出串行差分数据
    output  wire            ser_data_n      //输出串行差分数据
);
 
//wire  define
wire data;
wire    [4:0]   data_rise = {par_data[8],par_data[6],
                            par_data[4],par_data[2],par_data[0]};
wire    [4:0]   data_fall = {par_data[9],par_data[7],
                            par_data[5],par_data[3],par_data[1]};
 
//reg   define
reg     [4:0]   data_rise_s = 0;
reg     [4:0]   data_fall_s = 0;
reg     [2:0]   cnt = 0;
 
 
always @ (posedge clk_5x)
    begin
        cnt <= (cnt[2]) ? 3'd0 : cnt + 3'd1;
        data_rise_s  <= cnt[2] ? data_rise : data_rise_s[4:1];
        data_fall_s  <= cnt[2] ? data_fall : data_fall_s[4:1];
 
    end
 
//ODDR2原语 
//将单边沿时钟信号转换为双边沿时钟信号
//5倍时钟双边沿输出数据等价为10倍时钟单边沿输出数据
ODDR2 #(
   .DDR_ALIGNMENT("NONE"), //设置输出对齐方式 "NONE", "C0" or "C1" 
   .INIT         (1'b0  ), // 设置初始化输出电平
   .SRTYPE       ("SYNC")  // 同步复位 "SYNC" or 异步复位"ASYNC" set/reset
) ODDR2_inst0 (
   .Q (data          ),   //输出ddr双边沿数据
   .C0(~clk_5x       ),   //上升沿时钟
   .C1(clk_5x        ),   // 下降沿时钟
   .CE(1'b1          ), // 使能输入
   .D0(data_rise_s[0]), // 上升沿数据
   .D1(data_fall_s[0]), // 上升沿数据
   .R (1'b0          ),   // 复位输入,不复位
   .S (1'b0          )    // 置位输入,不置位
);
 
//OBUFDS原语
//将单端信号转换为差分信号,约束为TMDS33电平
OBUFDS #(
   .IOSTANDARD("TMDS_33") //约束电平为TMDS33
) OBUFDS_inst (
   .O (ser_data_p), //差分信号正极性输出
   .OB(ser_data_n), //差分信号正极性输出
   .I (data      )  //单端信号输入 
);
 
endmodule

2.4 顶层仿真验证
`timescale  1ns/1ns
 
module  tb_hdmi_colorbar();
//wire  define
wire            ddc_scl     ;
wire            ddc_sda     ;
wire            tmds_clk_p  ;
wire            tmds_clk_n  ;
wire    [2:0]   tmds_data_p ;
wire    [2:0]   tmds_data_n ;
 
//reg   define
reg             sys_clk     ;
reg             sys_rst_n   ;
 
 
//sys_clk,sys_rst_n初始赋值
initial
    begin
        sys_clk     =   1'b1;
        sys_rst_n   <=  1'b0;
        #200
        sys_rst_n   <=  1'b1;
    end
 
//sys_clk:产生时钟
always  #10 sys_clk = ~sys_clk  ;
 
 
 
//------------- hdmi_colorbar_inst -------------
hdmi_colorbar   hdmi_colorbar_inst
(
    .sys_clk     (sys_clk    ),   //输入工作时钟,频率50MHz
    .sys_rst_n   (sys_rst_n  ),   //输入复位信号,低电平有效
                  
    .ddc_scl     (ddc_scl    ),
    .ddc_sda     (ddc_sda    ),
    .tmds_clk_p  (tmds_clk_p ),
    .tmds_clk_n  (tmds_clk_n ),   //HDMI时钟差分信号
    .tmds_data_p (tmds_data_p),
    .tmds_data_n (tmds_data_n)    //HDMI图像差分信号
);
 
endmodule
 

 

 


3.总结
      HDMI显示是在vga的基础上,增加了TMDS传输技术的显示方式。难点就在于理解TMDS中的8bit转10bit编码技术。

说明:

       本人使用的是野火家Xilinx Spartan6系列开发板及配套教程主要用于自我学习,以上内容如有疑惑或错误欢迎评论区指出,或者移步B站观看野火家视频教程。

开发软件:ise14.7     仿真:modelsim 10.5 

如需上述资料私信或留下邮箱。