跳转至

效果演示

理论学习

在前面我们学习了使用VGA接口实现等宽彩条和RGB232图片显示的验证设计,VGA传输的是模拟信号,因此在传输过程中极易受到外界干扰源的影响,传输的信号会产生畸变,就会导致我们传输的图像出现问题。

HDMI--高清多媒体接口,是在2002.12,HDMI1.0问世,整个传输的原理使用TMDS编码技术,可以实现20m的无争议传输。除此之外,HDMI可以在一根线上实现数字信号和音频信号同步传输

1. HDMI引脚

HDMI引脚都是使用19Pin的引脚,下面是它的引脚说明,根据它的引脚功能,可以划分为四类:最重要的是四对传输信号(四对差分对)

  1. TMDS: Pin(1-12),负责发送音频视频以及各种辅助数据的。遵循DVI接口1.0
  2. DDC通道:Pin(15,16,17),这个通道实际上就是一个I2C接口,DDC时钟线对应SCL,数据线对应SDA。作用是获得发送能力和接收能力。在我们HDMI设计中只需要单向获知接收端的能力
  3. CEC通道:Pin(13,17),这个引脚是复用的引脚,这是一个预留的线路
  4. 其他通道:Pin(14,18,19),电源和地以及热插拔检测

2. HDMI显示原理

从图中可以看到,包含四对差分线,包含三个数据通道和一个时钟通道,用来传输视频、音频以及辅助数据。

下面是DDC通道,我们的发送端,可以 通过这个通道读取接收端IIC的模式相关信息

CEC通道是必须预留的通道,可以不使用但是要有

接着就是HEAC通道,可以选择hdmi的以太网和音频返回,缩写就是HEAC,他在连接的设备中提供以太网兼容的一个网络数据和一个TMDS相对方向的音频返回通道。说了这么多,我还是没有理解这到底是有什么用

最后还有一个检测热插拔的通道HPD。

总得来说,要使得HDMI正确显示,其中四对差分信号必不可少,另外我们需要提供RGB图像数据、音频以及控制信号(行场同步信号,使能信号),接着,HDMI将这些信号转化成四对差分信号传输出去,那么是怎么将这些数据转化成差分信号用来传输的呐?这就需要使用到TMDS传输原理了

3. TMDS传输原理

TMDS传输原理

TMDS,英文全称“Transition Minimized Differential Signaling”,译为“最小化传输差分信号”。

TMDS传输原理:这里我们传输的图像数据是并行的24位RGB图像数据,也就是RGB888, 然后将这些数据在行场同步下,以及我们的是能信号和时钟信号一起进行一个编码,然后进行并串转换,然后将这些数据分配到独立的端口发送出去。随后接收端接收到来自发送端发送的串行数据后进行解码和串并转换,接着将数据发送到接收端的控制端,使用我们输入的时钟信号进行同步,最后显示在显示器上面。其实我们只需要实现左边一部分的功能就可以了(实现编码和并串转换),接收端不需要进行考虑

TMDS编码

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

要实现TMDS通道传输,首先要将传入的8 位的并行数据进行编码、并/串转换,添加第9位编码位,如下图所示。

将 8 位并行数据发送到 TMDS 接收端;将接收到的8位数据并/串转换;随后进行最小化传输处理,加上第 9 位,即编码过程。

添加编码位的数据需要进行直流均衡处理。直流平衡(DC-balanced)就是指在编码过程中保证信道中直流偏移为零,使信道中传输数据包含的1与0的个数相同。方法是在添加编码位的 9 位数据的后面加上第 10 位数据,保证10位数据中1与0个数相同。这样,传输的数据趋于直流平衡,使信号对传输线的电磁干扰减少,提高信号传输的可靠性。

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

经过上述处理,我们得到了可以进行TMDS通道传输的差分信号,使用这种方法对24位图像数据(8 位R信号、8 位G 信号、8 位B 信号)和时钟信号进行处理,将4对差分信号通过HDMI接口发到接收设备;接收设备通过解码等一系列操作,实现图像后音频再现。

在我们实际使用的过程中,有一部分开发板是使用了VGA_HDMI的硬件芯片:SII9134或者其他,因此我们不需要做HDMI它的一个编码,只需要交付VGA并行数据到这个芯片上就可以了,这个芯片会自动转换为差分串行信号了。

