当前位置: 首页 > news >正文

基于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 位 的像素数据

C0C1 为 控制信号

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驱动 演示视频icon-default.png?t=M85Bhttps://live.csdn.net/v/264920



相关文章:

  • 【OpenCV-Python】教程:7-3 理解KMeans
  • 关于FileInputStream(万能文件字节输入流)
  • 哪一件事让你忽然意识到打工永无出路?
  • 【老保姆教程】:Tesseract-OCR图片文字识别
  • 【服务器数据恢复】Storwize系列存储raid5数据恢复案例
  • 如何在 Spring Boot 项目中开启 “热部署“
  • Kubernetes 实现自动扩容和自愈应用实践
  • docker https 证书/多域名通配符自动续期(群晖https证书)
  • 您如何在 Web 表单字段/输入标签上禁用浏览器自动完成功能?
  • 变分自编码器VAE的数学原理
  • Scala 高阶函数(二)
  • 【Python百日进阶-数据分析】Day136 - plotly旭日图:px.sunburst()实例
  • MyBatis学习 | SQL映射文件
  • 高校GIS系统有何作用?
  • android面经_安卓面试题<6/30>之Intent全解析
  • 借力 StarRocks,“陆战之王“ 大润发如何在零售业数字化转型中抢占先机?
  • 疫情感染开始,大多居家办公
  • 如何克隆列表以使其在分配后不会意外更改?
  • 消息中间件(消息队列)
  • 【代码审计-2】PHP框架MVC类文件上传断点测试挖掘