超级版主
- 积分
- 4754
- 金钱
- 4754
- 注册时间
- 2019-5-8
- 在线时间
- 1239 小时
|
本帖最后由 正点原子运营 于 2021-10-30 10:10 编辑
1)实验平台:正点原子新起点V2FPGA开发板
2) 章节摘自【正点原子】《新起点之FPGA开发指南 V2.1》
3)购买链接:https://detail.tmall.com/item.htm?id=609758951113
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_xinqidian(V2).html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122
第二十四章 HDMI彩条显示实验
HDMI接口在消费类电子行业,如电脑、液晶电视、投影仪等产品中得到了广泛的应用。一些专业的视频设备如摄像机、视频切换器等也都集成了HDMI接口。本章我们将学习如何驱动新起点开发板上的HDMI接口。
本章包括以下几个部分:
2424.1简介
24.2实验任务
24.3硬件设计
24.4程序设计
24.5下载验证
24.1简介
HDMI是新一代的多媒体接口标准,英文全称是High-Definition Multimedia Interface,即高清多媒体接口。它能够同时传输视频和音频,简化了设备的接口和连线;同时提供了更高的数据传输带宽,可以传输无压缩的数字音频及高分辨率视频信号。HDMI 1.0版本于2002年发布,最高数据传输速度为5Gbps;而2017年发布的HDMI 2.1标准的理论带宽可达48Gbps。
在HDMI接口出现之前,被广泛应用的是VGA接口。VGA的全称是Video Graphics Array,即视频图形阵列,是一个使用模拟信号进行视频传输的标准。VGA接口采用15针插针式结构,里面传输模拟信号颜色分量、同步等信号,是很多老显卡、笔记本和投影仪所使用的接口。由于VGA接口传输的是模拟信号,其信号容易受到干扰,因此VGA在高分辨率下字体容易虚,信号线长的话,图像有拖尾现象。VGA接口由下图所示:
图 24.1.1 VGA接口
VGA接口除信号容易受到干扰外,其体积也较大,因此VGA接口已逐渐退出舞台,一些显示器也不再带有VGA接口,在数字设备高度发展的今天,取而代之的是HDMI接口和DP(Display Port)接口等。
HDMI向下兼容DVI,但是DVI(数字视频接口)只能用来传输视频,而不能同时传输音频,这是两者最主要的差别。此外,DVI接口的尺寸明显大于HDMI接口,如下图所示:
图 24.1.2 DVI接口(左)和HDMI接口(右)实物图
图 24.1.2右侧是生活中最常见的A型HDMI接口,其引脚定义如下图所示:
图 24.1.3 HDMI接口引脚定义
DVI和HDMI接口协议在物理层使用TMDS标准传输音视频数据。TMDS(Transition TYPE_Cmized Differential Signaling,最小化传输差分信号)是美国Silicon Image公司开发的一项高速数据传输技术,在DVI和HDMI视频接口中使用差分信号传输高速串行数据。TMDS差分传输技术使用两个引脚(如图 24.1.3中的“数据2+”和“数据2-”)来传输一路信号,利用这两个引脚间的电压差的正负极性和大小来决定传输数据的数值(0或1)。
由于本次实验只是使用HDMI接口来显示图像,不需要传输音频,因此我们只需要实现DVI接口的驱动逻辑即可。不过在此之前我们还需要简单地了解一下TMDS视频传输协议。
图 24.1.4是TMDS发送端和接收端的连接示意图。DVI或HDMI视频传输所使用的TMDS连接通过四个串行通道实现。对于DVI来说,其中三个通道分别用于传输视频中每个像素点的红、绿、蓝三个颜色分量(RGB 4:4:4格式)。HDMI默认也是使用三个RGB通道,但是它同样可以选择传输像素点的亮度和色度信息(YCrCb 4:4:4或YCrCb 4:2:2格式)。第四个通道是时钟通道,用于传输像素时钟。独立的TMDS时钟通道为接收端提供接收的参考频率,保证数据在接收端能够正确恢复。
图 24.1.4 TMDS连接示意图
如果每个像素点的颜色深度为24位,即RGB每个颜色分量各占8位,那么每个通道上的颜色数据将通过一个8B/10B的编码器(Encoder)来转换成一个10位的像素字符。然后这个10位的字符通过并串转换器(Serializer)转换成串行数据,最后由TMDS数据通道发送出去。这个10:1的并转串过程所生成的串行数据速率是实际像素时钟速率的10倍。
在传输视频图像的过程中,数据通道上传输的是编码后的有效像素字符。而在每一帧图像的行与行之间,以及视频中不同帧之间的时间间隔(消隐期)内,数据通道上传输的则是控制字符。每个通道上有两位控制信号的输入接口,共对应四种不同的控制字符。这些控制字符提供了视频的行同步(HSYNC)以及帧同步(VSYNC)信息,也可以用来指定所传输数据的边界(用于同步)。
对于DVI传输,整个视频的消隐期都用来传输控制字符。而HDMI传输的消隐期除了控制字符之外,还可以用于传输音频或者其他附加数据,比如字幕信息等。这就是DVI和HDMI协议之间最主要的差别。从图 24.1.4中也可以看出这一差别,即“Auxiliary Data”接口标有“HDMI Only”,即它是HDMI所独有的接口。
从前面的介绍中我们可以看出,TMDS连接从逻辑功能上可以划分成两个阶段:编码和并串转换。在编码阶段,编码器将视频源中的像素数据、HDMI的音频/附加数据,以及行同步和场同步信号分别编码成10位的字符流。然后在并串转换阶段将上述的字符流转换成串行数据流,并将其从三个差分输出通道发送出去。
DVI编码器在视频有效数据段输出像素数据,在消隐期输出控制数据,如图 24.1.5所示。其中VDE(Video Data Enable)为高电平时表示视频数据有效,为低电平代表当前处于视频消隐期。
图 24.1.5 DVI编码输出示意图
图 24.1.6给出了三个通道的DVI编码器示意图。对于像素数据的RGB三个颜色通道,编码器的逻辑是完全相同的。VDE用于各个通道选择输出视频像素数据还是控制数据。HSYNC和VSYNC信号在蓝色通道进行编码得到10位字符,然后在视频消隐期传输。绿色和红色通道的控制信号C0和C1同样需要进行编码,并在消隐期输出。但是DVI规范中这两个通道的控制信号是预留的(未用到),因此将其置为2’b00。
图 24.1.6 DVI编码器示意图
每个通道输入的视频像素数据都要使用DVI规范中的TMDS编码算法进行编码。每个8-bit的数据都将被转换成460个特定10-bit字符中的一个。这个编码机制大致上实现了传输过程中的直流平衡,即一段时间内传输的高电平(数字“1”)的个数大致等于低电平(数字“0”)的个数。同时,每个编码后的10-bit字符中状态跳转(“由1到0”或者“由0到1”)的次数将被限制在五次以内。
除了视频数据之外,每个通道2-bit控制信号的状态也要进行编码,编码后分别对应四个不同的10-bit控制字符,分别是10'b1101010100,10'b0010101011,10'b0101010100,和10'b1010101011。可以看出,每个控制字符都有七次以上的状态跳转。视频字符和控制字符状态跳转次数的不同将会被用于发送和接收设备的同步。
HDMI协议与DVI协议在很多方面都是相同的,包括物理连接(TMDS)、有效视频编码算法以及控制字符的定义等。但是相比于DVI,HDMI在视频的消隐期会传输更多的数据,包括音频数据和附加数据。4-bit音频和附加数据将通过TERC4编码机制转换成10-bit TERC4字符,然后在绿色和红色通道上传输。
HDMI在输入附加数据的同时,还需要输入ADE(Aux/Audio Data Enable)信号,其作用和VDE是类似的:当ADE为高电平时,表明输入端的附加数据或者音频数据有效。如果大家想了解更多有关HDMI的细节,可以参考开发板资料(A盘)/8_FPGA参考资料/HDMI/《HDMI Specification 13a》。为了简单起见,我们在这里把HDMI接口当作DVI接口进行驱动。
在编码之后,3个通道的10-bit字符将进行并串转换,这一过程是通过调用Quartus软件中的ALTDDIO_OUT IP核来实现。
24.2实验任务
本章的实验任务是驱动新起点开发板上的HDMI接口,在显示器上显示彩条图案。HDMI接口输出的分辨率为1280*720,刷新速率为60hz。
24.3硬件设计
新起点FPGA开发板板载的HDMI接口原理图如下图所示:
图 24.3.1 HDMI接口原理图1
图 24.3.1是新起点FPGA开发板HDMI接口原理图的一部分,其中HDMI的三个数据通道HDMI_D[2:0]和一个时钟通道HDMI_CLK直接与TMDS差分引脚相连,此种方案的HDMI接口,其输出的分辨率支持720P,而使用性能更加强劲的FPGA芯片,能支持输出更高分辨率的图像。图中AZ1045是TVS二极管,用于保护线路,防止HDMI差分线上的电压差过高。
HDMI_CEC指的是用户电气控制(Consumer Electronics Control),它用于HDMI连接线上的设备之间进行信息交换。当一个设备的状态发生变化时,CEC可以使用远程控制或自动改变设置来命令连接的关联设备的状态发生相应的变化。例如,如果用户放置一张碟片到蓝光播放器并开始播放,那么高清电视机将会自动打开电源,设置正确的视频输入格式和打开环绕声设备等等,这种关联通信提供了一个更好的客户体验。
HDMI_HPD指的是热拔插检测(Hot Plug Detect),当视频设备与接收设备通过HDMI连接时,接收设备将HPD置为高电平,通知发送设备。当发送设备检测到HPD为低电平时,表明断开连接。
图 24.3.2 HDMI接口原理图2
在图 24.3.2中,HDMI_SCL_LS和HDMI_SDA_LS是HDMI接口的显示数据通道(DDC,Display Data Channel),用于HDMI 发送端和接收端之间交换一些配置信息,通过I2C协议通信。发送端通过 DDC通道,读取接收端保存在 EEPROM中的EDID数据,获取接收端的信息,确认接收端终端显示的设置和功能,决定跟接收端之间以什么格式传输音/视频数据。由于HDMI座子上的DDC通道是5V IO,而FPGA引脚的IO是3.3V,因此通过MOS管(AO3400)进行电平转换。
本次实验只使用了HDMI接口的TMDS数据和TMDS时钟等信号,各端口的管脚分配如下表所示:
表 24.3.1 HDMI彩条显示实验管脚分配
相关的管脚约束如下所示:
- set_location_assignment PIN_M2 -to sys_clk
- set_location_assignment PIN_M1 -to sys_rst_n
- set_location_assignment PIN_B12 -to tmds_clk_p
- set_location_assignment PIN_A12 -to tmds_clk_n
- set_location_assignment PIN_B9 -to tmds_data_p[2]
- set_location_assignment PIN_A9 -to tmds_data_n[2]
- set_location_assignment PIN_B10 -to tmds_data_p[1]
- set_location_assignment PIN_A10 -to tmds_data_n[1]
- set_location_assignment PIN_B11 -to tmds_data_p[0]
- set_location_assignment PIN_A11 -to tmds_data_n[0]
复制代码
24.4程序设计
由于本次实验只需要通过HDMI接口显示图像,因此将其当成DVI接口进行驱动。另外我们只需要实现图像的发送功能。由此得出本次实验的系统框图如下所示:
图 24.4.1 系统框图
本次实验在LCD彩条显示实验的基础上添加一个锁相环和RGB2DVI模块,将RGB888格式的视频图像转换成TMDS数据输出。本章的重点是介绍RGB2DVI模块,其余模块的介绍请参考LCD彩条显示实验。
顶层模块hdmi_colorbar_top的原理图如下所示:
图 24.4.2 顶层模块原理图
由上图可知,FPGA部分包括五个模块,顶层模块(hdmi_colorbar_top)、PLL锁相环模块(pll_clk)、 视频显示模块(video_display)、视频驱动模块(video_driver)和RGB转DVI模块(dvi_transmitter_top)。其中在顶层模块中完成对其余模块的例化。
PLL锁相环模块(pll_clk):该模块通过调用PLL IP核实现,总共输出两个时钟,频率分别为75Mhz和375Mhz,两者的频率相差5倍。其中75Mhz作为其余各模块的操作时钟;而375Mhz作为并行转串行的操作时钟。
视频显示模块(video_display):该模块用于控制HDMI显示器显示的像素值,即彩条。
视频驱动模块(video_driver):该模块和LCD彩条显示实验中的LCD驱动模块类似,不同的是,本次实验输出的图像分辨率为720P,且数据格式为RGB888共24位数据。
RGB转DVI模块(dvi_transmitter_top):该模块实现RGB接口到DVI接口的转换,是本次实验重点介绍的模块。
系统的顶层模块为hdmi_colorbar_top,其代码如下所示:
- 1 module hdmi_colorbar_top(
- 2 input sys_clk,
- 3 input sys_rst_n,
- 4
- 5 output tmds_clk_p, // TMDS 时钟通道
- 6 output tmds_clk_n,
- 7 output [2:0] tmds_data_p, // TMDS 数据通道
- 8 output [2:0] tmds_data_n
- 9 );
- 10
- 11 //wire define
- 12 wire pixel_clk;
- 13 wire pixel_clk_5x;
- 14 wire clk_locked;
- 15
- 16 wire [10:0] pixel_xpos_w;
- 17 wire [10:0] pixel_ypos_w;
- 18 wire [23:0] pixel_data_w;
- 19
- 20 wire video_hs;
- 21 wire video_vs;
- 22 wire video_de;
- 23 wire [23:0] video_rgb;
- 24
- 25 //*****************************************************
- 26 //** main code
- 27 //*****************************************************
- 28
- 29 //例化PLL IP核
- 30 pll_clk u_pll_clk(
- 31 .areset (~sys_rst_n),
- 32 .inclk0 (sys_clk),
- 33 .c0 (pixel_clk), //像素时钟
- 34 .c1 (pixel_clk_5x), //5倍像素时钟
- 35 .locked (clk_locked)
- 36 );
- 37
- 38 //例化视频显示驱动模块
- 39 video_driver u_video_driver(
- 40 .pixel_clk (pixel_clk),
- 41 .sys_rst_n (sys_rst_n),
- 42
- 43 .video_hs (video_hs),
- 44 .video_vs (video_vs),
- 45 .video_de (video_de),
- 46 .video_rgb (video_rgb),
- 47
- 48 .pixel_xpos (pixel_xpos_w),
- 49 .pixel_ypos (pixel_ypos_w),
- 50 .pixel_data (pixel_data_w)
- 51 );
- 52
- 53 //例化视频显示模块
- 54 video_display u_video_display(
- 55 .pixel_clk (pixel_clk),
- 56 .sys_rst_n (sys_rst_n),
- 57
- 58 .pixel_xpos (pixel_xpos_w),
- 59 .pixel_ypos (pixel_ypos_w),
- 60 .pixel_data (pixel_data_w)
- 61 );
- 62
- 63 //例化HDMI驱动模块
- 64 dvi_transmitter_top u_rgb2dvi_0(
- 65 .pclk (pixel_clk),
- 66 .pclk_x5 (pixel_clk_5x),
- 67 .reset_n (sys_rst_n & clk_locked),
- 68
- 69 .video_din (video_rgb),
- 70 .video_hsync (video_hs),
- 71 .video_vsync (video_vs),
- 72 .video_de (video_de),
- 73
- 74 .tmds_clk_p (tmds_clk_p),
- 75 .tmds_clk_n (tmds_clk_n),
- 76 .tmds_data_p (tmds_data_p),
- 77 .tmds_data_n (tmds_data_n)
- 78 );
- 79
- 80 endmodule
复制代码
在代码的30至36行,我们通过调用时钟IP核来产生两个时钟,其中pixel_clk为像素时钟,而pixel_clk_5x为并串转换模块所需要的串行数据时钟,其频率为pixel_clk的5倍。
在顶层模块中,video_display模块(第54行)负责产生RGB888格式的彩条图案,然后在video_driver模块(第39行)的驱动下按照工业标准的VGA显示时序输出视频信号、行场同步信号以及视频有效信号。这些信号作为RGB2DVI模块(第64行)的输入,最终转换成DVI/HDMI接口标准的TMDS串行数据输出到HDMI接口。
video_display模块和video_driver模块与LCD彩条显示实现中的lcd_driver和lcd_display模块几乎完全相同,只不过将数据位宽改为了24位,即RGB888格式,如果大家对这两个模块不熟悉的话,请参考《LCD彩条显示实验》
接下来,我们在整个系统的顶层模块中调用RGB2DVI模块,通过HDMI接口输出彩条图案。
RGB2DVI模块的原理图如下所示:
图 24.4.3 RGB2DVI模块原理图
由上图可知,RGB转DVI模块(dvi_transmitter_top)例化了异步复位模块、DVI编码模块(dvi_encoder)和10位并行转串行模块(serializer_10_to_1),其中DVI编码模块和10位并行转串行模块被例化了多次。
RGB2DVI模块的设计框图如下所示:
图 24.4.4 RGB2DVI模块框图
图 24.4.4中,dvi_encoder模块负责对数据进行编码,serializer_10_to_1模块对编码后的数据进行并串转换,最后转化成TMDS差分信号传输。我们用红/绿/蓝三种颜色分别标识出了输入的视频数据video_din中三个不同的颜色通道。从图中也可以看出,每个颜色通道的处理过程都是一样的,都是先经过dvi_encoder进行编码,然后经过serializer_10_to_1模块进行并串转换,最后转换成TMDS差分信号。
整个系统需要两个输入时钟,一个是视频的像素时钟Pixel Clk,另外一个时钟Pixel Clk x5的频率是像素时钟的五倍。由前面的简介部分我们知道,并串转换过程实现的是10:1的转换率,理论上转换器需要一个10倍像素时钟频率的串行时钟。这里我们只用了一个5倍的时钟频率,这是因为ALTDDIO_OUT IP核可以实现DDR的功能,即它在五倍时钟频率的基础上又实现了双倍数据速率。
TMDS连接的时钟通道我们采用与数据通道相同的并转串逻辑来实现。通过对10位二进制序列10’b11111_00000在10倍像素时钟频率下进行并串转换,就可以得到像素时钟频率下的TMDS参考时钟。
另外需要注意的是,图中左下脚HDMI的音频/附加数据输入在本次实验中并未用到,因此以虚线表示。
dvi_transmitter_top模块代码如下所示:
- 1 module dvi_transmitter_top(
- 2 input pclk, // pixel clock
- 3 input pclk_x5, // pixel clock x5
- 4 input reset_n, // reset
- 5
- 6 input [23:0] video_din, // RGB888 video in
- 7 input video_hsync, // hsync data
- 8 input video_vsync, // vsync data
- 9 input video_de, // data enable
- 10
- 11 output tmds_clk_p, // TMDS 时钟通道
- 12 output tmds_clk_n,
- 13 output [2:0] tmds_data_p, // TMDS 数据通道
- 14 output [2:0] tmds_data_n
- 15 );
- 16
- 17 //wire define
- 18 wire reset;
- 19
- 20 //并行数据
- 21 wire [9:0] red_10bit;
- 22 wire [9:0] green_10bit;
- 23 wire [9:0] blue_10bit;
- 24 wire [9:0] clk_10bit;
- 25
- 26 //串行数据
- 27 wire [2:0] tmds_data_serial;
- 28 wire tmds_clk_serial;
- 29
- 30 //*****************************************************
- 31 //** main code
- 32 //*****************************************************
- 33
- 34 assign clk_10bit = 10'b1111100000;
- 35
- 36 //异步复位,同步释放
- 37 asyn_rst_syn reset_syn(
- 38 .reset_n (reset_n),
- 39 .clk (pclk),
- 40
- 41 .syn_reset (reset) //高有效
- 42 );
- 43
- 44 //对三个颜色通道进行编码
- 45 dvi_encoder encoder_b (
- 46 .clkin (pclk),
- 47 .rstin (reset),
- 48
- 49 .din (video_din[7:0]),
- 50 .c0 (video_hsync),
- 51 .c1 (video_vsync),
- 52 .de (video_de),
- 53 .dout (blue_10bit)
- 54 ) ;
- 55
- 56 dvi_encoder encoder_g (
- 57 .clkin (pclk),
- 58 .rstin (reset),
- 59
- 60 .din (video_din[15:8]),
- 61 .c0 (1'b0),
- 62 .c1 (1'b0),
- 63 .de (video_de),
- 64 .dout (green_10bit)
- 65 ) ;
- 66
- 67 dvi_encoder encoder_r (
- 68 .clkin (pclk),
- 69 .rstin (reset),
- 70
- 71 .din (video_din[23:16]),
- 72 .c0 (1'b0),
- 73 .c1 (1'b0),
- 74 .de (video_de),
- 75 .dout (red_10bit)
- 76 ) ;
- 77
- 78 //对编码后的数据进行并串转换
- 79 serializer_10_to_1 serializer_b(
- 80 .serial_clk_5x (pclk_x5), // 输入串行数据时钟
- 81 .paralell_data (blue_10bit), // 输入并行数据
- 82
- 83 .serial_data_p (tmds_data_p[0]), // 输出串行数据P
- 84 .serial_data_n (tmds_data_n[0]) // 输出串行数据N
- 85 );
- 86
- 87 serializer_10_to_1 serializer_g(
- 88 .serial_clk_5x (pclk_x5), // 输入串行数据时钟
- 89 .paralell_data (green_10bit), // 输入并行数据
- 90
- 91 .serial_data_p (tmds_data_p[1]), // 输出串行数据P
- 92 .serial_data_n (tmds_data_n[1]) // 输出串行数据N
- 93 );
- 94
- 95 serializer_10_to_1 serializer_r(
- 96 .serial_clk_5x (pclk_x5), // 输入串行数据时钟
- 97 .paralell_data (red_10bit), // 输入并行数据
- 98
- 99 .serial_data_p (tmds_data_p[2]), // 输出串行数据P
- 100 .serial_data_n (tmds_data_n[2]) // 输出串行数据N
- 101 );
- 102
- 103 serializer_10_to_1 serializer_clk(
- 104 .serial_clk_5x (pclk_x5),
- 105 .paralell_data (clk_10bit),
- 106
- 107 .serial_data_p (tmds_clk_p), // 输出串行时钟P
- 108 .serial_data_n (tmds_clk_n) // 输出串行时钟N
- 109 );
- 110
- 111 endmodule
复制代码
在dvi_transmitter_top模块中,不仅例化了编码模块和并转串模块,还例化了asyn_rst_syn模块。编码模块要求复位信号高电平有效,因此,我们在asyn_rst_syn模块中将低电平有效的异步复位信号转换成高有效,同时对其进行异步复位,同步释放处理。
dvi_encoder模块代码如下所示:
- 1 `timescale 1 ps / 1ps
- 2
- 3 module dvi_encoder (
- 4 input clkin, // pixel clock input
- 5 input rstin, // async. reset input (active high)
- 6 input [7:0] din, // data inputs: expect registered
- 7 input c0, // c0 input
- 8 input c1, // c1 input
- 9 input de, // de input
- 10 output reg [9:0] dout // data outputs
- 11 );
- 12
- 13 ////////////////////////////////////////////////////////////
- 14 // Counting number of 1s and 0s for each incoming pixel
- 15 // component. Pipe line the result.
- 16 // Register Data Input so it matches the pipe lined adder
- 17 // output
- 18 ////////////////////////////////////////////////////////////
- 19 reg [3:0] n1d; //number of 1s in din
- 20 reg [7:0] din_q;
- 21
- 22 //计算像素数据中“1”的个数
- 23 always [url=home.php?mod=space&uid=95564]@[/url] (posedge clkin) begin
- 24 n1d <=#1 din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
- 25
- 26 din_q <=#1 din;
- 27 end
- 28
- 29 ///////////////////////////////////////////////////////
- 30 // Stage 1: 8 bit -> 9 bit
- 31 // Refer to DVI 1.0 Specification, page 29, Figure 3-5
- 32 ///////////////////////////////////////////////////////
- 33 wire decision1;
- 34
- 35 assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q[0] == 1'b0));
- 36
- 37 wire [8:0] q_m;
- 38 assign q_m[0] = din_q[0];
- 39 assign q_m[1] = (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
- 40 assign q_m[2] = (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
- 41 assign q_m[3] = (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
- 42 assign q_m[4] = (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
- 43 assign q_m[5] = (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
- 44 assign q_m[6] = (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
- 45 assign q_m[7] = (decision1) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);
- 46 assign q_m[8] = (decision1) ? 1'b0 : 1'b1;
- 47
- 48 /////////////////////////////////////////////////////////
- 49 // Stage 2: 9 bit -> 10 bit
- 50 // Refer to DVI 1.0 Specification, page 29, Figure 3-5
- 51 /////////////////////////////////////////////////////////
- 52 reg [3:0] n1q_m, n0q_m; // number of 1s and 0s for q_m
- 53 always @ (posedge clkin) begin
- 54 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];
- 55 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]);
- 56 end
- 57
- 58 parameter CTRLTOKEN0 = 10'b1101010100;
- 59 parameter CTRLTOKEN1 = 10'b0010101011;
- 60 parameter CTRLTOKEN2 = 10'b0101010100;
- 61 parameter CTRLTOKEN3 = 10'b1010101011;
- 62
- 63 reg [4:0] cnt; //disparity counter, MSB is the sign bit
- 64 wire decision2, decision3;
- 65
- 66 assign decision2 = (cnt == 5'h0) | (n1q_m == n0q_m);
- 67 /////////////////////////////////////////////////////////////////////////
- 68 // [(cnt > 0) and (N1q_m > N0q_m)] or [(cnt < 0) and (N0q_m > N1q_m)]
- 69 /////////////////////////////////////////////////////////////////////////
- 70 assign decision3 = (~cnt[4] & (n1q_m > n0q_m)) | (cnt[4] & (n0q_m > n1q_m));
- 71
- 72 ////////////////////////////////////
- 73 // pipe line alignment
- 74 ////////////////////////////////////
- 75 reg de_q, de_reg;
- 76 reg c0_q, c1_q;
- 77 reg c0_reg, c1_reg;
- 78 reg [8:0] q_m_reg;
- 79
- 80 always @ (posedge clkin) begin
- 81 de_q <=#1 de;
- 82 de_reg <=#1 de_q;
- 83
- 84 c0_q <=#1 c0;
- 85 c0_reg <=#1 c0_q;
- 86 c1_q <=#1 c1;
- 87 c1_reg <=#1 c1_q;
- 88
- 89 q_m_reg <=#1 q_m;
- 90 end
- 91
- 92 ///////////////////////////////
- 93 // 10-bit out
- 94 // disparity counter
- 95 ///////////////////////////////
- 96 always @ (posedge clkin or posedge rstin) begin
- 97 if(rstin) begin
- 98 dout <= 10'h0;
- 99 cnt <= 5'h0;
- 100 end else begin
- 101 if (de_reg) begin
- 102 if(decision2) begin
- 103 dout[9] <=#1 ~q_m_reg[8];
- 104 dout[8] <=#1 q_m_reg[8];
- 105 dout[7:0] <=#1 (q_m_reg[8]) ? q_m_reg[7:0] : ~q_m_reg[7:0];
- 106
- 107 cnt <=#1 (~q_m_reg[8]) ? (cnt + n0q_m - n1q_m) : (cnt + n1q_m - n0q_m);
- 108 end else begin
- 109 if(decision3) begin
- 110 dout[9] <=#1 1'b1;
- 111 dout[8] <=#1 q_m_reg[8];
- 112 dout[7:0] <=#1 ~q_m_reg[7:0];
- 113
- 114 cnt <=#1 cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);
- 115 end else begin
- 116 dout[9] <=#1 1'b0;
- 117 dout[8] <=#1 q_m_reg[8];
- 118 dout[7:0] <=#1 q_m_reg[7:0];
- 119
- 120 cnt <=#1 cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);
- 121 end
- 122 end
- 123 end else begin
- 124 case ({c1_reg, c0_reg})
- 125 2'b00: dout <=#1 CTRLTOKEN0;
- 126 2'b01: dout <=#1 CTRLTOKEN1;
- 127 2'b10: dout <=#1 CTRLTOKEN2;
- 128 default: dout <=#1 CTRLTOKEN3;
- 129 endcase
- 130
- 131 cnt <=#1 5'h0;
- 132 end
- 133 end
- 134 end
- 135
- 136 endmodule
复制代码
dvi_encoder模块按照DVI接口规范中TMDS编码算法对输入的8位像素数据以及2位行场同步信号进行编码。该模块是Xilinx应用笔记XAPP460中所提供的编码模块(该编码算法是通用的,因此Altera的开发板也可以直接使用该模块),其具体实现的编码算法如下图所示:
图 24.4.5 TMDS编码算法
TMDS通过逻辑算法将8位字符数据通过最小转换编码为10位字符数据,前8位数据由原始信号经运算后获得,第9位表示运算的方式,1表示异或0表示异或非。经过DC平衡后(第10位),采用差分信号传输数据。第10位实际是一个反转标志位,1表示进行了反转而0表示没有反转,从而达到 DC 平衡。
接收端在收到信号后,再进行相反的运算。TMDS和LVDS、TTL相比有较好的电磁兼容性能。这种算法可以减小传输信号过程的上冲和下冲,而DC平衡使信号对传输线的电磁干扰减少,可以用低成本的专用电缆实现长距离、高质量的数字信号传输。
图 24.4.5所描述的算法是DVI接口规范所定义的,我们不作深入研究,大家有兴趣的话也可以对照dvi_encoder模块中的代码来分析整个算法流程是如何使用Verilog来实现的。算法中各个参数的含义如下图所示:
图 24.4.6 TMDS编码算法的参数
TMDS编码之后的数据由serializer_10_to_1模块进行并串转换,serializer_10_to_1模块实现10:1的并串转换,并实现DDR数据输出,其中DDR数据的输出是通过调用ALTDDIO_OUT IP核来实现,该IP核位于Quartus软件IP核IO目录下,如下图所示:
图 24.4.7 DDIO IP核目录
Quartus软件通过调用ALTDDIO IP核来实现DDR(Double Data Rate)接口的功能,共分为三种,分别为ALTDDIO_BIDIR(双向双速率 IO)、ALTDDIO_IN(输入型双速率IO)和ALTDDIO_OUT(输出型双速率IO)。
ALTDDIO_BIDIR IP核用于在参考时钟的双沿(上升沿和下降沿)发送和接收数据;
ALTDDIO_IN IP核用于在参考时钟的双沿(上升沿和下降沿)接收数据;
ALTDDIO_OUT IP核用于在参考时钟的双沿(上升沿和下降沿)发送数据;
本章的HDMI彩条显示实验,只用到了差分引脚的输出,因此只需要调用ALTDDIO_OUT IP核,接下来我们详细介绍下该IP核。
ALTDDIO_OUT IP核用于实现DDR输出接口的功能,实现了将两路单沿信号转换为双沿信号,即在参考时钟的上升沿和下降沿发送数据。ALTDDIO_OUT IP核的信号框图如下图所示:
图 24.4.8 ALTDDIO_OUT IP核的信号框图
上述信号的官方描述如图 24.4.9和图 24.4.10所示:
图 24.4.9 ALTDDIO_OUT IP核输入端口信号描述
图 24.4.10 ALTDDIO_OUT IP核输出端口信号描述
图 24.4.9和图 24.4.10分别是ALTDDIO_OUT IP核输入和输出端口信号的描述,在实际使用时,对该IP核进行不同的配置,部分端口信号会隐藏,比如不勾选使用“outclocken”信号时,那么该IP核的这个端口信号就不可见。
这里着重介绍以下几个关键的端口信号。
datain_h:在outclock时钟上升沿输入的数据,输入数据的位宽为WIDTH;
datain_l:在outclock时钟下降沿输入的数据,输入数据的位宽为WIDTH;
outclock:用于寄存数据输出的时钟信号,在outclock时钟信号的每个电平输出DDR数据到dataout端口;
dataout:DDR输出数据端口,位宽为WIDTH。dataout端口应该直接提供顶层设计的输出引脚。
ALTDDIO_OUT的电路结构如下图所示:
图 24.4.11 ALTDDIO_OUT的电路结构
由上图可知,寄存器和二选一多路器等都是由outclock控制,datain_l和datain_h两路信号经寄存器后,接到了二选一多路器上。outclock时钟控制和选择将datain_h和datain_l输出给dataout,而OE信号用于控制dataout是否输出。
ALTDDIO_OUT输出时序图如下图所示:
图 24.4.12 ALTDDIO_OUT输出时序图
由上图可知,OE信号有效之后,在outclock的上升沿,dataout输出datain_h的值;在outclock的下升沿,dataout输出datain_l的值。
以上是对ALTDDIO OUT IP核的介绍,关于该IP核更详细的描述,可以参考ALTERA的官方手册ug_altddio。
serializer_10_to_1模块代码如下所示:
- 1 module serializer_10_to_1(
- 2 input serial_clk_5x, // 输入串行数据时钟
- 3 input [9:0] paralell_data, // 输入并行数据
- 4
- 5 output serial_data_p, // 输出串行差分数据P
- 6 output serial_data_n // 输出串行差分数据N
- 7 );
- 8
- 9 //reg define
- 10 reg [2:0] bit_cnt = 0;
- 11 reg [4:0] datain_rise_shift = 0;
- 12 reg [4:0] datain_fall_shift = 0;
- 13
- 14 //wire define
- 15 wire [4:0] datain_rise;
- 16 wire [4:0] datain_fall;
- 17
- 18 //*****************************************************
- 19 //** main code
- 20 //*****************************************************
- 21
- 22 //上升沿发送Bit[8]/Bit[6]/Bit[4]/Bit[2]/Bit[0]
- 23 assign datain_rise = {paralell_data[8],paralell_data[6],paralell_data[4],
- 24 paralell_data[2],paralell_data[0]};
- 25
- 26 //下降沿发送Bit[9]/Bit[7]/Bit[5]/Bit[3]/Bit[1]
- 27 assign datain_fall = {paralell_data[9],paralell_data[7],paralell_data[5],
- 28 paralell_data[3],paralell_data[1]};
- 29
- 30 //位计数器赋值
- 31 always @(posedge serial_clk_5x) begin
- 32 if(bit_cnt == 3'd4)
- 33 bit_cnt <= 1'b0;
- 34 else
- 35 bit_cnt <= bit_cnt + 1'b1;
- 36 end
- 37
- 38 //移位赋值,发送并行数据的每一位
- 39 always @(posedge serial_clk_5x) begin
- 40 if(bit_cnt == 3'd4) begin
- 41 datain_rise_shift <= datain_rise;
- 42 datain_fall_shift <= datain_fall;
- 43 end
- 44 else begin
- 45 datain_rise_shift <= datain_rise_shift[4:1];
- 46 datain_fall_shift <= datain_fall_shift[4:1];
- 47 end
- 48 end
- 49
- 50 //例化DDIO_OUT IP核
- 51 ddio_out u_ddio_out_p(
- 52 .datain_h (datain_rise_shift[0]),
- 53 .datain_l (datain_fall_shift[0]),
- 54 .outclock (serial_clk_5x),
- 55 .dataout (serial_data_p)
- 56 );
- 57
- 58 //例化DDIO_OUT IP核
- 59 ddio_out u_ddio_out_n(
- 60 .datain_h (~datain_rise_shift[0]),
- 61 .datain_l (~datain_fall_shift[0]),
- 62 .outclock (serial_clk_5x),
- 63 .dataout (serial_data_n)
- 64 );
- 65
- 66 endmodule
复制代码
serializer_10_to_1模块实现10:1的并串转换和DDR数据输出。在程序的第23行至第28行代码,分别将paralell_data的Bit[9]/Bit[7]/Bit[5]/Bit[3]/Bit[1]赋值给datain_rise,和paralell_data的Bit[9]/Bit[7]/Bit[5]/Bit[3] /Bit[1]赋值给datain_fall,表示在serial_clk_5x时钟的上升沿和下降沿要发送的数据。
在程序的第31行至第48行,通过位计数器(bit_cnt)来控制数据的移位发送,位计数器的计数范围是0~4,当计数器计到4时,重新寄存datain_rise和datain_fall的值,和移位发送数据。
在程序的第50行至第64行,通过例化ALTDDIO OUT IP核实现数据的DDR输出。需要说明的是,HDMI通过一对差分线实现一路数据的输出,因此ALTDDIO OUT IP核需要例化两次,分别对应差分信号线的P和N,而P和N的数据刚好相反,因此在程序的第60行和第61行,分别对datain_h和datain_l的输入数据取反即可。
接下来我们来讲解下如何创建ALTDDIO OUT IP核。
依次点击Quartus软件的菜单栏“Tools”→“MegaWizard Plug-In Manager”,在弹出的界面中选择“Create a new custom megafunction ”,随后在IP核搜索框中输入“ddio”,选择“ALTDDIO OUT”,并指定IP核的路径和名称,如下图所示:
图 24.4.13 指定ALTDDIO OUT IP核路径和名称
选择完成后,点击“Next”,进入下图所示界面。
图 24.4.14 ALTDDIO_OUT IP核配置界面1
Width用于设置数据的位宽,HDMI输出图像需要使用四对差分线(3对数据线+1对时钟线),此处可以将Width设置为4,当然也可以设置为1,然后对serializer_10_to_1模块例化四次,本次实验采用第二种方案,即Width位宽设置为1;
Asynchronus clear and asynchronous set ports用于设置异步复位信号和异步置位信号,不需要的话可以选择“Not used”,此处选择“Not used”;
“Use ‘outclocken’ port”设置是否使用时钟使能信号,此处不勾选;
“Invert ‘dataout’ output”设置对dataout信号取反,此处不勾选;
设置完成后,点击“Next”,进入下图所示界面。
图 24.4.15 ALTDDIO_OUT IP核配置界面2
该页面用于设置输出使能端口信号和同步复位/置位,这里无需设置,直接点击“Next”,进入下图所示界面。
图 24.4.16 ALTDDIO_OUT IP核配置界面3
由上图可知,如果需要对该IP核仿真的话,需要添加仿真库“altera_mf”,这里直接点击“Next”,进入下图所示界面。
图 24.4.17 ALTDDIO_OUT IP核配置界面4
该界面可以选择生成的文件,此处保持默认即可,直接点击“Finish”完成IP核的创建。
asyn_rst_syn模块的代码如下所示:
- 1 module asyn_rst_syn(
- 2 input clk, //目的时钟域
- 3 input reset_n, //异步复位,低有效
- 4
- 5 output syn_reset //高有效
- 6 );
- 7
- 8 //reg define
- 9 reg reset_1;
- 10 reg reset_2;
- 11
- 12 //*****************************************************
- 13 //** main code
- 14 //*****************************************************
- 15 assign syn_reset = reset_2;
- 16
- 17 //对异步复位信号进行同步释放,并转换成高有效
- 18 always @ (posedge clk or negedge reset_n) begin
- 19 if(!reset_n) begin
- 20 reset_1 <= 1'b1;
- 21 reset_2 <= 1'b1;
- 22 end
- 23 else begin
- 24 reset_1 <= 1'b0;
- 25 reset_2 <= reset_1;
- 26 end
- 27 end
- 28
- 29 endmodule
复制代码
可以看出,该模块的代码非常简单,相当于在需要同步的时钟域下对输入的异步复位信号连接寄存了两次,这是一种非常常用的对异步信号进行同步的方法。需要注意的是,在程序第18行的always块中,还实现了将低电平有效的复位信号转换成高电平有效的功能。
最后我们通过Modelsim软件对代码进行仿真,TB代码如下:
- 1 `timescale 1ns/1ns //定义仿真时间单位1ns和仿真时间精度为1ns
- 2
- 3 module tb_hdmi_colorbar_top;
- 4
- 5 //parameter define
- 6 parameter T = 20; //时钟周期为20ns
- 7
- 8 //reg define
- 9 reg sys_clk; //时钟信号
- 10 reg sys_rst_n; //复位信号
- 11
- 12 //wire define
- 13 wire tmds_clk_p;
- 14 wire tmds_clk_n;
- 15 wire [2:0] tmds_data_p;
- 16 wire [2:0] tmds_data_n;
- 17
- 18 //*****************************************************
- 19 //** main code
- 20 //*****************************************************
- 21
- 22 //给输入信号初始值
- 23 initial begin
- 24 sys_clk = 1'b0;
- 25 sys_rst_n = 1'b0; //复位
- 26 #(T+1) sys_rst_n = 1'b1; //在第21ns的时候复位信号信号拉高
- 27 end
- 28
- 29 //50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
- 30 always #(T/2) sys_clk = ~sys_clk;
- 31
- 32 //例化HDMI彩条顶层模块
- 33 hdmi_colorbar_top u_hdmi_colorbar_top(
- 34 .sys_clk (sys_clk),
- 35 .sys_rst_n (sys_rst_n),
- 36
- 37 .tmds_clk_p (tmds_clk_p),
- 38 .tmds_clk_n (tmds_clk_n),
- 39 .tmds_data_p (tmds_data_p),
- 40 .tmds_data_n (tmds_data_n)
- 41 );
- 42
- 43 endmodule
复制代码
TB代码比较简单,主要产生系统时钟和系统复位信号,以及对HDMI彩条顶层模块进行例化。需要注意的是,由于本次实验使用了Altera官方IP核(PLL IP核和ALTDDIO_OUT IP核),因此在通过Modelsim软件进行仿真的时候,需要添加altera_mf.v文件
下面这里只截取对serializer_10_to_1模块仿真的部分波形,如下图所示:
图 24.4.18 Modelsim仿真波形图
由上图可知,当输入的10位数据为10’b0011111111时,datain_rise的值为5’b01111,data_fall的值为5’b01111,serial_data_p的变化为4个时钟周期(4个上升沿+4个下降沿)的高电平,和1个时钟周期(1个上升沿+1个下降沿)的低电平。而serial_data_n的电平变化和serial_data_p的电平变化相反。
24.5下载验证
首先我们将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用HDMI连接线将HDMI显示器连接到新起点上的HDMI接口。最后连接开发板的电源,并打开电源开关,如下图所示:
图 24.5.1 新起点开发板连接示意图
然后我们将本次实验生成的SOF文件下载至开发板中,下载完成之后HDMI显示器上显示彩条图案,说明本次实验在新起点开发板上面下载验证成功。
实验结果如下图所示:
图 24.5.2 实验结果
|
|