一、基础理论
1.1 HDMI的发展(接口资源)

1.2 HDMI与DVI的区别与联系
1.3 HDMI与VGA的图像传输数据流转换的区别与联系
- VGA :在使用 VGA 接口的传输方式中,图像数据从图形处理器(也就是常说的显卡芯片)输
出(此时的信号为数字信号),为了能够变成 VGA 接口传输所需的模拟信号,会使用一片数模转换器转换为模拟信号后再输出到 VGA 接口上。然后模拟信号通过 VGA 电缆传输到 LCD 液晶显示屏的 VGA 接口,由于 TFT 像素面板为数字显示屏,每一个像素的颜色是由一个 TFT 驱动器实时的扫描描绘的。TFT 驱动器的输入要求为数字信号,所以 TFT 驱动器和 VGA 接口之间使用了一片模数转换器将 VGA 接口上的模拟信号转换为数字信号后再送给TFT 驱动器使用。
- HDMI:在使用 HDMI 接口的传输方式中,图像数据从图形处理器(也就是常说的显卡芯片)输
出(此时的信号为数字信号),为了能够将数字信号转换为 HDMI 标准的高速差分信号,会经过一个数据发送器。该数据发送器仅仅是改变了数据的传输方式,将原本的并行数据转换为高速串行数据输出,以符合 HDMI 协议标准,在 HDMI 接口上传输的内容实际还是数字信号。数字信号从显卡的 HDMI 接口经由 HDMI 线缆传输到 LCD 显示器的 HDMI 接口,再由LCD 显示器内的数据接收器将 HDMI 接口上的高速串行数字信号转换为并行数据,提供给TFT 驱动器使用。
数字信号 ——(数据发送器)并转串 ——> (线缆)——> (数据接收器)串转并——>数字信号
1.4 HDMI/DVI数据链路(TMDS编码)
1.4.1 链路框架
- 输入接口层: 输入接口层的信号格式为典型的 RGB 行场同步数字接口,该接口包括数据使能信号 DE(Data Enable)、24 位像素数据(Pixel Data)、6 位的控制数据(包括 HSYNC、VSYNC 和 空信号)和同步时钟信号。
- TMDS发送器:完成对输入接口层的数据和控制信号按照 TMDS 编码方式进行编码,再将编码的数据通过高速串行接口输出,最终将输入接口层的信号编码进 4 个TMDS 链路层中。
- TMDS接收器:将 链路上的高速串行数据接收,解串,TMDS 解码,得到与输入接口层相同的控制信号和数据。
- 输出接口层:将 TMDS 接收器解码得到的数据流和控制信号传递给最终的数据消费者(例如液晶显示器)
1.4.2 TMDS原理与实现(编码并串行化发送)
1.4.2.(a)数据的划分
TMDS 编码包含两个大的内容,传输控制信号的控制段和用来传输图像像素数据的数据段。
一个完整的图像传输TMDS模块包含三个相同的编码和发送模块。每个发模块包含8位的像素数据,对应24位像素数据中单个颜色的8位数据、2个控制信号,这两个控制信号可以分别接行同步、场同步信号,也可以空置接 0。另外还有一个数据有效信号 DE,该信号用来区分控制数据和像素数据。当 DE 信号为高电平的时候,表明当前数据有效,编码器对 8 位的 Data 数据进行编码。当 DE 信号为低电平的时候则对 2 位的控制信号进行编码。
这里,串行发送器使用的时钟信号频率为编码器的5倍。至于为什么是5倍,这是因为串行发送器在发送数据时候是采用双数据速率的方式传输数据的,一个时钟周期可以传输2位信号,所以只需要 5 倍的编码器的时钟即可完成10位数据的及时传输。
1.4.2.(b)TMDS 最小化传输编码原理
1.4.2.(c)TMDS 最小化传输实现原理
1.像素数据周期的编码方式
2.控制周期的编码方式
1.4.2.(d)TMDS 最小化传输编码实现
Verilog代码实现
`timescale 1ns / 1ps
//
// Description: TMDS编码
//
module encode(
clk ,
rst_n ,
din ,
c0 ,
c1 ,
de ,
dout
);
input clk ;//像素时钟输入
input rst_n ;
input [7:0] din ;//数据输入,需要寄存
input c0 ;
input c1 ;
input de ;//数据使能
output reg [9:0] dout ;//编码之后的10位数据
parameter CTL0 = 10'b1101010100;
parameter CTL1 = 10'b0010101011;
parameter CTL2 = 10'b0101010100;
parameter CTL3 = 10'b1010101011;
reg [3:0] n1d ; //统计输入的 8bit 数据中 1 的个数
reg [7:0] din_q ; //同步寄存输入的 8bit 数据(统计需要一拍时间)
reg [3:0] n1q_m ,
n0q_m ; // 统计 q_m 中 1 和 0 的个数
reg [4:0] cnt ; // 计数器差距统计:统计 1 和 0 是否过量发送,最高位(cnt[4])是符号位
// 流水线对齐(同步寄存器 2 拍)
reg [1:0] de_reg;
reg [1:0] c0_reg;
reg [1:0] c1_reg;
reg [8:0] q_m_reg;
wire [8:0] q_m;
wire decision1;
wire decision2;
wire decision3;
assign decision1 = ((n1d > 'd4)||((n1d == 'd4) && din_q == 1'b0)) ? 1'd1 : 1'd0;
assign decision2 = (cnt == 5'h0) || (n1q_m == n0q_m);
assign decision3 = (~cnt[4] && (n1q_m > n0q_m)) || (cnt[4] && (n0q_m > n1q_m));
// 统计每次输入的 8bit 数据中 1 和 0 的个数 流水线输出,同步寄存输入的 8 bit数据 判断条件为统计数据和输入信号的最低位,保持时序同步,所以对输入信号进行打拍
always@(posedge clk,negedge rst_n)begin
if(!rst_n)begin
n1d <= 'd0;
din_q <= 'd0;
end
else begin
n1d <= din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
din_q <= din;
end
end
// 第一步:8 bit -> 9 bit
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 bit -> 10 bit
always @ (posedge clk,negedge rst_n) begin
if(!rst_n)begin
n1q_m <= 'd0;
n0q_m <= 'd0;
end
else begin
n1q_m <= 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 <= 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
end
//信号时序同步
always@(posedge clk,negedge rst_n)begin
if(!rst_n)begin
de_reg <= 'd0;
c0_reg <= 'd0;
c1_reg <= 'd0;
q_m_reg <= 'd0;
end
else begin
de_reg <= {de_reg[0], de};
c0_reg <= {c0_reg[0], c0};
c1_reg <= {c1_reg[0], c1};
q_m_reg <= q_m;
end
end
always@(posedge clk,negedge rst_n)begin
if(!rst_n)begin
dout <= 10'h0;
cnt <= 5'd0;
end
else begin
if (de_reg[1]) begin// 数据周期:发送对应编码的数据
if (decision2) begin
dout[9] <= ~q_m_reg[8];
dout[8] <= q_m_reg[8];
dout[7:0] <= (q_m_reg[8]) ? q_m_reg[7:0] : ~q_m_reg[7:0] ;
cnt <= (~q_m_reg[8]) ? (cnt + n0q_m - n1q_m):(cnt + n1q_m - n0q_m);
end
else begin
if (decision3) begin
dout[9] <= 1'b1 ;
dout[8] <= q_m_reg[8] ;
dout[7:0] <= ~q_m_reg[7:0];
cnt <= cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);
end
else begin
dout[9] <= 1'b0 ;
dout[8] <= q_m_reg[8] ;
dout[7:0] <= q_m_reg[7:0];
cnt <= cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);
end
end
end
else begin // 控制周期:发送控制信号
cnt <= 5'd0;
case ({c1_reg[1], c0_reg[1]})
2'b00: dout <= CTL0;
2'b01: dout <= CTL1;
2'b10: dout <= CTL2;
default: dout <= CTL3;
endcase
end
end
end
endmodule
1.4.2.(e)TMDS 串行发送模块
PS:这边通过调用vivado中的DDR原语实现双数据率传输
Verilog 代码实现
`timescale 1ns / 1ps
//
// Description: 4路信号并行10bit转串行
//
module serdes_4b_10to1(
clkx5,
datain_0,
datain_1,
datain_2,
datain_3,
dataout_0_p,
dataout_0_n,
dataout_1_p,
dataout_1_n,
dataout_2_p,
dataout_2_n,
dataout_3_p,
dataout_3_n
);
input clkx5; // 5x clock input
input [9:0] datain_0; // input data for serialisation
input [9:0] datain_1; // input data for serialisation
input [9:0] datain_2; // input data for serialisation
input [9:0] datain_3; // input data for serialisation
output dataout_0_p; // out DDR data
output dataout_0_n; // out DDR data
output dataout_1_p; // out DDR data
output dataout_1_n; // out DDR data
output dataout_2_p; // out DDR data
output dataout_2_n; // out DDR data
output dataout_3_p; // out DDR data
output dataout_3_n; // out DDR data
reg [2:0] TMDS_mod5 = 0;//模5计数器
reg [4:0] TMDS_shift_0h = 0, TMDS_shift_0l = 0;
reg [4:0] TMDS_shift_1h = 0, TMDS_shift_1l = 0;
reg [4:0] TMDS_shift_2h = 0, TMDS_shift_2l = 0;
reg [4:0] TMDS_shift_3h = 0, TMDS_shift_3l = 0;
wire dataout_0;
wire dataout_1;
wire dataout_2;
wire dataout_3;
//先将每个通道的需要发送的高位(偶数)和低位(奇数)提取出来组成新的2个5位的数据
wire [4:0] TMDS_0_l;
wire [4:0] TMDS_0_h;
wire [4:0] TMDS_1_l;
wire [4:0] TMDS_1_h;
wire [4:0] TMDS_2_l;
wire [4:0] TMDS_2_h;
wire [4:0] TMDS_3_l;
wire [4:0] TMDS_3_h;
assign TMDS_0_l = {datain_0[9],datain_0[7],datain_0[5],datain_0[3],datain_0[1]};
assign TMDS_0_h = {datain_0[8],datain_0[6],datain_0[4],datain_0[2],datain_0[0]};
assign TMDS_1_l = {datain_1[9],datain_1[7],datain_1[5],datain_1[3],datain_1[1]};
assign TMDS_1_h = {datain_1[8],datain_1[6],datain_1[4],datain_1[2],datain_1[0]};
assign TMDS_2_l = {datain_2[9],datain_2[7],datain_2[5],datain_2[3],datain_2[1]};
assign TMDS_2_h = {datain_2[8],datain_2[6],datain_2[4],datain_2[2],datain_2[0]};
assign TMDS_3_l = {datain_3[9],datain_3[7],datain_3[5],datain_3[3],datain_3[1]};
assign TMDS_3_h = {datain_3[8],datain_3[6],datain_3[4],datain_3[2],datain_3[0]};
// 5倍速度移位发送数据
always @(posedge clkx5)begin
if(TMDS_mod5 >= 3'd4)
TMDS_mod5 <= 3'd0;
else
TMDS_mod5 <= TMDS_mod5 + 3'd1;
end
always @(posedge clkx5)begin
if(TMDS_mod5 == 3'd4)begin
TMDS_shift_0h <= TMDS_0_h;
TMDS_shift_0l <= TMDS_0_l;
TMDS_shift_1h <= TMDS_1_h;
TMDS_shift_1l <= TMDS_1_l;
TMDS_shift_2h <= TMDS_2_h;
TMDS_shift_2l <= TMDS_2_l;
TMDS_shift_3h <= TMDS_3_h;
TMDS_shift_3l <= TMDS_3_l;
end
else begin
TMDS_shift_0h <= TMDS_shift_0h[4:1];
TMDS_shift_0l <= TMDS_shift_0l[4:1];
TMDS_shift_1h <= TMDS_shift_1h[4:1];
TMDS_shift_1l <= TMDS_shift_1l[4:1];
TMDS_shift_2h <= TMDS_shift_2h[4:1];
TMDS_shift_2l <= TMDS_shift_2l[4:1];
TMDS_shift_3h <= TMDS_shift_3h[4:1];
TMDS_shift_3l <= TMDS_shift_3l[4:1];
end
end
/
//Xilinx FPGA ODDR
/
//********************* Channel 0*****************************\\
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_0
(
.Q (dataout_0 ), // 1-bit DDR output
.C (clkx5 ), // 1-bit clock input
.CE(1'b1 ), // 1-bit clock enable input
.D1(TMDS_shift_0h[0]), // 1-bit data input (positive edge)
.D2(TMDS_shift_0l[0]), // 1-bit data input (negative edge)
.R (1'b0 ), // 1-bit reset
.S (1'b0 ) // 1-bit set
);
OBUFDS #(
.IOSTANDARD("DEFAULT"), // Specify the output I/O standard
.SLEW ("SLOW" ) // Specify the output slew rate
)
OBUFDS_0
(
.O (dataout_0_p ),// Diff_p output (connect directly to top-level port)
.OB (dataout_0_n ),// Diff_n output (connect directly to top-level port)
.I (dataout_0 ) // Buffer input
);
//********************* Channel 1*****************************\\
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_1
(
.Q (dataout_0 ), // 1-bit DDR output
.C (clkx5 ), // 1-bit clock input
.CE(1'b1 ), // 1-bit clock enable input
.D1(TMDS_shift_1h[0]), // 1-bit data input (positive edge)
.D2(TMDS_shift_1l[0]), // 1-bit data input (negative edge)
.R (1'b0 ), // 1-bit reset
.S (1'b0 ) // 1-bit set
);
OBUFDS #(
.IOSTANDARD("DEFAULT"), // Specify the output I/O standard
.SLEW ("SLOW" ) // Specify the output slew rate
)
OBUFDS_1
(
.O (dataout_1_p ),// Diff_p output (connect directly to top-level port)
.OB (dataout_1_n ),// Diff_n output (connect directly to top-level port)
.I (dataout_1 ) // Buffer input
);
//********************* Channel 2*****************************\\
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_2
(
.Q (dataout_0 ), // 1-bit DDR output
.C (clkx5 ), // 1-bit clock input
.CE(1'b1 ), // 1-bit clock enable input
.D1(TMDS_shift_2h[0]), // 1-bit data input (positive edge)
.D2(TMDS_shift_2l[0]), // 1-bit data input (negative edge)
.R (1'b0 ), // 1-bit reset
.S (1'b0 ) // 1-bit set
);
OBUFDS #(
.IOSTANDARD("DEFAULT"), // Specify the output I/O standard
.SLEW ("SLOW" ) // Specify the output slew rate
)
OBUFDS_2
(
.O (dataout_2_p ),// Diff_p output (connect directly to top-level port)
.OB (dataout_2_n ),// Diff_n output (connect directly to top-level port)
.I (dataout_2 ) // Buffer input
);
//********************* Channel 3*****************************\\
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_3
(
.Q (dataout_0 ), // 1-bit DDR output
.C (clkx5 ), // 1-bit clock input
.CE(1'b1 ), // 1-bit clock enable input
.D1(TMDS_shift_3h[0]), // 1-bit data input (positive edge)
.D2(TMDS_shift_3l[0]), // 1-bit data input (negative edge)
.R (1'b0 ), // 1-bit reset
.S (1'b0 ) // 1-bit set
);
OBUFDS #(
.IOSTANDARD("DEFAULT"), // Specify the output I/O standard
.SLEW ("SLOW" ) // Specify the output slew rate
)
OBUFDS_3
(
.O (dataout_3_p ),// Diff_p output (connect directly to top-level port)
.OB (dataout_3_n ),// Diff_n output (connect directly to top-level port)
.I (dataout_3 ) // Buffer input
);
endmodule
1.4.3 发送器的顶层编写
基于底层编码和串行发送器的设计之后,例化这两个模块,并与图像数据流的数据和控制信号连接,完成顶层文件的编写。
verilog代码
`timescale 1ns / 1ps
//
// Description: 顶层发送器
//
module trans_top(
pixelclk,
pixelclk5x,
rst_p,
blue_din,
green_din,
red_din,
hsync,
vsync,
de,
tmds_clk_p,
tmds_clk_n,
tmds_data_p,
tmds_data_n
);
input pixelclk; // system clock
input pixelclk5x; // system clock x5
input rst_p; // reset
input [7:0] blue_din; // Blue data in
input [7:0] green_din; // Green data in
input [7:0] red_din; // Red data in
input hsync; // hsync data
input vsync; // vsync data
input de; // data enable
output tmds_clk_p; //clock
output tmds_clk_n; //clock
output [2:0] tmds_data_p; //rgb
output [2:0] tmds_data_n; //rgb
wire [9:0] red ;
wire [9:0] green;
wire [9:0] blue ;
encode encode_red(
.clk (pixelclk),
.rst_n (rst_p ),
.din (red_din ),
.c0 (hsync ),
.c1 (vsync ),
.de (de ),
.dout (red )
);
encode encode_green(
.clk (pixelclk ),
.rst_n (rst_p ),
.din (green_din),
.c0 (hsync ),
.c1 (vsync ),
.de (de ),
.dout (green )
);
encode encode_blue(
.clk (pixelclk),
.rst_n (rst_p ),
.din (blue_din),
.c0 (hsync ),
.c1 (vsync ),
.de (de ),
.dout (blue )
);
serdes_4b_10to1 serdes_4b_10to1_u0(
.clkx5 (pixelclk5x ),
.datain_0 (blue ),
.datain_1 (green ),
.datain_2 (red ), //RGB
.datain_3 (10'b1111100000 ),
.dataout_0_p (tmds_data_p[0] ),
.dataout_0_n (tmds_data_n[0] ),
.dataout_1_p (tmds_data_p[1] ),
.dataout_1_n (tmds_data_n[1] ),
.dataout_2_p (tmds_data_p[2] ),
.dataout_2_n (tmds_data_n[2] ),
.dataout_3_p (tmds_clk_p ),
.dataout_3_n (tmds_clk_n )
);
endmodule
二、基于HDMI/DVI接口的显示器彩条显示
Verilod 代码:
`timescale 1ns / 1ps
//
// Description: 彩条信号产生
//
module colour_bar_gen(
input [11:0] disp_h_addr ,
input [11:0] disp_v_addr ,
input disp_data_valid,
output reg [23:0] disp_data
);
wire C0_ACT ;
wire C1_ACT ;
wire R0_ACT ;
wire R1_ACT ;
wire R2_ACT ;
wire R3_ACT ;
wire C0_R0_ACT;
wire C0_R1_ACT;
wire C0_R2_ACT;
wire C0_R3_ACT;
wire C1_R0_ACT;
wire C1_R1_ACT;
wire C1_R2_ACT;
wire C1_R3_ACT;
localparam BLACK = 24'h000000,
RED = 24'hFF0000,
GREEN = 24'h00FF00,
BLUE = 24'h0000FF,
YELLOW = 24'hFFFF00,
PURPPLE = 24'hFF00FF,
CYAN = 24'h00FFFF,
WHITE = 24'hFFFFFF;
localparam R0_C0 = BLACK ,
R0_C1 = RED ,
R1_C0 = GREEN ,
R1_C1 = BLUE ,
R2_C0 = YELLOW ,
R2_C1 = PURPPLE,
R3_C0 = CYAN ,
R3_C1 = WHITE ;
//修改分辨率时需要修改这边的程序
assign C0_ACT = disp_h_addr >= 0 && disp_h_addr <640;
assign C1_ACT = disp_h_addr >= 640 && disp_h_addr <1280;
assign R0_ACT = disp_v_addr >= 0 && disp_v_addr <180;
assign R1_ACT = disp_v_addr >= 180 && disp_v_addr <360;
assign R2_ACT = disp_v_addr >= 360 && disp_v_addr <540;
assign R3_ACT = disp_v_addr >= 540 && disp_v_addr <720;
assign C0_R0_ACT = disp_data_valid && C0_ACT && R0_ACT;
assign C0_R1_ACT = disp_data_valid && C0_ACT && R1_ACT;
assign C0_R2_ACT = disp_data_valid && C0_ACT && R2_ACT;
assign C0_R3_ACT = disp_data_valid && C0_ACT && R3_ACT;
assign C1_R0_ACT = disp_data_valid && C1_ACT && R0_ACT;
assign C1_R1_ACT = disp_data_valid && C1_ACT && R1_ACT;
assign C1_R2_ACT = disp_data_valid && C1_ACT && R2_ACT;
assign C1_R3_ACT = disp_data_valid && C1_ACT && R3_ACT;
always@(*)begin
case({C0_R0_ACT,C0_R1_ACT,C0_R2_ACT,C0_R3_ACT,C1_R0_ACT,C1_R1_ACT,C1_R2_ACT,C1_R3_ACT})
8'b0000_0001:disp_data = R3_C1;
8'b0000_0010:disp_data = R2_C1;
8'b0000_0100:disp_data = R1_C1;
8'b0000_1000:disp_data = R0_C1;
8'b0001_0000:disp_data = R3_C0;
8'b0010_0000:disp_data = R2_C0;
8'b0100_0000:disp_data = R1_C0;
8'b1000_0000:disp_data = R0_C0;
default:disp_data = R0_C0;
endcase
end
endmodule
/
// Description : 显示设备驱动模块
/
`timescale 1ns / 1ps
module disp_drive#(
parameter H_SYNC_TIME = 12'd96 , //H_xxx 行相关参数单位为 像素单元
H_BACK_PORCH = 12'd40 ,
H_LEFT_BORDER = 12'd8 ,
H_DATA_TIME = 12'd640 ,
H_RIGHT_BORDER = 12'd8 ,
H_FRONT_PORCH = 12'd8 ,
V_SYNC_TIME = 12'd2 , //V_xxx 列相关参数单位为 行
V_BACK_PORCH = 12'd25 ,
V_TOP_BORDER = 12'd8 ,
V_DATA_TIME = 12'd480 ,
V_BOTTOM_BORDER = 12'd8 ,
V_FRONT_PORCH = 12'd2
)(
ClkDisp ,
Rst_p ,
Data ,
Data_valid ,
H_Addr ,
V_Addr ,
Disp_HS ,
Disp_VS ,
Disp_Red ,
Disp_Green ,
Disp_Blue ,
Disp_DE ,
Disp_PCLK
);
input ClkDisp ;
input Rst_p ;
input [23:0] Data ; //RGB888
output Data_valid ;
output[11:0] H_Addr ; //方便模块调用实时扫描位置输出
output[11:0] V_Addr ;
output reg Disp_HS ;
output reg Disp_VS ;
output reg [7:0]Disp_Red ;
output reg [7:0]Disp_Green;
output reg [7:0]Disp_Blue ;
output reg Disp_DE ;
output Disp_PCLK ;
reg [11:0] r_hcount; //修改分辨率时,注意计数器的位数修改
reg [11:0] r_vcount;
localparam
VGA_HS_end = H_SYNC_TIME - 1 ,
hdat_begin = VGA_HS_end + H_BACK_PORCH + H_LEFT_BORDER ,
hdat_end = hdat_begin + H_DATA_TIME ,
hpixel_end = hdat_end + H_RIGHT_BORDER + H_FRONT_PORCH ,
VGA_VS_end = V_SYNC_TIME - 1 ,
vdat_begin = VGA_VS_end + V_BACK_PORCH + V_TOP_BORDER ,
vdat_end = vdat_begin + V_DATA_TIME ,
vline_end = vdat_end + V_BOTTOM_BORDER + V_FRONT_PORCH;
assign Disp_PCLK = ~ClkDisp;//将VGA控制器时钟信号取反输出,作为DAC数据锁存信号
assign H_Addr = Disp_DE ? (r_hcount - hdat_begin) : 'd0;
assign V_Addr = Disp_DE ? (r_vcount - vdat_begin) : 'd0;
assign Data_valid = Disp_DE ; // ??????
//*********************** 行场扫描 ***********************************\\
//行扫描
always @(posedge ClkDisp or negedge Rst_p) begin
if(!Rst_p)
r_hcount <= 'd0;
else if(r_hcount >= hpixel_end)
r_hcount <= 'd0;
else
r_hcount <= r_hcount + 1;
end
//场扫描
always @(posedge ClkDisp or negedge Rst_p) begin
if(!Rst_p)
r_vcount <= 'd0;
else if(r_hcount >= hpixel_end)begin
if(r_vcount >= vline_end)
r_vcount <= 'd0;
else
r_vcount <= r_vcount + 1;
end
else
r_vcount <= r_vcount;
end
//数据有效
always @(posedge ClkDisp or negedge Rst_p) begin
if(!Rst_p)
Disp_DE <= 'd0;
else
Disp_DE <= ((r_hcount >= hdat_begin - 1)&&(r_hcount < hdat_end - 1))
&&((r_vcount >= vdat_begin)&&(r_vcount < vdat_end));
end
//行场同步信号 + 输出数据
always @(posedge ClkDisp or negedge Rst_p) begin
if(!Rst_p)begin
Disp_HS <= 'd0;
{Disp_Red,Disp_Green,Disp_Blue} <= 'd0;
end
else if(r_hcount >= 'd1649)begin
Disp_HS <= 'd0;
end
else begin
Disp_HS <= (r_hcount >= VGA_HS_end);
{Disp_Red,Disp_Green,Disp_Blue} <= Disp_DE ? Data : 'd0;
end
end
always @(posedge ClkDisp or negedge Rst_p) begin
if(!Rst_p)
Disp_VS <= 'd0;
else if(r_vcount == 'd749 && r_hcount == 'd1649)
Disp_VS <= 'd0;
else if(r_vcount == 'd4 && r_hcount == 'd1649)
Disp_VS <= 'd1;
else
Disp_VS <= Disp_VS;
end
endmodule
结合以上各模块代码,驱动HDMI显示彩色条,以下是顶层模块的编写,例化:
`timescale 1ns / 1ps
//
// Description: 顶层模块
//
module HDMI_test(
clk50m ,
reset_n ,
//hdmi1 interface
hdmi1_clk_p,
hdmi1_clk_n,
hdmi1_dat_p,
hdmi1_dat_n,
hdmi1_oe ,
//hdmi2 interface
hdmi2_clk_p,
hdmi2_clk_n,
hdmi2_dat_p,
hdmi2_dat_n,
hdmi2_oe
);
input clk50m ;
input reset_n ;
//hdmi1 interface
output hdmi1_clk_p;
output hdmi1_clk_n;
output [2:0] hdmi1_dat_p;
output [2:0] hdmi1_dat_n;
output hdmi1_oe ;
//hdmi2 interface
output hdmi2_clk_p;
output hdmi2_clk_n;
output [2:0] hdmi2_dat_p;
output [2:0] hdmi2_dat_n;
output hdmi2_oe ;
wire pll_locked ;
wire pixelclk ;
wire pixelclk5x ;
wire reset_p ;
wire [11:0] disp_h_addr ;
wire [11:0] disp_v_addr ;
wire disp_data_valid;
wire [23:0] disp_data ;
wire disp_hs ;
wire disp_vs ;
wire [7:0] disp_red ;
wire [7:0] disp_green ;
wire [7:0] disp_blue ;
wire disp_de ;
wire disp_pclk ;
assign reset_p = pll_locked;
//产生pixelclk以及pixelclk5x ----- 对应的是1280*720的分辨率
pll pll_u0(
.clk_in1 (clk50m ),
.resetn (reset_n ),
.locked (pll_locked),
.clk_out1(pixelclk ),
.clk_out2(pixelclk5x)
);
//彩色条数据显示
colour_bar_gen colour_bar_gen_u0(
.disp_h_addr (disp_h_addr ),
.disp_v_addr (disp_v_addr ),
.disp_data_valid (disp_data_valid),
.disp_data (disp_data )
);
//显示器显示驱动
disp_drive#(
.H_SYNC_TIME (12'd40 ), //H_xxx 行相关参数单位为 像素单元
.H_BACK_PORCH (12'd220 ),
.H_LEFT_BORDER (12'd0 ),
.H_DATA_TIME (12'd1280),
.H_RIGHT_BORDER (12'd0 ),
.H_FRONT_PORCH (12'd110 ),
.V_SYNC_TIME (12'd5 ), //V_xxx 列相关参数单位为 行
.V_BACK_PORCH (12'd20 ),
.V_TOP_BORDER (12'd0 ),
.V_DATA_TIME (12'd720 ),
.V_BOTTOM_BORDER (12'd0 ),
.V_FRONT_PORCH (12'd5 )
)
disp_drive_u0
(
.ClkDisp (pixelclk ),
.Rst_p (reset_p ),
.Data (disp_data ),
.Data_valid (disp_data_valid),
.H_Addr (disp_h_addr ),
.V_Addr (disp_v_addr ),
.Disp_HS (disp_hs ),
.Disp_VS (disp_vs ),
.Disp_Red (disp_red ),
.Disp_Green (disp_green ),
.Disp_Blue (disp_blue ),
.Disp_DE (disp_de ),
.Disp_PCLK (disp_pclk )
);
//顶层发送器 HDMI1
trans_top trans_top_u1(
.pixelclk (pixelclk ),
.pixelclk5x (pixelclk5x ),
.rst_p (reset_p ),
.blue_din (disp_blue ),
.green_din (disp_green ),
.red_din (disp_red ),
.hsync (disp_hs ),
.vsync (disp_vs ),
.de (disp_de ),
.tmds_clk_p (hdmi1_clk_p),
.tmds_clk_n (hdmi1_clk_n),
.tmds_data_p (hdmi1_dat_p),
.tmds_data_n (hdmi1_dat_n)
);
assign hdmi1_oe = 1'b1;
//顶层发送器 HDMI2
trans_top trans_top_u2(
.pixelclk (pixelclk ),
.pixelclk5x (pixelclk5x ),
.rst_p (reset_p ),
.blue_din (disp_blue ),
.green_din (disp_green ),
.red_din (disp_red ),
.hsync (disp_hs ),
.vsync (disp_vs ),
.de (disp_de ),
.tmds_clk_p (hdmi2_clk_p),
.tmds_clk_n (hdmi2_clk_n),
.tmds_data_p (hdmi2_dat_p),
.tmds_data_n (hdmi2_dat_n)
);
assign hdmi2_oe = 1'b1;
endmodule
TB仿真
上板实际操作: