一、初衷
HDMI是之前项目的一部分,本来不打算上传博客的,最近我们专业课设开始,恰好可以选择完成HDMI显示,也重新整理一下之前的项目,对于课设,这只是实现了基本功能,附加条件等过几天再更;希望这篇博客能帮助更多的初学者,结尾附代码;
二、开发平台
这个项目开发板我使用的是Xilinx的zynq-7030,读者可进行更改管脚进行更换;调用了vivado的pll以及ROM的IP;读者需要进行配置,在此,我就不做介绍,请自行百度;
三、项目介绍
先将图片转为coe文件存入ROM中,从ROM中读取24位真彩数据通过HDMI进行显示,显示的大小设置为1280*1024,如下所示:
图片大小为500*500;读者可根据需要自行更改;
由于我正在一家公司实习,开发板无法使用,故没有展示环节,代码曾经下过开发板了,可以正常使用;故只提供仿真波形,如果有问题可以留言,我看到就会回复;
四、HDMI接口
英文全称:H-High,D-Definition,M-Multimedia,I-Interface;高清晰度多媒体接口。
HDMI的数据传输有TMDS0,TMDS1,TMDS2三个通道,每个通道的传输流程都是一样的:
如上图所示,发送端将8bit的数据进入TMDS编码器,得到抗干扰性强的10bit TMDS信号,然后再进行串行化输出,在接收端收到串行的HDMI信号后,进行信号复原,得到10bit的TMDS信号,最后用TMDS解码器解码得到原来的8bit数据。
本项目使用的是1.3版本的HDMI接口,它支持的最大速率为10.2Gbps,而1.3版本的一个HDMI包括3个TMDS数据通道和1个TMDS时钟通道。什么是TMDS呢?
——TMDS(Transition Minimized Differential signal),最小化传输差分信号。
下图能很好的解释差分信号的优势:
它能很好的屏蔽噪声的影响;
实际上,我们只需要完成发送端这部分,即:
第一步:将8位并行RED数据发送到TMDS Tx。
第二步:进行最小化传输处理,加上第9位,即编码过程。第9位数据称为编码位。
第三步:并/串转换。
第四步:串行转差分
对第二步进行补充:每个数据通道都通过编码算法(异或、异或非等),将8位数据转换成10位数据,前8位数据由原始信号经运算后获得,第9位指示运算的方式,第10位用来对应直流平衡。通过这种算法,会使得数据的传输和恢复更加可靠。
对传输速率及时钟的一些补充:
PCLK:像素时钟
以1920x1080p/60hz为例:1920*1080*60=124.4MHz
以1280x720p/60hz为例:1280*720*60=55.3MHz
带宽:1s内传输的数据量(bit)
4K频率需要的带宽:选最常用的3840x2160分辨率,色深的话常用的是8位,RGB三色就是24bit,我们的目标是60Hz刷新率(60fps)→→→ 3840*2160*24bit*60fps=11.94Gbps
HDMI1.3/1.4版本像素时钟高达340MHz,即最大带宽是:
→→→ 340MHz*10bit(10bit编码)*3(3个数据通道)=10.2Gbps
但是由于HDMI采用的是8bit/10bit编码方式,实际效率是理论值的80%,所以10.2Gbps能传输的最大视频带宽是10.2*0.8=8.1Gbps
则本项目
- 频率:1280*1024*60 = 78.6Mhz,为什么不一样?我觉得可能是图像无效区也应该是进去;
- 实际频率:1688*1066*60 = 107.9Mhz,与定义的相同;
- 速率:108*10*3 = 3.24Gpbs;
五、代码讲解
(1)顶层:
由于我使用的开发板输入的是差分时钟,所以输入时钟是两对;
注意观察可以发现,有个540M的时钟;由于将并行的10bit数据转换为串行的数据,时钟频率需要快10倍;但是是差分输出,所以只需要5倍时钟,即540M;
//**************************************************************************
// *** 名称 : hdmixs_top
// *** 作者 : 不想上体育课
// *** 博客 : https://blog.csdn.net/m0_47220307?type=blog
// *** 日期 : 2022.09
// *** 描述 : top
//**************************************************************************
`timescale 1ns / 1ps
module hdmixs_top(
input sys_clk_n ,//差分时钟输入
input sys_clk_p ,
input sys_rst_n ,
output tmds_clk_n,//TMDS时钟通道
output tmds_clk_p,
output [2:0] tmds_data_n,//TMDS数据通道
output [2:0] tmds_data_p
);
wire rst_n ;
wire clk_out1;
wire clk_out2;
wire locked;
wire hdmi_hs;
wire hdmi_vs;
wire [10:0] w_x_pixel;
wire [10:0] w_y_pixel;
// wire [15:0] pixel_data;
wire hdmi_de;
wire [23:0] rgb_data;
wire tmds_oen;//TMDS输出使能
wire [23:0] pixel_data;
assign rst_n=sys_rst_n&locked; //在保证时钟稳定的情况下,才保证正常工作
assign clk_out1 = sys_clk_n;
assign clk_out2 = sys_clk_p;
clk_wiz_0 clk_div(
.clk_out1 (clk_108M ), //输出108MHZ的频率
.clk_out2 (clk_540M ),
.reset (1'b0 ),
.locked (locked ),
.clk_in1_p (sys_clk_p ),
.clk_in1_n (sys_clk_n )
);
hdmixs_driver dri(
.clk_out1 (clk_108M ),
.rst_n (rst_n ),
.pixel_data (pixel_data ),
.hdmi_hs (hdmi_hs ),
.hdmi_vs (hdmi_vs ),
.hdmi_de (hdmi_de ),
.vedio_rgb (rgb_data ),
.x_pixel (w_x_pixel ),
.y_pixel (w_y_pixel )
);
hdmixs_display dis(
.clk_out1 (clk_108M ),
.rst_n (rst_n ),
.x_pixel (w_x_pixel ),
.y_pixel (w_y_pixel ),
.pixel_data (pixel_data )
);
rgb_to_hdmi urtg(
.clk_out1 (clk_108M ), //输出108MHZ的频率
.clk_out2 (clk_540M ),
.rst_n (rst_n ),
.hdmi_de (hdmi_de ),
.pixel_data (rgb_data ),
.hdmi_hs (hdmi_hs ),
.hdmi_vs (hdmi_vs ),
.tmds_clk_n (tmds_clk_n ),
.tmds_clk_p (tmds_clk_p ),
.tmds_oen (tmds_oen ),
.tmds_data_n (tmds_data_n ),
.tmds_data_p (tmds_data_p )
);
endmodule
(2) 显示驱动设置:
此模块可以定义显示的屏幕设置,以及输出当前行列计数值和图像数据;
//**************************************************************************
// *** 名称 : hdmixs_driver
// *** 作者 : 不想上体育课
// *** 博客 : https://blog.csdn.net/m0_47220307?type=blog
// *** 日期 : 2022.09
// *** 描述 : 显示大小设置
//**************************************************************************
`timescale 1ns / 1ps
module hdmixs_driver(
input clk_out1 ,
input rst_n ,
input [23:0] pixel_data,
output reg hdmi_de ,
output hdmi_hs ,
output hdmi_vs ,
output [23:0] vedio_rgb ,
output [ 10:0] y_pixel , //y像素坐标
output [ 10:0] x_pixel //x像素坐标
);
//1280*1024@60Hz 108Mhz
parameter Hor_Total_Time = 11'd1688; //
parameter Hor_Sync = 10'd112; //
parameter Hor_Back_Porch = 10'd248; //
parameter Hor_Addr_Time = 11'd1280; //
parameter Hor_Front_Porch = 10'd48; //
parameter Ver_Total_Time = 11'd1066; //
parameter Ver_Sync = 10'd3; //
parameter Ver_Back_Porch = 10'd38; //
parameter Ver_Addr_Time = 11'd1024; //
parameter Ver_Front_Porch = 10'd1; //
//行列计数器
reg [10:0] x_cnt;
reg [10:0] y_cnt;
always@(posedge clk_out1 or negedge rst_n)
begin
if(!rst_n)
begin
x_cnt<= 11'd0;
y_cnt<= 11'd0;
end
else if(x_cnt==Hor_Total_Time)
begin
x_cnt<= 11'd0;
if(y_cnt==Ver_Total_Time)
y_cnt<=11'd0;
else
y_cnt<= y_cnt+1'b1;
end
else
x_cnt<= x_cnt + 1'b1;
end
assign hdmi_hs=(x_cnt < Hor_Sync)?1'b0:1'b1;
assign hdmi_vs=(y_cnt < Ver_Sync)?1'b0:1'b1;
always@(*) //使能、以及得到像素坐标
begin
hdmi_de<=(x_cnt>(Hor_Sync + Hor_Back_Porch)&&
x_cnt<=(Hor_Sync+Hor_Back_Porch+Hor_Addr_Time)&&
y_cnt>(Ver_Sync+Ver_Back_Porch)&&
y_cnt<=(Ver_Sync+Ver_Back_Porch+Ver_Addr_Time));
end
assign vedio_rgb=(hdmi_de)?pixel_data:24'd0;
assign x_pixel=(hdmi_de)?(x_cnt-(Hor_Sync + Hor_Back_Porch)):10'd0; //行像素坐标
assign y_pixel=(hdmi_de)?(y_cnt-(Ver_Sync + Ver_Back_Porch)):10'd0; //列像素坐标
endmodule
(3)图像显示模块
此模块可以设置需要显示的图片大小及位置,并输出图像;
//**************************************************************************
// *** 名称 : hdmixs_display
// *** 作者 : 不想上体育课
// *** 博客 : https://blog.csdn.net/m0_47220307?type=blog
// *** 日期 : 2022.09
// *** 描述 : 显示图像区域设置
//**************************************************************************
`timescale 1ns / 1ps
module hdmixs_display(
input wire clk_out1 ,
input wire rst_n ,
input wire [10:0] x_pixel ,
input wire [10:0] y_pixel ,
output reg [23:0] pixel_data
);
parameter height = 10'd500, width = 10'd500;
parameter pos_x0 = 320, pos_y0 = 272;// picture
wire rom_rd_en0; //读ROM使能信号
wire [32:0] total;
wire [23:0] rom_data0;
reg [23:0] rom_addr0; //读ROM有效信号
assign total = height*width;
assign rom_rd_en0 =(x_pixel > pos_x0)&&(x_pixel<=pos_x0+width)&&(y_pixel>pos_y0)&&(y_pixel<=pos_y0+height)?1'b1:1'b0; //first picture location
always @(posedge clk_out1 or negedge rst_n)
begin
if(!rst_n)
begin
pixel_data <= 24'b0;
end
else if(rom_rd_en0)
pixel_data <= rom_data0;
else
pixel_data <= 24'h000_000;
end
always@(posedge clk_out1 or negedge rst_n)
begin
if(!rst_n)
rom_addr0<=24'd0;
else if(rom_rd_en0)
begin
if(rom_addr0<total-1'b1)
rom_addr0<=rom_addr0+1'b1;
else
rom_addr0<=24'd0;
end
else
rom_addr0<=rom_addr0;
end
blk_mem_gen_0 rom1(
.addra(rom_addr0),
.ena(1),
.clka(clk_out1),
.douta(rom_data0)
);
endmodule
(4)rgb2hdmi模块
这个模块例化了其他两个子模块;实现了8bit/10bit,以及并行转串行;并使用原语OBUFDS进行串行转差分;
//**************************************************************************
// *** 名称 : rgb_to_hdmi
// *** 作者 : 不想上体育课
// *** 博客 : https://blog.csdn.net/m0_47220307?type=blog
// *** 日期 : 2022.09
// *** 描述 : 差分输出
//**************************************************************************
`timescale 1ns / 1ps
module rgb_to_hdmi(
input clk_out1 ,
input clk_out2 ,
input rst_n ,
input hdmi_de ,
input [24:0] pixel_data ,
input hdmi_hs ,
input hdmi_vs ,
output tmds_clk_n ,
output tmds_clk_p ,
output tmds_oen ,
output [2:0] tmds_data_n ,
output [2:0] tmds_data_p
);
wire [9:0] clk_10bit;
wire syn_rst;
wire [9:0] encode_red;
wire [9:0] encode_green;
wire [9:0] encode_blue;
wire [2:0] serial_data;
wire serial_out_clk;
assign clk_10bit=10'b11111_00000;
assign tmds_oen=1'b1;
asyn_to_syn syn(
.clk(clk_out1),
.rst_n(rst_n),
.syn_rst(syn_rst)
);
encoder r(
.clkin(clk_out1),
.rstin(syn_rst),
.din(pixel_data[23:16]),
.c0(1'b0),
.c1(1'b0),
.de(hdmi_de),
.dout(encode_red)
);
encoder g(
.clkin(clk_out1),
.rstin(syn_rst),
.din(pixel_data[15:8]),
.c0(1'b0),
.c1(1'b0),
.de(hdmi_de),
.dout(encode_green)
);
encoder b(
.clkin(clk_out1),
.rstin(syn_rst),
.din(pixel_data[7:0]),
.c0(hdmi_hs),
.c1(hdmi_vs),
.de(hdmi_de),
.dout(encode_blue)
);
serializer red(
.paralell_data(encode_red),
.paralell_clk(clk_out1),
.reset(syn_rst),
.serial_clk(clk_out2),
.serial_data_out(serial_data[2])
);
serializer green(
.paralell_data(encode_green),
.paralell_clk(clk_out1),
.reset(syn_rst),
.serial_clk(clk_out2),
.serial_data_out(serial_data[1])
);
serializer blue(
.paralell_data(encode_blue),
.paralell_clk(clk_out1),
.reset(syn_rst),
.serial_clk(clk_out2),
.serial_data_out(serial_data[0])
);
serializer clk(
.paralell_data(clk_10bit),
.paralell_clk(clk_out1),
.reset(syn_rst),
.serial_clk(clk_out2),
.serial_data_out(serial_out_clk)
);
OBUFDS #(
.IOSTANDARD("TMDS_33"), // Specify the output I/O standard
.SLEW("SLOW") // Specify the output slew rate
) TMDS0 (
.O(tmds_data_p[0]), // Diff_p output (connect directly to top-level port)
.OB(tmds_data_n[0]), // Diff_n output (connect directly to top-level port)
.I(serial_data[0]) // Buffer input
);
OBUFDS #(
.IOSTANDARD("TMDS_33"), // Specify the output I/O standard
.SLEW("SLOW") // Specify the output slew rate
) TMDS1 (
.O(tmds_data_p[1]), // Diff_p output (connect directly to top-level port)
.OB(tmds_data_n[1]), // Diff_n output (connect directly to top-level port)
.I(serial_data[1]) // Buffer input
);
OBUFDS #(
.IOSTANDARD("TMDS_33"), // Specify the output I/O standard
.SLEW("SLOW") // Specify the output slew rate
) TMDS2 (
.O(tmds_data_p[2]), // Diff_p output (connect directly to top-level port)
.OB(tmds_data_n[2]), // Diff_n output (connect directly to top-level port)
.I(serial_data[2]) // Buffer input
);
OBUFDS #(
.IOSTANDARD("TMDS_33"), // Specify the output I/O standard
.SLEW("SLOW") // Specify the output slew rate
) TMDS_clk (
.O(tmds_clk_p), // Diff_p output (connect directly to top-level port)
.OB(tmds_clk_n), // Diff_n output (connect directly to top-level port)
.I(serial_out_clk) // Buffer input
);
endmodule
(5)编码模块:
实现了8bit/10bit
//**************************************************************************
// *** 名称 : encoder
// *** 作者 : 不想上体育课
// *** 博客 : https://blog.csdn.net/m0_47220307?type=blog
// *** 日期 : 2022.09
// *** 描述 : 编码模块
//**************************************************************************
`timescale 1ns / 1ps
module encoder(
input clkin, // pixel clock input
input rstin, // async. reset input (active high)
input [7:0] din, // data inputs: expect registered
input c0, // c0 input
input c1, // c1 input
input de, // de input
output reg [9:0] dout // data outputs
);
// Counting number of 1s and 0s for each incoming pixel
// component. Pipe line the result.
// Register Data Input so it matches the pipe lined adder
// output
reg [3:0] n1d; //number of 1s in din
reg [7:0] din_q;
always @ (posedge clkin) begin
n1d <=#1 din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
din_q <=#1 din;
end
///
// Stage 1: 8 bit -> 9 bit
// Refer to DVI 1.0 Specification, page 29, Figure 3-5
///
wire decision1;
assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q[0] == 1'b0));
/*
reg [8:0] q_m;
always @ (posedge clkin) begin
q_m[0] <=#1 din_q[0];
q_m[1] <=#1 (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
q_m[2] <=#1 (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
q_m[3] <=#1 (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
q_m[4] <=#1 (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
q_m[5] <=#1 (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
q_m[6] <=#1 (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
q_m[7] <=#1 (decision1) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);
q_m[8] <=#1 (decision1) ? 1'b0 : 1'b1;
end
*/
wire [8:0] q_m;
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;
/
// Stage 2: 9 bit -> 10 bit
// Refer to DVI 1.0 Specification, page 29, Figure 3-5
/
reg [3:0] n1q_m, n0q_m; // number of 1s and 0s for q_m
always @ (posedge clkin) begin
n1q_m <=#1 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 <=#1 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
parameter CTRLTOKEN0 = 10'b1101010100;
parameter CTRLTOKEN1 = 10'b0010101011;
parameter CTRLTOKEN2 = 10'b0101010100;
parameter CTRLTOKEN3 = 10'b1010101011;
reg [4:0] cnt; //disparity counter, MSB is the sign bit
wire decision2, decision3;
assign decision2 = (cnt == 5'h0) | (n1q_m == n0q_m);
/
// [(cnt > 0) and (N1q_m > N0q_m)] or [(cnt < 0) and (N0q_m > N1q_m)]
/
assign decision3 = (~cnt[4] & (n1q_m > n0q_m)) | (cnt[4] & (n0q_m > n1q_m));
// pipe line alignment
reg de_q, de_reg;
reg c0_q, c1_q;
reg c0_reg, c1_reg;
reg [8:0] q_m_reg;
always @ (posedge clkin) begin
de_q <=#1 de;
de_reg <=#1 de_q;
c0_q <=#1 c0;
c0_reg <=#1 c0_q;
c1_q <=#1 c1;
c1_reg <=#1 c1_q;
q_m_reg <=#1 q_m;
end
///
// 10-bit out
// disparity counter
///
always @ (posedge clkin or posedge rstin) begin
if(rstin) begin
dout <= 10'h0;
cnt <= 5'h0;
end else begin
if (de_reg) begin
if(decision2) begin
dout[9] <=#1 ~q_m_reg[8];
dout[8] <=#1 q_m_reg[8];
dout[7:0] <=#1 (q_m_reg[8]) ? q_m_reg[7:0] : ~q_m_reg[7:0];
cnt <=#1 (~q_m_reg[8]) ? (cnt + n0q_m - n1q_m) : (cnt + n1q_m - n0q_m);
end else begin
if(decision3) begin
dout[9] <=#1 1'b1;
dout[8] <=#1 q_m_reg[8];
dout[7:0] <=#1 ~q_m_reg;
cnt <=#1 cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);
end else begin
dout[9] <=#1 1'b0;
dout[8] <=#1 q_m_reg[8];
dout[7:0] <=#1 q_m_reg[7:0];
cnt <=#1 cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);
end
end
end else begin
case ({c1_reg, c0_reg})
2'b00: dout <=#1 CTRLTOKEN0;
2'b01: dout <=#1 CTRLTOKEN1;
2'b10: dout <=#1 CTRLTOKEN2;
default: dout <=#1 CTRLTOKEN3;
endcase
cnt <=#1 5'h0;
end
end
end
endmodule
(6)并行数据转串行
使用原语OSERDESE2进行串行转差分操作;
//**************************************************************************
// *** 名称 : serializer
// *** 作者 : 不想上体育课
// *** 博客 : https://blog.csdn.net/m0_47220307?type=blog
// *** 日期 : 2022.09
// *** 描述 : 并行数据转串行
//**************************************************************************
`timescale 1ns / 1ps
module serializer(
input reset ,
input paralell_clk, //并行
input [9:0] paralell_data,
input serial_clk, //串行,比并行数据高
output serial_data_out
);
wire cascade1;
wire cascade2;
OSERDESE2 #(
.DATA_RATE_OQ("DDR"), // DDR, SDR
.DATA_RATE_TQ("SDR"), // DDR, BUF, SDR
.DATA_WIDTH(10), // Parallel data width (2-8,10,14)
.SERDES_MODE("MASTER"), // MASTER, SLAVE
.TBYTE_CTL("FALSE"), // Enable tristate byte operation (FALSE, TRUE)
.TBYTE_SRC("FALSE"), // Tristate byte source (FALSE, TRUE)
.TRISTATE_WIDTH(1) // 3-state converter width (1,4)
)
OSERDESE2_MAS (
.CLK(serial_clk), // 1-bit input: High speed clock
.CLKDIV(paralell_clk), // 1-bit input: Divided clock
.RST(reset), // 1-bit input: Reset
.OCE(1'b1), // 1-bit input: Output data clock enable
.OQ(serial_data_out), // 1-bit output: Data path output
// D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
.D1(paralell_data[0]),
.D2(paralell_data[1]),
.D3(paralell_data[2]),
.D4(paralell_data[3]),
.D5(paralell_data[4]),
.D6(paralell_data[5]),
.D7(paralell_data[6]),
.D8(paralell_data[7]),
.SHIFTOUT1(),
.SHIFTOUT2(),
.SHIFTIN1(cascade1),
.SHIFTIN2(cascade2),
.OFB(), // 1-bit output: Feedback path for data
// T1 - T4: 1-bit (each) input: Parallel 3-state inputs
.T1(1'b0),
.T2(1'b0),
.T3(1'b0),
.T4(1'b0),
.TBYTEIN(1'b0), // 1-bit input: Byte group tristate
.TCE(1'b0), // 1-bit input: 3-state clock enable
.TBYTEOUT(), // 1-bit output: Byte group tristate
.TFB(), // 1-bit output: 3-state control
.TQ() // 1-bit output: 3-state control
);
OSERDESE2 #(
.DATA_RATE_OQ("DDR"), // DDR, SDR
.DATA_RATE_TQ("SDR"), // DDR, BUF, SDR
.DATA_WIDTH(10), // Parallel data width (2-8,10,14)
.SERDES_MODE("SLAVE"), // MASTER, SLAVE
.TBYTE_CTL("FALSE"), // Enable tristate byte operation (FALSE, TRUE)
.TBYTE_SRC("FALSE"), // Tristate byte source (FALSE, TRUE)
.TRISTATE_WIDTH(1) // 3-state converter width (1,4)
)
OSERDESE2_SLA (
.CLK(serial_clk), // 1-bit input: High speed clock
.CLKDIV(paralell_clk), // 1-bit input: Divided clock
.RST(reset), // 1-bit input: Reset
.OCE(1'b1), // 1-bit input: Output data clock enable
.OQ(), // 1-bit output: Data path output
// D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
.D1(1'b0),
.D2(1'b0),
.D3(paralell_data[8]),
.D4(paralell_data[9]),
.D5(1'b0),
.D6(1'b0),
.D7(1'b0),
.D8(1'b0),
.SHIFTOUT1(cascade1),
.SHIFTOUT2(cascade2),
.SHIFTIN1(),
.SHIFTIN2(),
.OFB(), // 1-bit output: Feedback path for data
// T1 - T4: 1-bit (each) input: Parallel 3-state inputs
.T1(1'b0),
.T2(1'b0),
.T3(1'b0),
.T4(1'b0),
.TBYTEIN(1'b0), // 1-bit input: Byte group tristate
.TCE(1'b0), // 1-bit input: 3-state clock enable
.TBYTEOUT(), // 1-bit output: Byte group tristate
.TFB(), // 1-bit output: 3-state control
.TQ() // 1-bit output: 3-state control
);
endmodule
(7)同步模块
对复位进行同步处理(异步复位,同步释放);
//**************************************************************************
// *** 名称 : asyn_to_syn
// *** 作者 : 不想上体育课
// *** 博客 : https://blog.csdn.net/m0_47220307?type=blog
// *** 日期 : 2022.09
// *** 描述 : 同步处理模块
//**************************************************************************
`timescale 1ns / 1ps
module asyn_to_syn(
input clk,
input rst_n,
output syn_rst
);
reg rst_1;
reg rst_2;
assign syn_rst=rst_2;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
rst_1<=1'b1;
rst_2<=1'b1;
end
else
begin
rst_1<=1'b0;
rst_2<=rst_1;
end
end
endmodule
六、Testbench编写
由于使用的是差分时钟,使用需要产生一个差分时钟:
//**************************************************************************
// *** 名称 : tb_hdmixs_top
// *** 作者 : 不想上体育课
// *** 博客 : https://blog.csdn.net/m0_47220307?type=blog
// *** 日期 : 2022.09
// *** 描述 : 测试模块
//**************************************************************************
·timescale 1ns/1ps
module tb_hdmixs_top();
reg sys_clk_n ;
reg sys_clk_p ;
reg sys_rst_n ;
wire tmds_clk_n ;//TMDS时钟通道
wire tmds_clk_p ;
wire [2:0] tmds_data_n ;//TMDS数据通道
wire [2:0] tmds_data_p ;
initial
begin
sys_clk_p = 1'b0;
sys_clk_n = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #2.5 sys_clk_p = ~sys_clk_p;
always #2.5 sys_clk_n = ~sys_clk_n;
hdmixs_top hdmixs_top_inst(
.sys_clk_n (sys_clk_n ),
.sys_clk_p (sys_clk_p ),
.sys_rst_n (sys_rst_n ),
.tmds_clk_n (tmds_clk_n ),//TMDS时钟通道
.tmds_clk_p (tmds_clk_p ),
.tmds_data_n (tmds_data_n),//TMDS数据通道
.tmds_data_p (tmds_data_p)
);
endmodule
七、Vivado仿真结果与COE对比
读取的数据与COE文件一致。