实战演练

接着,我们就要开始实战演练了,这次的实验目标就是在640*480@60的显示器上显示等宽彩条的。

关于VGA显示的部分,可以参考

FPGA:VGA显示器驱动设计 - quincy的日记 (zikwq.github.io)

FPGA:VGA显示器驱动--字符游走 - quincy的日记 (zikwq.github.io)

1. 芯片原理图

使用的是米联客mz7030fa开发板,芯片型号:xc7z030ffg676-2,开发环境:vivado21.1

(不建议购买,配套的资料不是这一款芯片型号,里面的例程需要手动重新进行管脚约束很麻烦,特别涉及到例如MIG IP核的时候,需要重新配置IP的参数~~)

2. 程序流程图

这个流程图很清晰易懂,前面是我们设计的VGA显示,后面仅仅跟上了hdmi_ctrl模块就将VGA工程迁移到了HDMI上了,但是注意的是hdmi_ctrl模块中有clk_5x需要我们提前在时钟管理模块中新增一下,,下面是详细的hdmi_ctrl模块流程框图:

在这个hdmi_ctrl模块流程框图中,主要进行了两个功能的编写

1). 编码模块:对输入的色彩信息和行场同步信号以及使能信号进行编码

2). 并行转串行输出差分信号模块:将编码后的10bit并行数据进行转换,这里转换使用clk_5x时钟进行处理, 输出一个串行的差分信号

hdmi_ctrl.v 输入输出信号功能描述

信号 位宽 类型 功能描述
clk_1x 1Bit Input 工作时钟,频率25MHz
clk_5x 1Bit Input 工作时钟,频率125MHz
sys_rst_n 1Bit Input 复位信号,低电平有效
hsync 1Bit Input 行同步信号
vsync 1Bit Input 场同步信号
de 1Bit Input 使能信号
rgb_red 8Bit Input RGB图像色彩信息红色分量
rgb_green 8Bit Input RGB图像色彩信息绿色分量
rgb_blue 8Bit Input RGB图像色彩信息蓝色分量
hdmi_clk_p 1Bit Output HDMI时钟差分信号
hdmi_clk_n 1Bit Output HDMI时钟差分信号
hdmi_r_p 1Bit Output HDMI红色分量差分信号
hdmi_r_n 1Bit Output HDMI红色分量差分信号
hdmi_g_p 1Bit Output HDMI绿色分量差分信号
hdmi_g_n 1Bit Output HDMI绿色分量差分信号
hdmi_b_p 1Bit Output HDMI蓝色分量差分信号
hdmi_b_n 1Bit Output HDMI蓝色分量差分信号

程序设计

``verilog vga_pic.vtimescale 1ns/1ns //////////////////////////////////////////////////////////////////////// // Author : EmbedFire // Create Date : 2019/03/12 // Module Name : vga_pic // Project Name : hdmi_colorbar // Target Devices: Altera EP4CE10F17C8N // Tool Versions : Quartus 13.0 // Description : 图像数据生成模块 // // Revision : V1.0 // Additional Comments: // // 实验平台: 野火_征途Pro_FPGA开发板 // 公司 : http://www.embedfire.com // 论坛 : http://www.firebbs.cn // 淘宝 : https://fire-stm32.taobao.com ////////////////////////////////////////////////////////////////////////

module vga_pic ( input wire vga_clk , //输入工作时钟,频率25MHz input wire sys_rst_n , //输入复位信号,低电平有效 input wire [11:0] pix_x , //输入VGA有效显示区域像素点X轴坐标 input wire [11:0] pix_y , //输入VGA有效显示区域像素点Y轴坐标

output  reg     [15:0]  pix_data        //输出像素点色彩信息

);

//**********// //** Parameter and Internal Signal **// //***********// //parameter define parameter H_VALID = 12'd640 , //行有效数据 V_VALID = 12'd480 ; //场有效数据

parameter RED = 16'hF800, //红色 ORANGE = 16'hFC00, //橙色 YELLOW = 16'hFFE0, //黄色 GREEN = 16'h07E0, //绿色 CYAN = 16'h07FF, //青色 BLUE = 16'h001F, //蓝色 PURPPLE = 16'hF81F, //紫色 BLACK = 16'h0000, //黑色 WHITE = 16'hFFFF, //白色 GRAY = 16'hD69A; //灰色

