实验目标:驱动HdMI显示十色等宽彩条。
本实验的重点是:
1掌握TMDS通信协议。
2rgb565转rgb888。
3编写HDMI驱动程序。
4学会看流程图编写代码。
值得注意的事情
1注意数据与解析数据的信号(比如传入的数据中0或者1的个数),要在时序上与数据进行对齐。
如果解析数据的信号是时序信号,那么它将会滞后数据一个clk。如果后面要用到,数据与对应的解析数据的信号同时做条件,那么需要对数据进行打一拍处理。用打拍过后的数据与解析数据的信号作为条件去做判断。
2如果一个数据是有符号位的,那么判断它的正负,就不能用简单的 > 0 来判断。
比如本实验中cnt就是5位宽的有符号数,判断其正负(cnt[4] == 1) 为负数;(cnt[4] == 0) 为正数。如果是cnt < 0 表述负数的话,那么cnt永远不会被判定为负数。
看仿真波形的经验之谈:
1在本实验与官方的编码模块代码,那么在通过功能方正,验证自己编写的编码模块代码是否正确时,可以把官方的模块例化进工程,然后仿真对比波形是否一致,即可判断自己编写的代码是否正确。
2在遇到不一致的时候,比如输入信号都一致,某个输出信号不一致。
先不要着急看去检查哪些信号会直接影响这个输出信号赋值,因为前面的一些信号,会间接影响输出信号赋值。比如影响直接影响输出信号赋值的信号。
那么此时应该先检查一些直接受输入信号影响的信号,是否正确,是否和官方给的代码波形一致;
然后再看一些”条件“波形是否正确;
也就是说由输入信号逐渐到输出信号这个过程中一次产生(你编写代码的顺序)去检查这些信号是否正确。(自顶向下)。慢慢检查。
3实在检查不出来,看看自己的代码和官方的代码编写的差在哪里。
TMDS通信协议:
一种传输视频/音频/辅助数据的传输技术:最小化传输差分信号。
采用DIV编码。附上几张图:
8bit像素信息到最小化传输直流均衡10bit数据,编码流程:
信源端与接收端的数据传输框图:由图可知,3路差分信号传输,由rgb888转10bit的数据。一路差分信号传输clock。
下面两路ddc信号是从接收端读取硬件工作模式相关信息的,本实验没有用到。(IIC通信协议。)
模块框图:
时序图:
代码:
/*********************
功能: 将8位rgb信号,转换成10位信号。编码
cnt 当前时钟的值
cnt(t - 1) 相对于当前时钟,上一个时钟周期的值
*/
module encode(
input wire clk_25 ,
input wire rst_n ,
input wire [7:0] data_in ,
input wire hsync ,
input wire vsync ,
input wire DE ,
output wire [9:0] data_out
);
reg [7:0] data_in_reg1 ;
reg [3:0] data_in_n1 ; // 保存传入数据1的个数.
wire crtl_1 ; // 一个判断条件,data_in中1的个数大于4 or 个数等于4且data_in[0] == 0
wire [8:0] q_m ;
reg [8:0] q_m_reg1 ;
reg DE_reg1 ;
reg DE_reg2 ;
reg C0_reg1 ; // 行同步信号,打拍,为了和数据对齐。
reg C1_reg1 ; // 场同步信号,打拍,为了和数据对齐。
reg C0_reg2 ;
reg C1_reg2 ;
reg [9:0] q_out ; // 最终要输出的10为数据,经过一系列编码。
wire ctrl_2 ;
wire ctrl_3 ;
reg [4:0] cnt ; // 这是一个用于跟踪数据流差异的寄存器。正值表示已传输的“1”的超额数量。负值表示已传输的“0”的超额数量。
reg [3:0] q_m_n1 ; // q_m中1的个数。cnt的最高位是符号位。有符号位的数据不能简单通过是否大于小于0来判断正负,应该判断最高位符号位的0/1(1负数,0正数)
reg [3:0] q_m_n0 ; // q_m中0的个数。
/**********************************************************************************/
// 产生第一个条件:计算输入数据中1的个数。
always @(posedge clk_25 or negedge rst_n) begin // 计算传入数据1的个数.
if(~rst_n) begin
data_in_n1 <= 4'd0 ;
end else begin
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] ;
end
end
// 对输入数据打一拍,以对齐数据data_in_n1。
always @(posedge clk_25 or negedge rst_n) begin
if(~rst_n) begin
data_in_reg1 <= 8'd0 ;
end else begin
data_in_reg1 <= data_in ;
end
end
// 根据data_in_n1与输入数据的打拍信号,描述出crtl_1判断条件。
assign crtl_1 = ( data_in_n1 > 4 || (data_in_n1 == 4 && data_in_reg1[0] == 1'b0) ) ? 1'b1 : 1'b0 ;
// 8bit 到 9bit 编码逻辑。
assign q_m[0] = data_in_reg1[0] ;
assign q_m[1] = (crtl_1) ? q_m[0]~^data_in_reg1[1] : q_m[0]^data_in_reg1[1] ;
assign q_m[2] = (crtl_1) ? q_m[1]~^data_in_reg1[2] : q_m[1]^data_in_reg1[2] ;
assign q_m[3] = (crtl_1) ? q_m[2]~^data_in_reg1[3] : q_m[2]^data_in_reg1[3] ;
assign q_m[4] = (crtl_1) ? q_m[3]~^data_in_reg1[4] : q_m[3]^data_in_reg1[4] ;
assign q_m[5] = (crtl_1) ? q_m[4]~^data_in_reg1[5] : q_m[4]^data_in_reg1[5] ;
assign q_m[6] = (crtl_1) ? q_m[5]~^data_in_reg1[6] : q_m[5]^data_in_reg1[6] ;
assign q_m[7] = (crtl_1) ? q_m[6]~^data_in_reg1[7] : q_m[6]^data_in_reg1[7] ;
assign q_m[8] = (crtl_1) ? 1'b0 : 1'b1 ;
/********************* 对q_out和cnt赋值,用到的条件 ***************************/
// 数据q_m中1的个数和0的个数。
always @(posedge clk_25 or negedge rst_n) begin
if(~rst_n) 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 <= ~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
end
// 根据cnt和q_m_n1 q_m_n2 产生判断条件ctrl_2,ctrl_3。
assign ctrl_2 = ((cnt == 5'd0) || (q_m_n1 == q_m_n0)) ? 1'b1 : 1'b0 ; // 其实可以直接写逻辑运算关系式。
assign ctrl_3 = (((~cnt[4]) && (q_m_n1 > q_m_n0)) || ((cnt[4]) && (q_m_n1 < q_m_n0))) ? 1'b1 : 1'b0 ;
// reg [8:0] q_m_reg1 ;
// 由于q_m_n1 q_m_n0 是时序逻辑,相对于q_m就延后了一个时钟周期,为了保证qm数据与q_m_n1 q_m_n0 对齐,所以要对qm打一拍。
always @(posedge clk_25 or negedge rst_n) begin
if(~rst_n) begin
q_m_reg1 <= 8'd0 ;
end else begin
q_m_reg1 <= q_m ;
end
end
// reg DE_reg1 ;
// reg DE_reg2 ;
// DE 逐渐的要与q_m_reg1对齐
always @(posedge clk_25 or negedge rst_n) begin
if(~rst_n) begin
DE_reg1 <= 1'b0 ;
DE_reg2 <= 1'b0 ;
end else begin
DE_reg1 <= DE ;
DE_reg2 <= DE_reg1 ;
end
end
// reg C0_reg1 ; // 行同步信号,打拍,为了和数据对齐。
// reg C0_reg2 ;
// 行场同步信号,也是要与q_m_reg1对齐。
always @(posedge clk_25 or negedge rst_n) begin
if(~rst_n) begin
C0_reg1 <= 1'b0 ;
C0_reg2 <= 1'b0 ;
end else begin
C0_reg1 <= hsync ;
C0_reg2 <= C0_reg1 ;
end
end
// reg C1_reg1 ; // 场同步信号,打拍,为了和数据对齐。
// reg C1_reg2 ;
always @(posedge clk_25 or negedge rst_n) begin
if(~rst_n) begin
C1_reg1 <= 1'b0 ;
C1_reg2 <= 1'b0 ;
end else begin
C1_reg1 <= vsync ;
C1_reg2 <= C1_reg1 ;
end
end
// 对q_out 与 cnt 赋值
// q_out
always @(posedge clk_25 or negedge rst_n) begin
if(~rst_n) begin
q_out <= 10'd0 ;
cnt <= 5'd0 ;
end else begin
if(DE_reg2 == 1'b0) begin
case ({C1_reg2 ,C0_reg2})
2'b00: q_out[9:0] <= 10'b11010_10100 ;
2'b01: q_out[9:0] <= 10'b00101_01011 ;
2'b10: q_out[9:0] <= 10'b01010_10100 ;
2'b11: q_out[9:0] <= 10'b10101_01011 ;
default: q_out[9:0] <= 10'b11010_10100 ; // 随便挑一个。
endcase
cnt <= 5'd0 ;
end else begin
if(ctrl_2 == 1'b1) begin
q_out[9] <= ~q_m_reg1[8] ; // q_out[9] <= ~q_m_reg1[8] ;
q_out[8] <= q_m_reg1[8] ; // q_out[8] <= q_m_reg1[8] ;
q_out[7:0] <= (q_m_reg1[8] == 1'b1) ? q_m_reg1[7:0] : ~q_m_reg1[7:0] ; // q_out[7:0] = (q_m_reg1[8] == 1'b1) ? q_m_reg1[7:0] : ~q_m_reg1[7:0] ;
if(q_m_reg1[8] == 1'b0) begin
cnt <= cnt + q_m_n0 - q_m_n1 ;
end else begin
cnt <= cnt - q_m_n0 + q_m_n1 ;
end
end else begin
if(ctrl_3 == 1'b1) begin
q_out[9] <= 1'b1 ;
q_out[8] <= q_m_reg1[8] ;
q_out[7:0] <= ~q_m_reg1[7:0] ;
cnt <= cnt + {q_m_reg1[8], 1'b0} + q_m_n0 - q_m_n1 ; // 2 乘以 1位宽的数据,相当于把该数据左移一位。 + {q_m_reg1[8],0} +
end else begin
q_out[9] <= 1'b0 ;
q_out[8] <= q_m_reg1[8] ;
q_out[7:0] <= q_m_reg1[7:0] ;
cnt <= cnt - {~q_m_reg1[8], 1'b0} - q_m_n0 + q_m_n1 ;
end
end
end
end
end
/************output signal descrable **************/
assign data_out = q_out ;
endmodule
// 10位并行数据信号转换为,串行差分信号。
module par_to_ser(
input wire clk_125 ,
input wire rst_n ,
input wire [9:0] data_out, // 应该在一个周期内把并行转串行?应该用到流水线技术?
output wire ser_p ,
output wire ser_n
);
/**********************************************************************************
// // wire signal define
// wire dataout_flag ;
// wire [3:0] datain_h_num ;
// wire [3:0] datain_l_num ;
// // reg signal define
// reg datain_h ;
// reg datain_l ;
// reg [3:0] cnt_bit ;
// reg [9:0] data_out_reg1 ;
//
// // wire dataout_flag ;
// assign dataout_flag = (data_out != data_out_reg1) ? 1'b1 : 1'b0 ;
// assign datain_h_num = 2'd2*cnt_bit ;
// assign datain_l_num = 2'd2*cnt_bit + 1'b1 ;
// // reg datain_h ;
// always @(posedge clk_125 or negedge rst_n) begin
// if(~rst_n) begin
// datain_h <= 1'b0 ;
// end else begin
// datain_h <= data_out_reg1[datain_h_num] ; // 其实这里可能有一个问题,datain_h_num如果计算慢了,那么datain_h将会滞后一个clk_125.
// end
// end
// // reg datain_l ;
// always @(posedge clk_125 or negedge rst_n) begin
// if(~rst_n) begin
// datain_l <= 1'b0 ;
// end else begin
// datain_l <= data_out_reg1[datain_l_num] ;
// end
// end
// // reg [3:0] cnt_bit ;
// always @(posedge clk_125 or negedge rst_n) begin
// if(~rst_n) begin
// cnt_bit <= 4'd0 ;
// end else begin
// if(dataout_flag == 1'b1 || cnt_bit == 4'd4) begin
// cnt_bit <= 4'd0 ;
// end else begin
// cnt_bit <= cnt_bit + 1'b1 ;
// end
// end
// end
// // reg [9:0] data_out_reg1 ;
// always @(posedge clk_125 or negedge rst_n) begin
// if(~rst_n) begin
// data_out_reg1 <= 10'd0 ;
// end else begin
// data_out_reg1 <= data_out ;
// end
// end
************************************************************************************/
/***********************************************************************************/
// 野火教程里的方法,10bit并行数据转串行数据。
// wire signal degine
wire dataout_flag ;
wire [4:0] data_rise ;
wire [4:0] data_fall ;
// reg signal define
reg [3:0] cnt_shift ;
reg [9:0] data_out_reg1 ;
reg [4:0] data_rise_s ;
reg [4:0] data_fall_s ;
// [4:0] data_rise ;
assign data_rise = {data_out[8],data_out[6],data_out[4],data_out[2],data_out[0]} ;
// [4:0] data_fall ;
assign data_fall = {data_out[9],data_out[7],data_out[5],data_out[3],data_out[1]} ;
// wire dataout_flag;
assign dataout_flag = (data_out != data_out_reg1) ? 1'b1 : 1'b0 ;
// reg [9:0] data_out_reg1 ;
always @(posedge clk_125 or negedge rst_n) begin
if(~rst_n) begin
data_out_reg1 <= 10'd0 ;
end else begin
data_out_reg1 <= data_out ;
end
end
// reg [3:0] cnt_shift ;
always @(posedge clk_125 or negedge rst_n) begin
if(~rst_n) begin
cnt_shift <= 4'd0 ;
end else begin
if(dataout_flag == 1'b1 || cnt_shift == 4'd4) begin
cnt_shift <= 4'd0 ;
end else begin
cnt_shift <= cnt_shift + 1'b1 ;
end
end
end
// reg [4:0] data_rise_s ;
// reg [4:0] data_fall_s ;
always @(posedge clk_125 or negedge rst_n) begin
if(~rst_n) begin
data_rise_s <= 5'd0 ;
data_fall_s <= 5'd0 ;
end else begin
if(cnt_shift == 4'd4) begin
data_rise_s <= data_rise ;
data_fall_s <= data_fall ;
end else begin
data_rise_s <= data_rise_s[4:1] ; // 相当于右移1位。
data_fall_s <= data_fall_s[4:1] ;
end
end
end
// 单沿到双沿采样,通过调用ddio_out实现。相当于把采样时钟翻倍。
ddio_out ddio_out_inst0 (
.datain_h ( data_rise_s[0] ), // 在outclk上升沿对datain_h采样传给data_out。1bit的串行数据。
.datain_l ( data_fall_s[0] ), // 在outclk下降沿对datain_l采样传给data_out。1bit的串行数据。
.outclock ( ~clk_125 ), // 采样时钟。
.dataout ( ser_p )
);
ddio_out ddio_out_inst1 (
.datain_h ( ~data_rise_s[0] ), // 单端到差分之间的转换。
.datain_l ( ~data_fall_s[0] ),
.outclock ( ~clk_125 ),
.dataout ( ser_n )
);
endmodule
只放了两个新模块的代码。
`timescale 1ns/1ns
module test();
reg sys_clk ;
reg sys_rst_n ;
wire clk_p ;
wire clk_n ;
wire r_p ;
wire r_n ;
wire g_p ;
wire g_n ;
wire b_p ;
wire b_n ;
wire DDC_SCL ;
wire DDC_SDA ;
top top_insert(
.sys_clk ( sys_clk ) ,
.sys_rst_n ( sys_rst_n ) ,
.clk_p ( clk_p ) ,
.clk_n ( clk_n ) ,
.r_p ( r_p ) ,
.r_n ( r_n ) ,
.g_p ( g_p ) ,
.g_n ( g_n ) ,
.b_p ( b_p ) ,
.b_n ( b_n ) ,
.DDC_SCL ( DDC_SCL ) ,
.DDC_SDA ( DDC_SDA )
);
parameter CYCLE = 20 ;
initial begin
sys_clk = 1'b1 ;
sys_rst_n = 1'b0 ;
#( CYCLE ) ;
sys_rst_n = 1'b1 ;
end
always #( CYCLE / 2 ) sys_clk = ~sys_clk ;
endmodule