FPGA—HDMI 显示器驱动设计与验证(附代码)

这篇具有很好参考价值的文章主要介绍了FPGA—HDMI 显示器驱动设计与验证(附代码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1.理论

2.实操

2.1 顶层模块

2.2 时钟生成模块

2.3 HDMI 驱动控制模块

2.3.1 编码模块

2.3.2 并行转串行模块

2.4 顶层仿真验证

3.总结

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驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

 表一: 接口引脚定义

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

       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驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

                                                             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 信号的数据和时钟信号分别分配到独立的传输通道发送出去。接收端接收来自发送端的串行信号,对其进行解码和串/并转换,然后发送到显示器的控制端。与此同时也接收时钟信号,以实现同步。 流程框图如下图所示。

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

信源端内部流程框图:

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

                                                           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 根线来传输信号,一根线上传输原来的信号,另一根线上传输与原来信号相反的信号。这样接收端就可以通过让一根线上的信号减去另一根线上的信号的方式来屏蔽电磁干扰,从而得到正确的信号。 原理图如下图所示。

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

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


2.实操

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

 硬件资源:

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

 HDMI 部分原理图

2.1 顶层模块

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

 HDMI 彩条显示实验整体框图

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

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

       观察上图可知,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 时钟,具体原因后面讲。

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

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

2.3 HDMI 驱动控制模块

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

                                                      HDMI 驱动控制模块框图

HDMI 驱动控制模块简介:

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

        由上可知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 的编码。

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

 依据上面流程图编写编码代码,编码模块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 并行转串行模块

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

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

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

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

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

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

 波形图绘制

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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

      第一步:将输入的 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

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

hdmi驱动,FPGA_Xilinx Spartan6基础入门,fpga开发

3.总结

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

说明:

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

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

如需上述资料私信或留下邮箱。文章来源地址https://www.toymoban.com/news/detail-694671.html

到了这里,关于FPGA—HDMI 显示器驱动设计与验证(附代码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Macbook外接显示器 HDMI 不显示

    朋友,你是否也有Macbook用HDMI接上了外接显示器但是显示不出来的烦恼? 你是否也试尽了网上各种方法:调分辨率、调刷新率、重启电脑、把HDMI线拔了插插了拔,就差买根新线或者更新系统了? _ 我在接近绝望的时候突然在油管看到了一个让人极度无语的解决方法,如果你也

    2024年02月11日
    浏览(80)
  • 树莓派魔镜MagicMirror —— 3 HDMI连接显示器

    本系列文章仅做作业做的记录!    树莓派魔镜MagicMirror: 树莓派魔镜MagicMirror —— 1 前期准备工作 树莓派魔镜MagicMirror —— 2 烧写系统卡 树莓派魔镜MagicMirror —— 3 HDMI连接显示器 树莓派魔镜MagicMirror —— 4 系统安装与配置 树莓派魔镜MagicMirror —— 5 配置无线网络 树莓派

    2024年02月10日
    浏览(50)
  • HP笔记本HDMI检测不到外接显示器

    我的笔记本电脑由于屏幕尺寸不大,所以搞了一个更大的显示器作为外接屏幕。 但是每隔一段时间都会出现 检测不到外接显示器 的情况, 具体表现如下: 使用拓展坞(type-C的)连接笔记本显示器可以正常显示,但是受限于拓展坞传输效率,显示画面不到30帧。这说明 显示

    2024年02月10日
    浏览(74)
  • 关于HDMI如何连接显示器,看这篇文章就够了

    使用HDMI(高清多媒体接口)电缆将显示器连接到计算机,可以提供一种简单方便的方式来增强你的观看体验。HDMI是一种广泛使用的数字视频和音频接口,可实现视频和音频信号的高质量未压缩传输。使用HDMI,你可以在显示器上享受令人惊叹的视觉效果和水晶般清晰的声音,

    2024年02月01日
    浏览(92)
  • HDMI接口的计算机外接DP接口的显示器

    2019年8月入手了一台HKCGX329S显示器(31.5英寸, 144hz, 1500R曲面屏), 用来做PC主机的显示器. HKCGX329S 显示接口为VGA和DP. 当时买了转接线(VGA转miniDP, 因为PC主机外接显卡的接口是miniDP), 在台式机上使用正常, 说明显示器没问题. 因为DP比VGA的显示效果好, 当时也尝试买了DP转miniDP的线, 好

    2024年02月10日
    浏览(66)
  • Macbook用HDMI(非HDMI2.0)外接4K显示器1080p只能30Hz的解决方法

    这个问题困扰我很久了,我用的是比较老的4K显示器,不支持HDMI2.0,接上Macbook的时候4K分辨率不支持60Hz,最高只能30Hz,用起来卡卡的,这个也是正常的。但我调到1080p还是只能选择30Hz,这就很郁闷,看了下显示器设置,还是显示出入的是4K分辨率,怪不得不支持60Hz。 这一度

    2024年02月16日
    浏览(220)
  • 惠普光影精灵7type-c转HDMI拓展坞连接显示器,突然不能识别-解决办法

    关机后,拔掉电源适配器,移除全部外接设备,按住电源键并保持30秒再松开,之后,插电源适配器,开机观察。 具体做法是:首先把电脑先关机,关机状态下拔掉所有外接设备包括电源适配器,然后按住开机键30秒左右。操作完成后,正常开机就行,可以释放笔记本内的静

    2024年02月12日
    浏览(105)
  • 【已解决】笔记本HDMI接口外接显示器调不了分辨率,无信号

    最近用笔记本HDMI接口外接显示器突然遇到拓展屏调不了分辨率,无信号问题,困扰了我很久,经过不懈的尝试,终于解决了! 调不了分辨率大多情况下是显卡驱动问题,可以更新驱动,卸载重装 最简单的方法就是换一台笔记本电脑,连接看能否正常显示,正常显示则问题出在

    2024年02月09日
    浏览(127)
  • 笔记本通过HDMI接口扩展显示器,微信/Outlook等界面模糊变清晰的解决办法

    解决方案: 第一步:鼠标右键打开微信快捷方式,选择‘属性’,找到‘兼容性’,选择‘ 更改高DPI设置’ 第二步:高DPI缩放替代:勾选✔ ‘替代高DPI缩放行为’ 第三步:点击“确定”。 第四步:重新启动微信,微信界面的字体显示清晰了   解决方法: 点击Outlook【文件

    2024年02月03日
    浏览(61)
  • [桌面运维]PC常用的视频接口,显示器VGA、DVI、HDMI、DP、USB-C接口的认识和应用

    ⬜⬜⬜ 🐰🟧🟨🟩🟦🟪(*^▽^*)欢迎光临 🟧🟨🟩🟦🟪🐰⬜⬜⬜ ✏️write in front✏️ 📝个人主页:陈丹宇jmu 🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​ 🙉联系作者🙈by QQ:813942269🐧 🌈致亲爱的读者:很高兴你能看到我的文章,希望我的文章可以帮助到你,祝万事

    2024年02月07日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包