06_HDMI 显示器驱动设计与验证
- 1. HDMI 接口及引脚定义
- 1.1 HDMI A Type 接口引脚图
- 1.2 HDMI A Type 接口引脚定义
- 2. HDMI 原理
- 2.1 HDMI 显示原理
- 2.2 TMDS 传输原理
- 3. 实验目标
- 4. 程序框图
- 4.1 顶层模块
- 4.2 时钟生成模块
- 4.3 HDMI 驱动控制模块
- 4.3.1 第一个步骤
- 4.3.2 编码模块参考流程图
- 4.3.3 第二个步骤
- 4.3.4 ALTDDIO_OUT IP 核框图
- 4.3.5 控制模块
- 5. RTL
- 5.1 encode
- 5.2 hdmi_ctrl
- 5.3 par_to_ser
- 5.4 hdmi_colorbar
- 6. Testbench
1. HDMI 接口及引脚定义
1.1 HDMI A Type 接口引脚图
1.2 HDMI A Type 接口引脚定义
2. HDMI 原理
2.1 HDMI 显示原理
2.2 TMDS 传输原理
输出的是差分信号, 都是一对一对的
要经过编码和串并转换
25MHZ 下 一个时钟周期是 8 bit 的并行数据,再此刻时钟下 要经过编码 变成 10 bit 数据。然后10 bit 数据要再此时钟下进行串行数据的转换。 则需要 10 * 25 = 250 MHZ 的时钟,因为 并串转换 是上升沿和下降沿都可以 所以需要 125 MHZ 即可。
需要 2 步
- 8->10 编码
- 10 差分 串
3. 实验目标
实验目标:编写 HDMI 驱动,使用 FPGA 开发板驱动 HDMI 显示器显示十色等宽彩条, HDMI 显示模式为 640*480@60。
4. 程序框图
4.1 顶层模块
Rgb 是 565 rgb 是888
4.2 时钟生成模块
HDMI 显示模式为 640*480@60,时钟频率为 25MHz,而板卡晶振传入时钟频率为 50MHz。时钟生成模块的作用就是将 50MHz 晶振时钟分频为 25MHz 的 HDMI 工作时钟;除此之外,还要生成25MHz 时钟的 5 倍频 125MHz 时钟,125MHz 时钟的具体用途会在后文讲到。
4.3 HDMI 驱动控制模块
4.3.1 第一个步骤
编码
4.3.2 编码模块参考流程图
4.3.3 第二个步骤
串并转换
4.3.4 ALTDDIO_OUT IP 核框图
ALTDDIO_OUT IP 核接口信号描述
ALTDDIO_OUT IP 核时序图
ALTDDIO_OUT IP 核框图
4.3.5 控制模块
5. RTL
5.1 encode
`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 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
5.2 hdmi_ctrl
`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 //蓝色分量差分信号
);
//********************************************************************//
//****************** 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
5.3 par_to_ser
`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 //输出串行差分数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//wire define
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 -------------
ddio_out ddio_out_inst0
(
.datain_h (data_rise_s[0] ),
.datain_l (data_fall_s[0] ),
.outclock (~clk_5x ),
.dataout (ser_data_p )
);
//------------- ddio_out_inst1 -------------
ddio_out ddio_out_inst1
(
.datain_h (~data_rise_s[0]),
.datain_l (~data_fall_s[0]),
.outclock (~clk_5x ),
.dataout (ser_data_n )
);
endmodule
5.4 hdmi_colorbar
`timescale 1ns/1ns
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_gen clk_gen_inst
(
.areset (~sys_rst_n ), //输入复位信号,高电平有效,1bit
.inclk0 (sys_clk ), //输入50MHz晶振时钟,1bit
.c0 (vga_clk ), //输出VGA工作时钟,频率25Mhz,1bit
.c1 (clk_5x ),
.locked (locked ) //输出pll locked信号,1bit
);
//------------- 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
6. Testbench
`timescale 1ns/1ns
module tb_hdmi_colorbar();
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//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 ;
//********************************************************************//
//**************************** Clk And Rst ***************************//
//********************************************************************//
//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 ;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------- 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