//**********// //***** Main Code ****// //*********// //pix_data:输出像素点色彩信息,根据当前像素点坐标指定当前像素点颜色数据 always@(posedge vga_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) pix_data <= 16'd0; else if((pix_x >= 0) && (pix_x < (H_VALID/10)1)) pix_data <= RED; else if((pix_x >= (H_VALID/10)1) && (pix_x < (H_VALID/10)2)) pix_data <= ORANGE; else if((pix_x >= (H_VALID/10)2) && (pix_x < (H_VALID/10)3)) pix_data <= YELLOW; else if((pix_x >= (H_VALID/10)3) && (pix_x < (H_VALID/10)4)) pix_data <= GREEN; else if((pix_x >= (H_VALID/10)4) && (pix_x < (H_VALID/10)5)) pix_data <= CYAN; else if((pix_x >= (H_VALID/10)5) && (pix_x < (H_VALID/10)6)) pix_data <= BLUE; else if((pix_x >= (H_VALID/10)6) && (pix_x < (H_VALID/10)7)) pix_data <= PURPPLE; else if((pix_x >= (H_VALID/10)7) && (pix_x < (H_VALID/10)8)) pix_data <= BLACK; else if((pix_x >= (H_VALID/10)8) && (pix_x < (H_VALID/10)9)) pix_data <= WHITE; else if((pix_x >= (H_VALID/10)*9) && (pix_x < H_VALID)) pix_data <= GRAY; else pix_data <= BLACK;

endmodule


```verilog vga_ctrl.v
`timescale  1ns/1ns
////////////////////////////////////////////////////////////////////////
// Author        : EmbedFire
// Create Date   : 2019/03/12
// Module Name   : vga_ctrl
// Project Name  : hdmi_colorbar
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : VGA控制模块
// 
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com
////////////////////////////////////////////////////////////////////////

