本设计使用Xilinx原语和自己手写的代码实现了HDMI发送功能,纯verilog手写,有源码,也提供封装好的IP,你喜欢用例化的方式就用源码,你喜欢搭建BD工程就用IP,目前IP的适应器件为zynq,如果是用7系列FPGA的兄弟可以改一下芯片型号即可使用。
这个HDMI发送模块我已在项目中多次使用,稳定性和实用性可以保证,最大支持1080P@60Hz帧率。
本文详细描述了设计方案,工程代码编译通过后上板调试验证,可直接项目移植,适用于在校学生、研究生项目开发,也适用于在职工程师做项目开发,可应用于医疗、军工等行业的数字成像和图像传输领域;
提供完整的、跑通的工程源码和技术支持;
工程源码和技术支持的获取方式放在了文章末尾,请耐心看到最后;
关于HDMI的理论介绍这里不多说,请自行搜索,很简单,本文只讲如何代码层级实现的干货。
HDMI编码框图如下:
Encoder 模块负责对数据进行编码,Serializer 模块对编码后的数据进行并串转换,最后
通过 OBUFDS 转化成 TMDS 差分信号传输。
整个系统需要两个输入时钟,一个是视频的像素时钟 Pixel Clk,另外一个时钟 Pixel Clk x5 的频率是像
素时钟的五倍。由前面的简介部分我们知道,并串转换过程的实现的是 10:1 的转换率,理论上转换器需要一个 10 倍像素时钟频率的串行时钟。这里我们只用了一个 5 倍的时钟频率,这是因为 OSERDESE2 模块可以实现 DDR 的功能,即它在五倍时钟频率的基础上又实现了双倍数据速率。
TMDS 连接的时钟通道我们采用与数据通道相同的并转串逻辑来实现。通过对 10 位二进制序列10’b11111_00000 在 10 倍像素时钟频率下进行并串转换,就可以得到像素时钟频率下的 TMDS 参考时钟。
另外需要注意的是,图中左下脚 HDMI 的音频/附加数据输入在本次实验中并未用到,因此以虚线表示。
Encoder 模块完成TMDS 编码算法,dvi_encoder 模块按照 DVI 接口规范中 TMDS 编码算法对输入的 8 位像素数据以及 2 位行场同步信号进行编码。该模块是 Xilinx 应用笔记 XAPP460 中所提供的编码模块,其具体实现的编码算法如下图所示:TMDS 通过逻辑算法将 8 位字符数据通过最小转换编码为 10 位字符数据,前 8 位数据由原始信号经运
算后获得,第 9 位表示运算的方式,1 表示异或 0 表示异或非。经过 DC 平衡后(第 10 位),采用差分信号传输数据。第 10 位实际是一个反转标志位,1 表示进行了反转而 0 表示没有反转,从而达到 DC 平衡。
接收端在收到信号后,再进行相反的运算。TMDS 和 LVDS、TTL 相比有较好的电磁兼容性能。这种算
法可以减小传输信号过程的上冲和下冲,而 DC 平衡使信号对传输线的电磁干扰减少,可以用低成本的专用电缆实现长距离、高质量的数字信号传输。
算法中各个参数的含义如下图所示:
Encoder 模块顶层接口如下:
module dvi_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
);
代码部分并不复杂,就是将TMDS 编码算法的流程图翻译成了 verilog语言。
TMDS 编码之后的数据由 Serializer 模块进行并串转换,通过调用 OSERDESE2 原语来实现 10:1 的并串转换。原语是 Xilinx 器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于 IP 核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。
需要注意的是,一个 OSERDESE2 只能实现最多 8:1 的转换率,在这里我们通过位宽扩展实现了 10:1
的并串转换,如下图所示:
OSERDESE2 位宽扩展通过两个 OSERDESE2 模块来实现,其中一个作为 Master,
另一个作为 Slave,通过这种方式最多可实现 14:1 的并串转换。需要注意的是,在位宽扩展时,Slave 模块的数据输入端只能使用 D3 至 D8。
OSERDESE串并转换模块顶层接口如下:
module serializer_10_to_1(
input reset,
input paralell_clk,
input serial_clk_5x,
input [9:0] paralell_data,
output serial_data_out
);
将各个功能模块进行例化,即完成了本设计,为了适应不同的使用需求,提供源码的同时也将源码进行了IP封装,两者一并提供。
顶层源码如下:
module helai_hdmi_out(
input clk_hdmi ,
input clk_hdmix5 ,
input reset_n ,
input i_vga_hs ,
input i_vga_vs ,
input i_vga_de ,
input [23:0] i_vga_rgb ,
output o_hdmi_clk_p ,
output o_hdmi_clk_n ,
output [2: 0] o_hdmi_data_p,
output [2: 0] o_hdmi_data_n
);
wire reset;
//²¢ÐÐÊý¾Ý
wire [9:0] red_10bit;
wire [9:0] green_10bit;
wire [9:0] blue_10bit;
wire [9:0] clk_10bit;
//´®ÐÐÊý¾Ý
wire [2:0] tmds_data_serial;
wire tmds_clk_serial;
//*****************************************************
//** main code
//*****************************************************
assign clk_10bit = 10'b1111100000;
//Òì²½¸´Î»£¬Í¬²½ÊÍ·Å
asyn_rst_syn reset_syn(
.reset_n (reset_n),
.clk (clk_hdmi),
.syn_reset (reset) //¸ßÓÐЧ
);
//¶ÔÈý¸öÑÕɫͨµÀ½øÐбàÂë
dvi_encoder encoder_b (
.clkin (clk_hdmi),
.rstin (reset),
.din (i_vga_rgb[7:0]),
.c0 (i_vga_hs),
.c1 (i_vga_vs),
.de (i_vga_de),
.dout (blue_10bit)
) ;
dvi_encoder encoder_g (
.clkin (clk_hdmi),
.rstin (reset),
.din (i_vga_rgb[15:8]),
.c0 (i_vga_hs),
.c1 (i_vga_vs),
.de (i_vga_de),
.dout (green_10bit)
) ;
dvi_encoder encoder_r (
.clkin (clk_hdmi),
.rstin (reset),
.din (i_vga_rgb[23:16]),
.c0 (i_vga_hs),
.c1 (i_vga_vs),
.de (i_vga_de),
.dout (red_10bit)
) ;
//¶Ô±àÂëºóµÄÊý¾Ý½øÐв¢´®×ª»»
serializer_10_to_1 serializer_b(
.reset (reset), // ¸´Î»,¸ßÓÐЧ
.paralell_clk (clk_hdmi), // ÊäÈë²¢ÐÐÊý¾ÝʱÖÓ
.serial_clk_5x (clk_hdmix5), // ÊäÈë´®ÐÐÊý¾ÝʱÖÓ
.paralell_data (blue_10bit), // ÊäÈë²¢ÐÐÊý¾Ý
// .paralell_data (10'h146), // ÊäÈë²¢ÐÐÊý¾Ý
.serial_data_out (tmds_data_serial[0]) // Êä³ö´®ÐÐÊý¾Ý
);
serializer_10_to_1 serializer_g(
.reset (reset),
.paralell_clk (clk_hdmi),
.serial_clk_5x (clk_hdmix5),
.paralell_data (green_10bit),
// .paralell_data (10'h146),
.serial_data_out (tmds_data_serial[1])
);
serializer_10_to_1 serializer_r(
.reset (reset),
.paralell_clk (clk_hdmi),
.serial_clk_5x (clk_hdmix5),
.paralell_data (red_10bit),
// .paralell_data (10'h146),
.serial_data_out (tmds_data_serial[2])
);
serializer_10_to_1 serializer_clk(
.reset (reset),
.paralell_clk (clk_hdmi),
.serial_clk_5x (clk_hdmix5),
.paralell_data (clk_10bit),
.serial_data_out (tmds_clk_serial)
);
//ת»»²î·ÖÐźÅ
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/Oµçƽ±ê׼ΪTMDS
) TMDS0 (
.I (tmds_data_serial[0]),
.O (o_hdmi_data_p[0]),
.OB (o_hdmi_data_n[0])
);
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/Oµçƽ±ê׼ΪTMDS
) TMDS1 (
.I (tmds_data_serial[1]),
.O (o_hdmi_data_p[1]),
.OB (o_hdmi_data_n[1])
);
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/Oµçƽ±ê׼ΪTMDS
) TMDS2 (
.I (tmds_data_serial[2]),
.O (o_hdmi_data_p[2]),
.OB (o_hdmi_data_n[2])
);
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/Oµçƽ±ê׼ΪTMDS
) TMDS3 (
.I (tmds_clk_serial),
.O (o_hdmi_clk_p),
.OB (o_hdmi_clk_n)
);
endmodule