基于FPGA的 TMDS 编码 及 HDMI 显示
目录
引言
TMDS 编码
原理简介
TMDS编码实现
HDMI差分数据串行
实现方法
源码
HDMI显示方法
思路
实现
工程结构
源代码分享
板级调试视频
引言
最近在开发板上倒腾了一下 TMDS 视频编码的原理以及实现。特在此做一个记录。文附 全部设计源码、MATLAB源码,需要的可以关注一下。
TMDS 编码
原理简介
TMDS,Transition Minimized Differential Signaling,是一种视频编码方式。其将8位数据编码为10位数据。分为两大阶段:
1、8bit —> 9bit
第一比特不变,接下来的7比特或者是与上一比特异或,或者是同或,取决于哪种结果导致翻转数较少;第9比特指示是哪种操作(异或或者同或);
2、9bit —> 10bit
第10比特决定是否要把阶段1中的前8比特反相,决策依据取决于操作是否有利于整体数据的0-1平衡(即,DC平衡)。
其编码流程:
D 是 8 位 的像素数据
C0 和 C1 为 控制信号
DE 是数据有效信号
cnt是一个寄存器,用来记录数据流的极性,一个大 于0的正值表明了数据流中总共多传了多少个1,一个小于0 的负值表明了数据流中总共多传了多少个0。cnt{t-1}为上一次传输的中的极性,cnt{t}为当前输入的数据的极性。q_m 最小化传输的编码结果,9 位,由输入的8 位数据经过最小化传输编码原理编码得到。
q_out 对最小化传输编码结果的9 位数据继续进行直流平衡编码后得到的10位输出结果。
N1{x} 统计输入的参数X 中1 的个数
N0{X} 统计输入的参数X 中0 的个数
TMDS编码实现
// | ===================================================---------------------------===================================================
// | --------------------------------------------------- TMDS 编码模块 ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-21
// | 完成时间 : 2022-12-21
// | 作 者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// | -1-
// | -2-
// | -3-
// |
// | ================================= 模块修改历史纪录 =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:
module TMDS_ENCODE_MDL(
// | ==================================== 模块输入输出端口声明 ====================================
input I_PIXEL_CLK ,//像素数据时钟
input I_SYS_RSTN ,
input I_DATA_EN ,
input [7:0] I_PIXEL_DATA ,
input I_CTRL0 ,
input I_CTRL1 ,
output reg [9:0] O_ENCODE_DATA
);
// | ==================================== 模块内部参数声明 ====================================
localparam LP_CTRL0 = 10'b1101010100;
localparam LP_CTRL1 = 10'b0010101011;
localparam LP_CTRL2 = 10'b0101010100;
localparam LP_CTRL3 = 10'b1010101011;
// | ==================================== 模块内部信号声明 ====================================
// 寄存
reg [1:0] R_I_DATA_EN;
reg [7:0] R_I_PIXEL_DATA;
reg [1:0] R_I_CTRL0;
reg [1:0] R_I_CTRL1;
// 4 个判断条件 (依据流程图)
wire W_JUDGE_CONDT1;
wire W_JUDGE_CONDT2;
wire W_JUDGE_CONDT3;
wire W_JUDGE_CONDT4;
// 计数
reg [3:0] R_I_PIXEL_DATA_1_NUM;
reg [3:0] R_Q_M_1_NUM;
reg [3:0] R_Q_M_0_NUM;
reg [4:0] R_CNT;
// 9bit编码
wire [8:0] W_Q_M;
reg [8:0] R_Q_M;
// | ==================================== 模块内部逻辑设计 ====================================
always @ (posedge I_PIXEL_CLK)
begin
if(~I_SYS_RSTN)
begin
R_I_DATA_EN <= 2'd0;
R_I_PIXEL_DATA <= I_PIXEL_DATA;
R_I_CTRL0 <= 2'd0;
R_I_CTRL1 <= 2'd0;
R_Q_M <= 9'd0;
end
else
begin
R_I_DATA_EN <= {R_I_DATA_EN[0],I_DATA_EN};
R_I_PIXEL_DATA <= I_PIXEL_DATA;
R_I_CTRL0 <= {R_I_CTRL0[0],I_CTRL0};
R_I_CTRL1 <= {R_I_CTRL1[0],I_CTRL1};
R_Q_M <= W_Q_M;
end
end
always @ (posedge I_PIXEL_CLK)
begin
if(~I_SYS_RSTN)
begin
R_I_PIXEL_DATA_1_NUM <= 4'd0;
end
else
begin
R_I_PIXEL_DATA_1_NUM <= I_PIXEL_DATA[0] + I_PIXEL_DATA[1] + I_PIXEL_DATA[2] + I_PIXEL_DATA[3] +
I_PIXEL_DATA[4] + I_PIXEL_DATA[5] + I_PIXEL_DATA[6] + I_PIXEL_DATA[7] ;
end
end
assign W_JUDGE_CONDT1 = (R_I_PIXEL_DATA_1_NUM >4) | ((R_I_PIXEL_DATA_1_NUM == 4) & !R_I_PIXEL_DATA[0]);
assign W_Q_M[0] = R_I_PIXEL_DATA[0];
assign W_Q_M[1] = W_JUDGE_CONDT1 ? ~(W_Q_M[0] ^ R_I_PIXEL_DATA[1]) : W_Q_M[0] ^ R_I_PIXEL_DATA[1];
assign W_Q_M[2] = W_JUDGE_CONDT1 ? ~(W_Q_M[1] ^ R_I_PIXEL_DATA[2]) : W_Q_M[1] ^ R_I_PIXEL_DATA[2];
assign W_Q_M[3] = W_JUDGE_CONDT1 ? ~(W_Q_M[2] ^ R_I_PIXEL_DATA[3]) : W_Q_M[2] ^ R_I_PIXEL_DATA[3];
assign W_Q_M[4] = W_JUDGE_CONDT1 ? ~(W_Q_M[3] ^ R_I_PIXEL_DATA[4]) : W_Q_M[3] ^ R_I_PIXEL_DATA[4];
assign W_Q_M[5] = W_JUDGE_CONDT1 ? ~(W_Q_M[4] ^ R_I_PIXEL_DATA[5]) : W_Q_M[4] ^ R_I_PIXEL_DATA[5];
assign W_Q_M[6] = W_JUDGE_CONDT1 ? ~(W_Q_M[5] ^ R_I_PIXEL_DATA[6]) : W_Q_M[5] ^ R_I_PIXEL_DATA[6];
assign W_Q_M[7] = W_JUDGE_CONDT1 ? ~(W_Q_M[6] ^ R_I_PIXEL_DATA[7]) : W_Q_M[6] ^ R_I_PIXEL_DATA[7];
assign W_Q_M[8] = W_JUDGE_CONDT1 ? 1'b0:1'b1;
assign W_JUDGE_CONDT2 = R_I_DATA_EN[1];
always @ (posedge I_PIXEL_CLK)
begin
if(~I_SYS_RSTN)
begin
R_Q_M_1_NUM <= 4'd0;
R_Q_M_0_NUM <= 4'd0;
end
else
begin
R_Q_M_1_NUM <= W_Q_M[0] + W_Q_M[1] + W_Q_M[2] + W_Q_M[3] +
W_Q_M[4] + W_Q_M[5] + W_Q_M[6] + W_Q_M[7] ;
R_Q_M_0_NUM <= !W_Q_M[0] + !W_Q_M[1] + !W_Q_M[2] + !W_Q_M[3] +
!W_Q_M[4] + !W_Q_M[5] + !W_Q_M[6] + !W_Q_M[7] ;
end
end
assign W_JUDGE_CONDT3 = (R_CNT == 5'd0) | (R_Q_M_0_NUM == R_Q_M_1_NUM);
assign W_JUDGE_CONDT4 = (!R_CNT[4] & (R_Q_M_1_NUM > R_Q_M_0_NUM)) | (R_CNT[4] & (R_Q_M_0_NUM > R_Q_M_1_NUM));
always @ (posedge I_PIXEL_CLK)
begin
if(~I_SYS_RSTN)
begin
R_CNT <= 5'd0;
O_ENCODE_DATA <= 10'd0;
end
else if(W_JUDGE_CONDT2)
begin
if(W_JUDGE_CONDT3)
begin
O_ENCODE_DATA[7:0] <= R_Q_M[8] ? R_Q_M[7:0] : ~R_Q_M[7:0];
O_ENCODE_DATA[8] <= R_Q_M[8];
O_ENCODE_DATA[9] <= ~R_Q_M[8];
R_CNT <= R_Q_M[8] ? (R_CNT + R_Q_M_1_NUM - R_Q_M_0_NUM) : (R_CNT + R_Q_M_0_NUM - R_Q_M_1_NUM);
end
else
begin
if(W_JUDGE_CONDT4)
begin
O_ENCODE_DATA[7:0] <= ~R_Q_M[7:0];
O_ENCODE_DATA[8] <= R_Q_M[8];
O_ENCODE_DATA[9] <= 1'b1;
R_CNT <= R_CNT + {R_Q_M[8],1'b0} + R_Q_M_0_NUM - R_Q_M_1_NUM;
end
else
begin
O_ENCODE_DATA[7:0] <= R_Q_M[7:0];
O_ENCODE_DATA[8] <= R_Q_M[8];
O_ENCODE_DATA[9] <= 1'b0;
R_CNT <= R_CNT - {~R_Q_M[8],1'b0} + R_Q_M_1_NUM - R_Q_M_0_NUM;
end
end
end
else
begin
R_CNT <= 5'd0;
case({R_I_CTRL1[1],R_I_CTRL0[1]})
2'b00:
begin
O_ENCODE_DATA <= LP_CTRL0;
end
2'b01:
begin
O_ENCODE_DATA <= LP_CTRL1;
end
2'b10:
begin
O_ENCODE_DATA <= LP_CTRL2;
end
2'b11:
begin
O_ENCODE_DATA <= LP_CTRL3;
end
default:
begin
O_ENCODE_DATA <= LP_CTRL0;
end
endcase
end
end
endmodule
HDMI差分数据串行
实现方法
每个像素时钟到来时,给出10bit的编码数据,串行发送时,需要用 5倍 于像素数据时钟的时钟频率分别在时钟的双沿将数据逐比特发出。
双沿数据发送使用 ODDR 原语(相同边沿 模式),差分转换使用 OBUFDS 原语。
源码
// | ===================================================---------------------------===================================================
// | --------------------------------------------------- HDMI 数据发送模块 ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-21
// | 完成时间 : 2022-12-21
// | 作 者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// | -1- 仅包含视频数据发送(无音频)
// | -2- 1280 * 720 分辨率
// | -3-
// |
// |
// | ================================= 模块修改历史纪录 =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:
module HDMI_DATA_TX_MDL(
// | ==================================== 模块输入输出端口声明 ====================================
input I_CLK_74M25 ,
input I_CLK_371M25 ,
input I_SYS_RSTN ,
input I_PIXEL_DATA_EN,
input [7:0] I_PIXEL_DATA_R,
input [7:0] I_PIXEL_DATA_G,
input [7:0] I_PIXEL_DATA_B,
input I_HSYNC,
input I_VSYNC,
output [2:0] O_TMDS_DATA_P,
output [2:0] O_TMDS_DATA_N,
output O_TMDS_CLK_P,
output O_TMDS_CLK_N
);
// | ==================================== 模块内部信号声明 ====================================
wire [9:0] W_ENCODE_DATA_10BIT[2:0];
wire [3:0] W_ODDR_Q;
// 数据分配
wire [4:0] W_TMDS_CH0_D1;
wire [4:0] W_TMDS_CH0_D2;
wire [4:0] W_TMDS_CH1_D1;
wire [4:0] W_TMDS_CH1_D2;
wire [4:0] W_TMDS_CH2_D1;
wire [4:0] W_TMDS_CH2_D2;
wire [4:0] W_TMDS_CH3_D1;
wire [4:0] W_TMDS_CH3_D2;
// 移位寄存
reg [4:0] R_TMDS_CH0_D1;
reg [4:0] R_TMDS_CH0_D2;
reg [4:0] R_TMDS_CH1_D1;
reg [4:0] R_TMDS_CH1_D2;
reg [4:0] R_TMDS_CH2_D1;
reg [4:0] R_TMDS_CH2_D2;
reg [4:0] R_TMDS_CH3_D1;
reg [4:0] R_TMDS_CH3_D2;
// 计数
reg [2:0] R_CNT ;
// | ==================================== 模块内部逻辑设计 ====================================
assign W_TMDS_CH0_D1 = {W_ENCODE_DATA_10BIT[0][8],W_ENCODE_DATA_10BIT[0][6],W_ENCODE_DATA_10BIT[0][4],W_ENCODE_DATA_10BIT[0][2],W_ENCODE_DATA_10BIT[0][0]};
assign W_TMDS_CH0_D2 = {W_ENCODE_DATA_10BIT[0][9],W_ENCODE_DATA_10BIT[0][7],W_ENCODE_DATA_10BIT[0][5],W_ENCODE_DATA_10BIT[0][3],W_ENCODE_DATA_10BIT[0][1]};
assign W_TMDS_CH1_D1 = {W_ENCODE_DATA_10BIT[1][8],W_ENCODE_DATA_10BIT[1][6],W_ENCODE_DATA_10BIT[1][4],W_ENCODE_DATA_10BIT[1][2],W_ENCODE_DATA_10BIT[1][0]};
assign W_TMDS_CH1_D2 = {W_ENCODE_DATA_10BIT[1][9],W_ENCODE_DATA_10BIT[1][7],W_ENCODE_DATA_10BIT[1][5],W_ENCODE_DATA_10BIT[1][3],W_ENCODE_DATA_10BIT[1][1]};
assign W_TMDS_CH2_D1 = {W_ENCODE_DATA_10BIT[2][8],W_ENCODE_DATA_10BIT[2][6],W_ENCODE_DATA_10BIT[2][4],W_ENCODE_DATA_10BIT[2][2],W_ENCODE_DATA_10BIT[2][0]};
assign W_TMDS_CH2_D2 = {W_ENCODE_DATA_10BIT[2][9],W_ENCODE_DATA_10BIT[2][7],W_ENCODE_DATA_10BIT[2][5],W_ENCODE_DATA_10BIT[2][3],W_ENCODE_DATA_10BIT[2][1]};
assign W_TMDS_CH3_D1 = {1'b1,1'b1,1'b0,1'b0,1'b0};
assign W_TMDS_CH3_D2 = {1'b1,1'b1,1'b1,1'b0,1'b0};
always @ (posedge I_CLK_371M25)
begin
if(~I_SYS_RSTN)
begin
R_TMDS_CH0_D1 <= 5'd0;
R_TMDS_CH0_D2 <= 5'd0;
R_TMDS_CH1_D1 <= 5'd0;
R_TMDS_CH1_D2 <= 5'd0;
R_TMDS_CH2_D1 <= 5'd0;
R_TMDS_CH2_D2 <= 5'd0;
R_TMDS_CH3_D1 <= 5'd0;
R_TMDS_CH3_D2 <= 5'd0;
R_CNT <= 3'd0;
end
else
begin
R_CNT <= R_CNT[2] ? 3'd0 : R_CNT + 3'd1;
R_TMDS_CH0_D1 <= R_CNT[2] ? W_TMDS_CH0_D1 : {1'b0,R_TMDS_CH0_D1[4:1]};
R_TMDS_CH0_D2 <= R_CNT[2] ? W_TMDS_CH0_D2 : {1'b0,R_TMDS_CH0_D2[4:1]};
R_TMDS_CH1_D1 <= R_CNT[2] ? W_TMDS_CH1_D1 : {1'b0,R_TMDS_CH1_D1[4:1]};
R_TMDS_CH1_D2 <= R_CNT[2] ? W_TMDS_CH1_D2 : {1'b0,R_TMDS_CH1_D2[4:1]};
R_TMDS_CH2_D1 <= R_CNT[2] ? W_TMDS_CH2_D1 : {1'b0,R_TMDS_CH2_D1[4:1]};
R_TMDS_CH2_D2 <= R_CNT[2] ? W_TMDS_CH2_D2 : {1'b0,R_TMDS_CH2_D2[4:1]};
R_TMDS_CH3_D1 <= R_CNT[2] ? W_TMDS_CH3_D1 : {1'b0,R_TMDS_CH3_D1[4:1]};
R_TMDS_CH3_D2 <= R_CNT[2] ? W_TMDS_CH3_D2 : {1'b0,R_TMDS_CH3_D2[4:1]};
end
end
// | ==================================== 模块内部模块例化 ====================================
// | TMDS 编码模块
TMDS_ENCODE_MDL INST_TMDS_ENCODE_MDL_R
(
.I_PIXEL_CLK (I_CLK_74M25),
.I_SYS_RSTN (I_SYS_RSTN),
.I_DATA_EN (I_PIXEL_DATA_EN),
.I_PIXEL_DATA (I_PIXEL_DATA_R),
.I_CTRL0 (1'b0),
.I_CTRL1 (1'b0),
.O_ENCODE_DATA (W_ENCODE_DATA_10BIT[2])
);
TMDS_ENCODE_MDL INST_TMDS_ENCODE_MDL_G
(
.I_PIXEL_CLK (I_CLK_74M25),
.I_SYS_RSTN (I_SYS_RSTN),
.I_DATA_EN (I_PIXEL_DATA_EN),
.I_PIXEL_DATA (I_PIXEL_DATA_G),
.I_CTRL0 (1'b0),
.I_CTRL1 (1'b0),
.O_ENCODE_DATA (W_ENCODE_DATA_10BIT[1])
);
TMDS_ENCODE_MDL INST_TMDS_ENCODE_MDL_B
(
.I_PIXEL_CLK (I_CLK_74M25),
.I_SYS_RSTN (I_SYS_RSTN),
.I_DATA_EN (I_PIXEL_DATA_EN),
.I_PIXEL_DATA (I_PIXEL_DATA_B),
.I_CTRL0 (I_HSYNC),
.I_CTRL1 (I_VSYNC),
.O_ENCODE_DATA (W_ENCODE_DATA_10BIT[0])
);
// | ODDR OBUFDS 原语例化
// CH0
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_CH0 (
.Q (W_ODDR_Q[0] ), // 1-bit DDR output
.C (I_CLK_371M25 ), // 1-bit clock input
.CE (1'b1 ), // 1-bit clock enable input
.D1 (R_TMDS_CH0_D1[0] ), // 1-bit data input (positive edge)
.D2 (R_TMDS_CH0_D2[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_CH0 (
.O (O_TMDS_DATA_P[0]), // Diff_p output (connect directly to top-level port)
.OB (O_TMDS_DATA_N[0]), // Diff_n output (connect directly to top-level port)
.I (W_ODDR_Q[0] ) // Buffer input
);
// CH1
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_CH1 (
.Q (W_ODDR_Q[1] ), // 1-bit DDR output
.C (I_CLK_371M25 ), // 1-bit clock input
.CE (1'b1 ), // 1-bit clock enable input
.D1 (R_TMDS_CH1_D1[0] ), // 1-bit data input (positive edge)
.D2 (R_TMDS_CH1_D2[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_CH1 (
.O (O_TMDS_DATA_P[1]), // Diff_p output (connect directly to top-level port)
.OB (O_TMDS_DATA_N[1]), // Diff_n output (connect directly to top-level port)
.I (W_ODDR_Q[1] ) // Buffer input
);
// CH2
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_CH2 (
.Q (W_ODDR_Q[2] ), // 1-bit DDR output
.C (I_CLK_371M25 ), // 1-bit clock input
.CE (1'b1 ), // 1-bit clock enable input
.D1 (R_TMDS_CH2_D1[0] ), // 1-bit data input (positive edge)
.D2 (R_TMDS_CH2_D2[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_CH2 (
.O (O_TMDS_DATA_P[2]), // Diff_p output (connect directly to top-level port)
.OB (O_TMDS_DATA_N[2]), // Diff_n output (connect directly to top-level port)
.I (W_ODDR_Q[2] ) // Buffer input
);
// CH3
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_CH3 (
.Q (W_ODDR_Q[3] ), // 1-bit DDR output
.C (I_CLK_371M25 ), // 1-bit clock input
.CE (1'b1 ), // 1-bit clock enable input
.D1 (R_TMDS_CH3_D1[0] ), // 1-bit data input (positive edge)
.D2 (R_TMDS_CH3_D2[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_CH3 (
.O (O_TMDS_CLK_P), // Diff_p output (connect directly to top-level port)
.OB (O_TMDS_CLK_N), // Diff_n output (connect directly to top-level port)
.I (W_ODDR_Q[3] ) // Buffer input
);
endmodule
HDMI显示方法
思路
共有两种演示模式,由拨码开关来切换。
开关为 1:彩条轮播;
开关为 0:显示图片;
图片为:(忽略水印)
图片尺寸大小:160*277 (由于FPGA的ROM资源有限,此处显示小图片)
ROM配置 : 位宽 24位,深度 160*277
像素时钟:74.25MHz
串行发送时钟:371.25MHz
分辨率:1280*720@60Hz
实现
工程结构
源代码分享
测试顶层:
// | ===================================================---------------------------===================================================
// | --------------------------------------------------- HDMI 数据发送测试顶层模块 ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-22
// | 完成时间 : 2022-12-22
// | 作 者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// | -1- 仅包含视频数据发送(无音频)
// | -2- 1280 * 720 分辨率
// | -3-
// |
// |
// | ================================= 模块修改历史纪录 =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:
// Resolution_640x480 //时钟为25.175MHz
// Resolution_800x480 //时钟为33MHz,可兼容TFT5.0
// Resolution_800x600 //时钟为40MHz
// Resolution_1024x600 //时钟为51MHz
// Resolution_1024x768 //时钟为65MHz
// Resolution_1280x720 //时钟为74.25MHz
// Resolution_1920x1080 //时钟为148.5MHz
// 宏定义
`define DEF_H_Total_Time 12'd1650
`define DEF_H_Right_Border 12'd0
`define DEF_H_Front_Porch 12'd110
`define DEF_H_Sync_Time 12'd40
`define DEF_H_Back_Porch 12'd220
`define DEF_H_Left_Border 12'd0
`define DEF_V_Total_Time 12'd750
`define DEF_V_Bottom_Border 12'd0
`define DEF_V_Front_Porch 12'd5
`define DEF_V_Sync_Time 12'd5
`define DEF_V_Back_Porch 12'd20
`define DEF_V_Top_Border 12'd0
module HDMI_DISP_TEST_MDL(
// | ==================================== 模块输入输出端口声明 ====================================
input I_SYS_CLK ,
input I_SYS_RSTN ,
input I_DISP_SEL,
output O_HDMI_EN,
output [2:0] O_TMDS_DATA_P,
output [2:0] O_TMDS_DATA_N,
output O_TMDS_CLK_P,
output O_TMDS_CLK_N
);
// | ==================================== 模块内部参数声明 ====================================
localparam LP_DISP_WIDTH = 1280;
localparam LP_DISP_HEIGHT = 720;
//颜色编码
localparam LP_BLACK = 24'h000000; //黑色
localparam LP_BLUE = 24'h0000FF; //蓝色
localparam LP_RED = 24'hFF0000; //红色
localparam LP_PURPPLE = 24'hFF00FF; //紫色
localparam LP_GREEN = 24'h00FF00; //绿色
localparam LP_CYAN = 24'h00FFFF; //青色
localparam LP_YELLOW = 24'hFFFF00; //黄色
localparam LP_WHITE = 24'hFFFFFF; //白色
localparam LP_H_DATA_BEGIN = `DEF_H_Sync_Time + `DEF_H_Back_Porch + `DEF_H_Left_Border - 1'b1;
localparam LP_H_DATA_END = `DEF_H_Total_Time - `DEF_H_Right_Border - `DEF_H_Front_Porch - 1'b1;
localparam LP_V_DATA_BEGIN = `DEF_V_Sync_Time + `DEF_V_Back_Porch + `DEF_V_Top_Border - 1'b1;
localparam LP_V_DATA_END = `DEF_V_Total_Time - `DEF_V_Bottom_Border - `DEF_V_Front_Porch - 1'b1;
// | ==================================== 模块内部信号声明 ====================================
wire W_CLK_100M;
wire W_CLK_74M25;
wire W_CLK_371M25;
wire W_MMCM_LOCKED1;
wire W_MMCM_LOCKED2;
wire [11:0] W_H_ADDR;
wire [11:0] W_V_ADDR;
reg [11:0] R_H_SCAN_CNT;
reg [11:0] R_V_SCAN_CNT;
wire W_H_CNT_OVER;
wire W_V_CNT_OVER;
reg R_PIXEL_DATA_REQ;
reg [23:0] R_PIXEL_DATA;
reg R_H_SYNC;
reg R_V_SYNC;
reg [7:0] R_PIXEL_DATA_R;
reg [7:0] R_PIXEL_DATA_G;
reg [7:0] R_PIXEL_DATA_B;
wire W_R0_FLAG;//第 0 行标志位
wire W_R1_FLAG;//第 1 行标志位
wire W_R2_FLAG;//第 2 行标志位
wire W_R3_FLAG;//第 3 行标志位
wire W_C0_FLAG;//第 0 列标志位
wire W_C1_FLAG;//第 1 列标志位
wire W_C2_FLAG;//第 0 列标志位
wire W_C3_FLAG;//第 1 列标志位
reg [8*24-1:0] R_COLOR;
reg [24:0] R_CNT_25M;
wire W_PULSE;
// ROM
wire [15:0] W_ROM_ADDR;
wire [23:0] W_ROM_DATA;
// | ==================================== 模块内部逻辑设计 ====================================
// 行标志
assign W_R0_FLAG = (W_V_ADDR >= 0 ) && (W_V_ADDR < LP_DISP_HEIGHT/4 );
assign W_R1_FLAG = (W_V_ADDR >= LP_DISP_HEIGHT/4 ) && (W_V_ADDR < LP_DISP_HEIGHT/2 );
assign W_R2_FLAG = (W_V_ADDR >= LP_DISP_HEIGHT/2 ) && (W_V_ADDR < LP_DISP_HEIGHT/4*3);
assign W_R3_FLAG = (W_V_ADDR >= LP_DISP_HEIGHT/4*3) && (W_V_ADDR < LP_DISP_HEIGHT );
// 列标志
assign W_C0_FLAG = (W_H_ADDR >= 0 ) && (W_H_ADDR < LP_DISP_WIDTH/4 );
assign W_C1_FLAG = (W_H_ADDR >= LP_DISP_WIDTH/4 ) && (W_H_ADDR < LP_DISP_WIDTH/2 );
assign W_C2_FLAG = (W_H_ADDR >= LP_DISP_WIDTH/2 ) && (W_H_ADDR < LP_DISP_WIDTH/4*3 );
assign W_C3_FLAG = (W_H_ADDR >= LP_DISP_WIDTH/4*3 ) && (W_H_ADDR < LP_DISP_WIDTH );
always @ (posedge I_SYS_CLK)
begin
if(~I_SYS_RSTN)
begin
R_CNT_25M <= 25'd0;
R_COLOR <= {LP_BLACK,LP_BLUE,LP_RED,LP_PURPPLE,LP_GREEN,LP_CYAN,LP_YELLOW,LP_WHITE};
end
else
begin
if(R_CNT_25M == 25'd25_000_000)
begin
R_CNT_25M <= 25'd0;
end
else
begin
R_CNT_25M <= R_CNT_25M + 1;
end
if(W_PULSE)
begin
R_COLOR <= {R_COLOR[0+:7*24],R_COLOR[(8*24-1)-:24]};
end
else
begin
R_COLOR <= R_COLOR;
end
end
end
assign W_PULSE = (R_CNT_25M == 25'd25_000_000);
// 像素数据
always@(*)
begin
if(~I_SYS_RSTN)
begin
R_PIXEL_DATA = 24'd0;
end
else if(R_PIXEL_DATA_REQ)
begin
if(I_DISP_SEL)
begin
case({W_R0_FLAG,W_R1_FLAG,W_R2_FLAG,W_R3_FLAG})
4'b1000:
begin
R_PIXEL_DATA = W_C0_FLAG ? R_COLOR[4*24+:24] :
W_C1_FLAG ? R_COLOR[0*24+:24] :
W_C2_FLAG ? R_COLOR[0*24+:24] :
W_C3_FLAG ? R_COLOR[4*24+:24] : R_COLOR[0*24+:24];
end
4'b0100:
begin
R_PIXEL_DATA = W_C0_FLAG ? R_COLOR[0*24+:24] :
W_C1_FLAG ? R_COLOR[7*24+:24] :
W_C2_FLAG ? R_COLOR[7*24+:24] :
W_C3_FLAG ? R_COLOR[0*24+:24] : R_COLOR[0*24+:24];
end
4'b0010:
begin
R_PIXEL_DATA = W_C0_FLAG ? R_COLOR[0*24+:24] :
W_C1_FLAG ? R_COLOR[7*24+:24] :
W_C2_FLAG ? R_COLOR[7*24+:24] :
W_C3_FLAG ? R_COLOR[0*24+:24] : R_COLOR[0*24+:24];
end
4'b0001:
begin
R_PIXEL_DATA = W_C0_FLAG ? R_COLOR[4*24+:24] :
W_C1_FLAG ? R_COLOR[0*24+:24] :
W_C2_FLAG ? R_COLOR[0*24+:24] :
W_C3_FLAG ? R_COLOR[4*24+:24] : R_COLOR[0*24+:24];
end
default:
begin
R_PIXEL_DATA = 24'hFFFFFF;
end
endcase
end
else
begin
if((W_H_ADDR >= 12'd499) && (W_H_ADDR <= 12'd776) && (W_V_ADDR >= 12'd279) && (W_V_ADDR <= 12'd439))
begin
R_PIXEL_DATA = W_ROM_DATA;
end
else
begin
R_PIXEL_DATA = 24'hFFFFFF;
end
end
end
else
begin
R_PIXEL_DATA = 24'hFFFFFF;
end
end
assign W_ROM_ADDR = ((W_H_ADDR >= 12'd499) && (W_H_ADDR <= 12'd776) && (W_V_ADDR >= 12'd279) && (W_V_ADDR <= 12'd439)) ?
((W_H_ADDR-12'd499) * 160 + (W_V_ADDR - 12'd279)) : 16'd0;
// 行扫描
always @ (posedge W_CLK_74M25)
begin
if(~W_MMCM_LOCKED2)
begin
R_H_SCAN_CNT <= 12'd0;
end
else if(W_H_CNT_OVER)
begin
R_H_SCAN_CNT <= 12'd0;
end
else
begin
R_H_SCAN_CNT <= R_H_SCAN_CNT + 1;
end
end
assign W_H_CNT_OVER = (R_H_SCAN_CNT >= (`DEF_H_Total_Time - 1));
// 场扫描
always @ (posedge W_CLK_74M25)
begin
if(~W_MMCM_LOCKED2)
begin
R_V_SCAN_CNT <= 12'd0;
end
else if(W_H_CNT_OVER)
begin
if(W_V_CNT_OVER)
begin
R_V_SCAN_CNT <= 12'd0;
end
else
begin
R_V_SCAN_CNT <= R_V_SCAN_CNT + 1;
end
end
else
begin
R_V_SCAN_CNT <= R_V_SCAN_CNT ;
end
end
assign W_V_CNT_OVER = (R_V_SCAN_CNT >= (`DEF_V_Total_Time - 1));
// 行同步
always @ (posedge W_CLK_74M25)
begin
if(~W_MMCM_LOCKED2)
begin
R_H_SYNC <= 1'b0;
end
else
begin
R_H_SYNC <= (R_H_SCAN_CNT > (`DEF_H_Sync_Time - 1));
end
end
// 场同步
always @ (posedge W_CLK_74M25)
begin
if(~W_MMCM_LOCKED2)
begin
R_V_SYNC <= 1'b0;
end
else
begin
R_V_SYNC <= (R_V_SCAN_CNT > (`DEF_V_Sync_Time - 1));
end
end
// RGB 数据
always @ (posedge W_CLK_74M25)
begin
if(~W_MMCM_LOCKED2)
begin
R_PIXEL_DATA_R <= 8'd0;
R_PIXEL_DATA_G <= 8'd0;
R_PIXEL_DATA_B <= 8'd0;
end
else if(R_PIXEL_DATA_REQ)
begin
R_PIXEL_DATA_R <= R_PIXEL_DATA[16+:8];
R_PIXEL_DATA_G <= R_PIXEL_DATA[8+:8];
R_PIXEL_DATA_B <= R_PIXEL_DATA[0+:8];
end
else
begin
R_PIXEL_DATA_R <= 8'd0;
R_PIXEL_DATA_G <= 8'd0;
R_PIXEL_DATA_B <= 8'd0;
end
end
// 数据使能
always @ (posedge W_CLK_74M25)
begin
if(~W_MMCM_LOCKED2)
begin
R_PIXEL_DATA_REQ <= 1'b0;
end
else
begin
R_PIXEL_DATA_REQ <= (R_H_SCAN_CNT >= LP_H_DATA_BEGIN) && (R_H_SCAN_CNT < LP_H_DATA_END)&&
(R_V_SCAN_CNT >= LP_V_DATA_BEGIN) && (R_V_SCAN_CNT < LP_V_DATA_END);
end
end
// 行 场 地址
assign W_H_ADDR = R_PIXEL_DATA_REQ ? (R_H_SCAN_CNT - LP_H_DATA_BEGIN) : 12'd0;
assign W_V_ADDR = R_PIXEL_DATA_REQ ? (R_V_SCAN_CNT - LP_V_DATA_BEGIN) : 12'd0;
assign O_HDMI_EN = 1'b1;
// | ==================================== 模块内部模块例化 ====================================
// | 时钟 IP例化
MMCM_HDMI INST_MMCM_HDMI
(
// Clock out ports
.O_CLK_74M25(W_CLK_74M25), // output O_CLK_74M25
.O_CLK_371M25(W_CLK_371M25), // output O_CLK_371M25
// Status and control signals
.reset(!I_SYS_RSTN ), // input reset
.locked(W_MMCM_LOCKED2), // output locked
// Clock in ports
.I_CLK_50M(I_SYS_CLK)); // input I_CLK_100M
// | HDMI 数据发送模块例化
HDMI_DATA_TX_MDL INST_HDMI_DATA_TX_MDL
(
.I_CLK_74M25 (W_CLK_74M25),
.I_CLK_371M25 (W_CLK_371M25),
.I_SYS_RSTN (W_MMCM_LOCKED2),
.I_PIXEL_DATA_EN (R_PIXEL_DATA_REQ),
.I_PIXEL_DATA_R (R_PIXEL_DATA_R),
.I_PIXEL_DATA_G (R_PIXEL_DATA_G),
.I_PIXEL_DATA_B (R_PIXEL_DATA_B),
.I_HSYNC (R_H_SYNC),
.I_VSYNC (R_V_SYNC),
.O_TMDS_DATA_P (O_TMDS_DATA_P),
.O_TMDS_DATA_N (O_TMDS_DATA_N),
.O_TMDS_CLK_P (O_TMDS_CLK_P),
.O_TMDS_CLK_N (O_TMDS_CLK_N)
);
// ROM
ROM_RGB_DATA INST_ROM_RGB_DATA (
.clka(W_CLK_74M25), // input wire clka
.ena(1'b1), // input wire ena
.addra(W_ROM_ADDR), // input wire [15 : 0] addra
.douta(W_ROM_DATA) // output wire [23 : 0] douta
);
endmodule
ROM 初始化文件 .coe 生成 MATLAB源码
%% -------- 1280 * 720 像素数据提取 存入 .coe文件 -------- % 作 者:Xu Y. B.(CSDN 用户名:在路上,正出发) % 创建日期:2022-12-22 % 完成日期:2022-12-22 %% 准备 clc; clearvars; close all; %% 图片读取 File_Path = "D:\VIVADO_WORK_SPACE\XC7A35T_DESIGN\Figure\"; Figure4_Name = "Figure4.png"; Figure_Data = imread(strcat(File_Path,Figure4_Name)); imshow(Figure_Data(:,:,:)) DATA_R = uint32(reshape(Figure_Data(:,:,1),[],1)); DATA_G = uint32(reshape(Figure_Data(:,:,2),[],1)); DATA_B = uint32(reshape(Figure_Data(:,:,3),[],1)); DATA_COE = uint32(DATA_R * 256^2 + DATA_G * 256 + DATA_B); str='D:\VIVADO_WORK_SPACE\XC7A35T_DESIGN\COE_FILE\FIGURE_DATA.coe';%该字符串为文件的路径 fid_coe=fopen(str,'w+');%打开文件获得ID fprintf(fid_coe,'memory_initialization_radix=10;\n');%写入第一行 fprintf(fid_coe,'memory_initialization_vector=\n');%写入第二行 fprintf(fid_coe,'%d,\n',DATA_COE((1:end-1),1));%写入数据并以逗号隔开 fprintf(fid_coe,'%d;',DATA_COE(end,1));%写入最后一个数据以分号结束
板级调试视频
基于FPGA的TMDS编码以及HDMI驱动 演示视频https://live.csdn.net/v/264920