module  vga_ctrl
(
    input   wire            vga_clk     ,   //输入工作时钟,频率25MHz
    input   wire            sys_rst_n   ,   //输入复位信号,低电平有效
    input   wire    [15:0]  pix_data    ,   //输入像素点色彩信息

    output  wire    [11:0]  pix_x       ,   //输出VGA有效显示区域像素点X轴坐标
    output  wire    [11:0]  pix_y       ,   //输出VGA有效显示区域像素点Y轴坐标
    output  wire            hsync       ,   //输出行同步信号
    output  wire            vsync       ,   //输出场同步信号
    output  wire            rgb_valid   ,
    output  wire    [15:0]  rgb             //输出像素点色彩信息
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter H_SYNC    =   10'd96  ,   //行同步
          H_BACK    =   10'd40  ,   //行时序后沿
          H_LEFT    =   10'd8   ,   //行时序左边框
          H_VALID   =   10'd640 ,   //行有效数据
          H_RIGHT   =   10'd8   ,   //行时序右边框
          H_FRONT   =   10'd8   ,   //行时序前沿
          H_TOTAL   =   10'd800 ;   //行扫描周期
parameter V_SYNC    =   10'd2   ,   //场同步
          V_BACK    =   10'd25  ,   //场时序后沿
          V_TOP     =   10'd8   ,   //场时序上边框
          V_VALID   =   10'd480 ,   //场有效数据
          V_BOTTOM  =   10'd8   ,   //场时序下边框
          V_FRONT   =   10'd2   ,   //场时序前沿
          V_TOTAL   =   10'd525 ;   //场扫描周期

//wire  define
wire            pix_data_req    ;   //像素点色彩信息请求信号

//reg   define
reg     [11:0]  cnt_h           ;   //行同步信号计数器
reg     [11:0]  cnt_v           ;   //场同步信号计数器

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//cnt_h:行同步信号计数器
always@(posedge vga_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_h   <=  12'd0   ;
    else    if(cnt_h == H_TOTAL - 1'd1)
        cnt_h   <=  12'd0   ;
    else
        cnt_h   <=  cnt_h + 1'd1   ;

//hsync:行同步信号
assign  hsync = (cnt_h  <=  H_SYNC - 1'd1) ? 1'b1 : 1'b0  ;

//cnt_v:场同步信号计数器
always@(posedge vga_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_v   <=  12'd0 ;
    else    if((cnt_v == V_TOTAL - 1'd1) &&  (cnt_h == H_TOTAL-1'd1))
        cnt_v   <=  12'd0 ;
    else    if(cnt_h == H_TOTAL - 1'd1)
        cnt_v   <=  cnt_v + 1'd1 ;
    else
        cnt_v   <=  cnt_v ;

//vsync:场同步信号
assign  vsync = (cnt_v  <=  V_SYNC - 1'd1) ? 1'b1 : 1'b0  ;

//rgb_valid:VGA有效显示区域
assign  rgb_valid = (((cnt_h >= H_SYNC + H_BACK + H_LEFT)
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID))
                    &&((cnt_v >= V_SYNC + V_BACK + V_TOP)
                    && (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)))
                    ? 1'b1 : 1'b0;

//pix_data_req:像素点色彩信息请求信号,超前rgb_valid信号一个时钟周期
assign  pix_data_req = (((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'b1)
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID - 1'b1))
                    &&((cnt_v >= V_SYNC + V_BACK + V_TOP)
                    && (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)))
                    ? 1'b1 : 1'b0;

//pix_x,pix_y:VGA有效显示区域像素点坐标
assign  pix_x = (pix_data_req == 1'b1)
                ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'b1)) : 12'hfff;
assign  pix_y = (pix_data_req == 1'b1)
                ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 12'hfff;

//rgb:输出像素点色彩信息
assign  rgb = (rgb_valid == 1'b1) ? pix_data : 16'b0 ;

endmodule

``verilog hdmi_colorbar.vtimescale 1ns/1ns //////////////////////////////////////////////////////////////////////// // Author : EmbedFire // Create Date : 2019/11/01 // Module Name : hdmi_colorbar // Project Name : hdmi_colorbar // Target Devices: Altera EP4CE10F17C8N // Tool Versions : Quartus 13.0 // Description : hdmi_colorbar顶层模块 // // Revision : V1.0 // Additional Comments: // // 实验平台: 野火_征�?�Pro_FPGA�?发板 // 公司 : http://www.embedfire.com // 论坛 : http://www.firebbs.cn // 淘宝 : https://fire-stm32.taobao.com ////////////////////////////////////////////////////////////////////////

module hdmi_colorbar ( input wire sys_clk , //输入工作时钟,频率50MHz 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图像差分信号

);

//**********// //** Parameter and Internal Signal **// //***********// //wire define 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;

//rst_n:VGA模块复位信号 assign rst_n = (sys_rst_n & locked); assign ddc_scl = 1'b1; assign ddc_sda = 1'b1;

//**********// //**** Instantiation ***// //************//

//------------- clk_gen_inst ------------- clk_wiz_0 instance_name ( // Clock out ports .clk_out1(vga_clk), // output clk_out1 .clk_out2(clk_5x), // output clk_out2 // Status and control signals .resetn(sys_rst_n), // input resetn .locked(locked), // output locked // Clock in ports .clk_in1(sys_clk)); // input clk_in1

//------------- vga_ctrl_inst ------------- 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_inst ------------- 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_inst ------------- 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


```verilog encode.v
`timescale  1ns/1ns
////////////////////////////////////////////////////////////////////////
// Author        : EmbedFire
// Create Date   : 2019/11/01
// Module Name   : encode
// Project Name  : hdmi_colorbar
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : 8b转10b编码模块
// 
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com
////////////////////////////////////////////////////////////////////////


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 and Internal Signal *******************//
//********************************************************************//
//parameter define
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信号打一拍

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//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

``verilog hdmi_ctrl.vtimescale 1ns/1ns //////////////////////////////////////////////////////////////////////// // Author : EmbedFire // Create Date : 2019/11/01 // Module Name : hdmi_ctrl // Project Name : hdmi_colorbar // Target Devices: Altera EP4CE10F17C8N // Tool Versions : Quartus 13.0 // Description : HDMI控制模块 // // Revision : V1.0 // Additional Comments: // // 实验平台: 野火_征途Pro_FPGA开发板 // 公司 : http://www.embedfire.com // 论坛 : http://www.firebbs.cn // 淘宝 : https://fire-stm32.taobao.com ////////////////////////////////////////////////////////////////////////

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        //蓝色分量差分信号

);

//**********// //** Parameter and Internal Signal **// //***********// wire [9:0] red ; //8b转10b后的红色分量 wire [9:0] green ; //8b转10b后的绿色分量 wire [9:0] blue ; //8b转10b后的蓝色分量

//**********// //**** Instantiate ****// //***********// //------------- 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


```verilog par_to_ser.v
`timescale  1ns/1ns
////////////////////////////////////////////////////////////////////////
// Author        : EmbedFire
// Create Date   : 2019/11/01
// Module Name   : par_to_ser
// Project Name  : hdmi_colorbar
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : 并行转串行�?�单端转差分、单沿转双沿
// 
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征�?�Pro_FPGA�?发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com
////////////////////////////////////////////////////////////////////////

module par_to_ser
(
    input   wire            clk_5x      ,   //输入系统时钟
    input   wire    [9:0]   par_data    ,   //输入并行数据

    output  wire            ser_data_p  ,   //输出串行差分数据
    output  wire            ser_data_n      //输出串行差分数据
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//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

//********************************************************************//
//**************************** Instantiate ***************************//
//********************************************************************//
//------------- ddio_out_inst0 -------------


ODDR #(
    .DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" 
    .INIT(1'b0),    // Initial value of Q: 1'b0 or 1'b1
    .SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC" 
 ) ODDR_inst (
    .Q(data),   // 1-bit DDR output
    .C(clk_5x ),   // 1-bit clock input
    .CE(1'd1), // 1-bit clock enable input
    .D1(data_rise_s[0]), // 1-bit data input (positive edge)
    .D2(data_fall_s[0]), // 1-bit data input (negative edge)
    .R(1'd0),   // 1-bit reset
    .S(1'd0)    // 1-bit set
 );
 // ����OBUFDSԭ��

 OBUFDS #(
      .IOSTANDARD("TMDS_33"), // Specify the output I/O standard
      .SLEW("SLOW")           // Specify the output slew rate
   ) OBUFDS_inst (
      .O(ser_data_p),     // Diff_p output (connect directly to top-level port)
      .OB(ser_data_n),   // Diff_n output (connect directly to top-level port)
      .I(data)      // Buffer input
   );
endmodule

注意:par_to_ser.v模块中包含并行转串行以及单端转差分功能,在hdmi_ctrl.v中将其模块分别对R、G、B、CLK实例化了共四次,其中ODDR和OBUFDS的原语需要根据自己的开发板进行复制

管脚配置

set_property PACKAGE_PIN C8 [get_ports sys_clk]
set_property PACKAGE_PIN V19 [get_ports sys_rst_n]
set_property PACKAGE_PIN J14 [get_ports tmds_clk_p]
set_property PACKAGE_PIN K15 [get_ports {tmds_data_p[2]}]
set_property PACKAGE_PIN G14 [get_ports {tmds_data_p[1]}]
set_property PACKAGE_PIN K13 [get_ports {tmds_data_p[0]}]
set_property IOSTANDARD LVCMOS18 [get_ports ddc_sda]
set_property IOSTANDARD SSTL135 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
set_property IOSTANDARD LVDS [get_ports tmds_clk_p]
set_property IOSTANDARD LVDS [get_ports tmds_clk_n]
set_property PACKAGE_PIN J10 [get_ports ddc_scl]
set_property PACKAGE_PIN B9 [get_ports ddc_sda]
set_property IOSTANDARD LVDS [get_ports {tmds_data_p[2]}]
set_property IOSTANDARD LVDS [get_ports {tmds_data_n[2]}]
set_property IOSTANDARD LVDS [get_ports {tmds_data_p[1]}]
set_property IOSTANDARD LVDS [get_ports {tmds_data_n[1]}]
set_property IOSTANDARD LVDS [get_ports {tmds_data_p[0]}]
set_property IOSTANDARD LVDS [get_ports {tmds_data_n[0]}]
set_property IOSTANDARD LVCMOS18 [get_ports ddc_scl]