基于FPGA的HDMI编码模块设计(包含工程源文件)

  前文已经通过FPGA实现了TMDS视频编码的算法,也对单沿数据采样转双沿数据采样的ODDR原语做了详细讲解和仿真验证,本文将这些模块结合,设计出HDMI编码模块,在HDMI接口的显示器上显示一张图片。

1、整体思路

  如图1所示,是本文的整体框图,video_display用于产生图片的像素数据,video_driver用于生成指定分辨率,刷新率的像素有效指示信号,行、场同步信号,dvi_transmitter模块将行、场同步信号,像素信号,像素有效指示信号通过TMDS编码,然后将并行的单沿信号转换为串行双沿采样信号,最后通过OBUFDS将串行数据转换为差分信号输出,驱动HDMI接口的显示器显示图片。
在这里插入图片描述

图1 整体框图

  本次使用的显示器分辨率为1024*768,如果刷新率达到60Hz,根据数据手册要求HDMI并行数据时钟为65MHz,串行数据的时钟频率为65MHz*5 = 325MHz。开发板外部晶振提供100MHz时钟,所以利用MMCM时钟单元通过输入的100MHz时钟,输出65MHz和325MHz时钟作为其余模块时钟信号。

2、video_driver模块

  该模块实现的功能与VGA、LCD的RGB接口的功能一致,没有找到HDMI相关时序图,就放一个LCD的RGB接口时序图,如图2所示。Vsync为场同步信号,Hsync为行同步信号,DE为像素有效指示信号,Dn0~Dn7为像素数据。Hsync信号为高电平时表示在刷新一帧数据,Vsync表示刷新一行数据,在tvd和thd重合的时间内DE为高电平,此时的像素数据用于显示器显示。

在这里插入图片描述

图2 LCD的RGB接口时序

  如图3 中V_DSIP和H_DISP围成蓝色区域就是人眼看到的显示器区域,控制芯片刷新显示器时,除了产生有效区域的时序外,还要产生显示前沿和显示后沿等时序,这些时序只有行场同步信号产生对应时序,DE信号和像素信号只在蓝色区域有效。比如一块1024*600分辨率的频率,其实就是蓝色区域有1024个纵坐,有600个横坐标,即H_DISP=1024,V_DISP=600。根据数据手册知 H_SYNC=20,H_BACK=36,H_FRONT=4,即刷新一行数据,其实要发送1024+20+36+4=1084个场同步信号,相当于刷新1084列的时间。行刷新也是类似的道理。
在这里插入图片描述

图3 显示器的显示区域示意图

  显示器一般是一行一行进行刷新数据的,从左往右,从上往下刷新,当行、场同步信号生成到有效显示区域时,输出对应位置的像素数据,并且把像素有效指示信号拉高。

  该模块需要根据显示器分辨率产生符合要求的行、场同步信号,当刷新到有效显示区域时,向上游模块请求像素数据,并且把刷新位置的像素坐标输出给上游模块。向下游模块或者显示器接口输出行、场信号,像素数据,像素有效指示信号。信号端口如下表1所示:

表1 video_driver模块的端口信号
信号名I/O位宽含义
clkI1系统时钟
rstI1系统复位,高电平有效
data_reqO1像素请求信号,高电平有效
pixel_xposO11请求像素横坐标
pixel_yposO11请求像素纵坐标
pixel_dataI24上游模块输入的像素数据
video_hsO1场同步信号
video_vsO1行同步信号
video_deO1像素有效指示信号
video_rgbO24输出的像素数据

  通过视频电子标准协会的显示器标准手册(在公众号后台回复”HDMI”就可获取)就可以获得不同分辨率显示器的各个参数信息,如图4是1024*768分辨率显示器的一些参数。当刷新率为60Hz时,需要65MHz的时钟信号,同时可以获取图3中8个参数,H_DISP=1024,H_SYNC=136,H_BACK=160,H_FRONT=24,V_DISP=768,V_SYNC=6,V_BACK=29,V_FRONT=3,这些数值的单位是时钟个数。

在这里插入图片描述

图4 1024*768分辨率的各个参数

  该手册有几乎所有分辨率显示器的参数,可以查取各自的所需要的分辨率,可知1080P 60Hz需要148.5MHz的时钟信号。

  该模块的代码整体比较简单,如下所示,需要注意的是像素请求信号和像素坐标需要提前几个时钟周期发送给上游模块。、

`define HDMI_768P
module video_driver (input           	        clk	        ,//系统时钟信号;input           	        rst  	    ,//系统复位信号,高电平有效;output  reg     	        video_hs	,//行同步信号;output  reg     	        video_vs	,//场同步信号;output  reg     	        video_de	,//数据使能;output  reg [23:0]          video_rgb	,//RGB888颜色数据;output	reg			        data_req 	,//像素申请信号;input   	[23:0]          pixel_data	,//像素点数据;output  reg	[10:0]          pixel_xpos	,//像素点横坐标;output  reg	[10:0]          pixel_ypos   //像素点纵坐标;
);`ifdef HDMI_720P//1280*720 分辨率时序参数;60HZ刷新率对应时钟频率74.25MHZlocalparam  H_SYNC      =   11'd40      ;//行同步;localparam  H_BACK      =   11'd220     ;//行显示后沿;localparam  H_DISP      =   11'd1280    ;//行有效数据;localparam  H_FRONT     =   11'd110     ;//行显示前沿;localparam  V_SYNC      =   11'd5       ;//场同步;localparam  V_BACK      =   11'd20      ;//场显示后沿;localparam  V_DISP      =   11'd720     ;//场有效数据;localparam  V_FRONT     =   11'd5       ;//场显示前沿;`elsif HDMI_768P//1024*768,60HZ分辨率时序参数;对应时钟频率65MHz;localparam  H_SYNC      =  12'd136      ;//行同步;localparam  H_BACK      =  12'd160      ;//行显示后沿;localparam  H_DISP      =  12'd1024     ;//行有效数据;localparam  H_FRONT     =  12'd24       ;//行显示前沿;localparam  V_SYNC      =  12'd6        ;//场同步;localparam  V_BACK      =  12'd29       ;//场显示后沿;localparam  V_DISP      =  12'd768      ;//场有效数据;localparam  V_FRONT     =  12'd3        ;//场显示前沿;`elsif HDMI1080P//1920*1080分辨率时序参数,60HZ刷新率对应时钟频率148.5MHZlocalparam  H_SYNC      =  12'd44       ;//行同步;localparam  H_BACK      =  12'd148      ;//行显示后沿;localparam  H_DISP      =  12'd1920     ;//行有效数据;localparam  H_FRONT     =  12'd88       ;//行显示前沿;localparam  V_SYNC      =  12'd5        ;//场同步;localparam  V_BACK      =  12'd36       ;//场显示后沿;localparam  V_DISP      =  12'd1080     ;//场有效数据;localparam  V_FRONT     =  12'd4        ;//场显示前沿;`else//1024*600分辨率时序参数;localparam  H_SYNC      =  12'd20       ;//行同步;localparam  H_BACK      =  12'd140      ;//行显示后沿;localparam  H_DISP      =  12'd1024     ;//行有效数据;localparam  H_FRONT     =  12'd160      ;//行显示前沿;localparam  V_SYNC      =  12'd3        ;//场同步;localparam  V_BACK      =  12'd20       ;//场显示后沿;localparam  V_DISP      =  12'd600      ;//场有效数据;localparam  V_FRONT     =  12'd12       ;//场显示前沿;`endiflocalparam  SHOW_H_B    =   H_SYNC + H_BACK;//LCD图像行起点;localparam  SHOW_V_B    =   V_SYNC + V_BACK;//LCD图像场起点;localparam  SHOW_H_E    =   H_SYNC + H_BACK + H_DISP;//LCD图像行结束;localparam  SHOW_V_E    =   V_SYNC + V_BACK + V_DISP;//LCD图像场结束;localparam  H_TOTAL     =   H_SYNC + H_BACK + H_DISP + H_FRONT ;//行扫描周期;localparam  V_TOTAL     =   V_SYNC + V_BACK + V_DISP + V_FRONT;//场扫描周期;localparam  H_TOTAL_W   =   clogb2(H_TOTAL - 1);localparam  V_TOTAL_W   =   clogb2(V_TOTAL - 1);reg       	                video_en    ;reg  [H_TOTAL_W - 1 : 0]    cnt_h       ;reg  [V_TOTAL_W - 1 : 0]    cnt_v       ;//自动计算位宽函数function integer clogb2(input integer depth);beginif(depth == 0)clogb2 = 1;else if(depth != 0)for(clogb2=0 ; depth>0 ; clogb2=clogb2+1)depth=depth >> 1;endendfunction//行计数器对像素时钟计数;always@(posedge clk)beginif(rst)cnt_h <= {{H_TOTAL_W}{1'b0}};else if(cnt_h >= H_TOTAL - 1)cnt_h <= {{H_TOTAL_W}{1'b0}};elsecnt_h <= cnt_h + 1'b1;end//场计数器对行计数;always@(posedge clk)beginif(rst)cnt_v <= {{V_TOTAL_W}{1'b0}};else if(cnt_h == H_TOTAL - 1'b1) beginif(cnt_v >= V_TOTAL - 1'b1)cnt_v <= {{V_TOTAL_W}{1'b0}};elsecnt_v <= cnt_v + 1'b1;endend//请求像素点颜色数据输入,在产生杭长同步信号前两个时钟向上游产生请求信号;always@(posedge clk)beginif(rst)//初始值为0;data_req <= 1'b0;else if((cnt_h >= SHOW_H_B - 4) && (cnt_h < SHOW_H_E - 4) && (cnt_v >= SHOW_V_B - 1) && (cnt_v < SHOW_V_E - 1))data_req <= 1'b1;elsedata_req <= 1'b0;end//生产X轴坐标值,与req信号对齐;always@(posedge clk)beginif(rst)//初始值为0;pixel_xpos <= 11'd0;else if((cnt_h >= SHOW_H_B - 4) && (cnt_h < SHOW_H_E - 4))pixel_xpos <= cnt_h + 4 - SHOW_H_B;else pixel_xpos <= 11'd0;end//生产y轴坐标值,与req信号对齐;always@(posedge clk)beginif(rst)//初始值为0;pixel_ypos <= 11'd0;else if((cnt_v >= SHOW_V_B - 1) && (cnt_v < SHOW_V_E - 1))pixel_ypos <= cnt_v + 1 - SHOW_V_B;else pixel_ypos <= 11'd0;end//video_hs相对data_req滞后两个时钟周期,video_de和video_rgb要与video_hs对齐;//则video_de和video_rgb也要滞后data_req两个时钟;//要求像素数据pixel_data在data_req拉高后的下一个时钟输入;always@(posedge clk)beginif(rst)begin//初始值为0;video_hs <= 1'b0;video_vs <= 1'b0;video_en <= 1'b0;video_de <= 1'b0;video_rgb <= 24'd0;endelse beginvideo_en <= data_req;video_de <= video_en;video_hs <= (cnt_h >= H_SYNC);//行同步信号赋值;video_vs <= (cnt_v >= V_SYNC);//场同步信号赋值;video_rgb <= video_en ? pixel_data : 24'd0;//RGB888数据输出;endendendmodule

  在本次设计中,上游模块输出的像素数据会滞后像素坐标两个时钟(坐标经过2级触发器后输出像素数据),由于生成请求信号又是时序电路,所以需要提前三个时钟周期(即提前三列,即场计数器提前4个时钟周期,计数器从0开始计数)产生像素请求信号和对应坐标。保证所有信号严格对齐传输到下一模块即可。

  该模块仿真如下面几张图所示。

在这里插入图片描述

图5 整体仿真

在这里插入图片描述

图6 开始刷新一行时序

在这里插入图片描述

图7 刷新一行图像的中间时序

在这里插入图片描述

图8 刷新一行尾部时序

3、video_display模块

  video_display模块是video_driver模块的上游模块,需要根据video_driver模块的像素请求信号和坐标数据输出对应的像素数据。对应的端口信号如下表2所示。

表2 video_display模块端口信号
信号名I/O位宽定义
clkI1系统时钟
rstI1系统复位,高电平有效
pixel_reqI1像素请求信号,高电平有效
pixel_xposI11像素点横坐标
pixel_yposI11像素点纵坐标
pixel_dataO24像素数据

  本文通过LCD显示图片,通过Image2Lcd 2.9软件获取生成图片数据,将数据存储在rom中,然后通过坐标值读取对应数据显示在显示器上即可。

  如图9所示,解压软件安装包,双击Img2Lcd.exe文件即可运行。

在这里插入图片描述

图9 Image2Lcd 2.9软件

  如图10所示,加载一张图片,输出数据类型选择数组格式,水平扫描,生成300*200分辨率的16位rgb565格式数据。由于本文没有使用DDR存储器,只使用了内部的ROM IP(一个ROM最多存储65536个数据)对数据进行存储,所以数据量不能过大,在显示时,将图片放大三倍即可占满大半个屏幕。

在这里插入图片描述

图10 Image2Lcd 2.9软件生成数据

  由于ROM IP存储数据的格式与输出数据的格式有点出入,需要把数组的前后括号去掉,利用vscode的替换功能,将文件中的0x删除,将逗号替换为空格,需要将前后两个8位数据合并为16位数据,将回车全部去掉,操作方式如下视频所示。

图片数据处理

  利用ROM IP存储上述生成的数据,生成ROM IP的方法比较简单,如下所示,深度等于分辨率,即300*200,数据宽度为16位。

在这里插入图片描述

图11 ROM IP位宽深度设置

  Port coning页面直接默认即可,此处不对输入、输出信号加寄存器延时,最后配置图12界面即可,首先点击1处,在一个路径下存储一个.coe文件。点击2处为这个文件添加内容(ROM初始化数据),在弹出的界面下,点击3处填写“memory_initialization_radix”,在4处填写16,表示存储在.coe文件中是16进制数据。然后就是在5处填写“memory_initialization_vector”,6处就是ROM IP存储的数据,即前文视频中利用vscode处理的60000个16进制图像数据,将该文件的数据复制到图6处,然后点击图7生产ROM初始化文件,依次点击8、9处,最后10处表示没有被初始化的内存部分被赋值为16进制的0。

在这里插入图片描述

图12 ROM初始化数据生成

  ROM存储300200分辨率的像素数据,显示时将图片放大三倍,分辨率变为900600,那么将图片放到显示器中间,如下图13所示。当下游模块输出的横坐标位于62962,纵坐标位于84684之间时,从ROM中读取对应位置的数据进行显示。
在这里插入图片描述

图13 LCD显示策略

  具体处理方式为,当横坐标位于62962,纵坐标位于84684之间时,横坐标减去62,纵坐标减去84生成新的坐标(x,y)来取ROM中的数据。由于需要将图片放大三倍,即显示器连续三行显示一行图像,显示器连续三列显示同一列的像素数据。因此x/3+y/3*200作为ROM地址即可实现图片放大三倍,乘以200时因为存储的一行数据有200个。

  注意此模块,输入坐标到对应坐标输出像素数据只延迟两个时钟,与上一个模块要求的延时必须一致。需要将ROM存储的16位像素数据转换为24位像素数据输出给下游模块进行显示,rgb565转rgb888很简单,补零即可,不再赘述。参考代码如下:

module  video_display(input                   clk         ,input                   rst         ,input                   pixel_req   ,//请求输入像素数据;input        [10 : 0]   pixel_xpos  ,//像素点横坐标input        [10 : 0]   pixel_ypos  ,//像素点纵坐标output  reg  [23 : 0]   pixel_data   //像素点数据
);localparam WHITE  = 24'b11111111_11111111_11111111;  //RGB888 白色localparam RED    = 24'b11111111_00001100_00000000;  //RGB888 红色localparam GREEN  = 24'b00000000_11111111_00000000;  //RGB888 绿色localparam BLUE   = 24'b00000000_00000000_11111111;  //RGB888 蓝色reg  [1 : 0]    pixel_req_r;always@(posedge clk)beginpixel_req_r <= {pixel_req_r[0],pixel_req};endreg [9 : 0] x,y;//always@(posedge clk)beginif(rst)begin//初始值为0;x <= 10'd0;endelse if(pixel_xpos >= 62 && pixel_xpos < 962)beginx <= pixel_xpos - 62;endelse beginx <= 0;endendalways@(posedge clk)beginif(rst)begin//初始值为0;y <= 10'd0;endelse if(pixel_ypos >= 84 && pixel_ypos < 684)beginy <= pixel_ypos - 84;endelse beginy <= 0;endendreg [9 : 0] pixel_xpos_r,pixel_ypos_r;//always@(posedge clk)beginpixel_xpos_r <= pixel_xpos;pixel_ypos_r <= pixel_ypos;endreg [15 : 0] addr;//always@(posedge clk)beginif(rst)begin//初始值为0;addr <= 16'd0;endelse beginaddr <= x/3 + (y/3)*300;endendwire  [15:0]    rom_out;video_rom u_video_rom (.a      ( addr      ),// input wire [15 : 0] a.spo    ( rom_out   ) // output wire [23 : 0] spo);//根据当前像素点坐标指定当前像素点颜色数据,在屏幕上显示图片;always@(posedge clk)beginif(rst)pixel_data <= WHITE;else if(pixel_req_r[1])beginif((pixel_xpos_r >= 62) && (pixel_xpos_r < 962) && (pixel_ypos_r >= 84) && (pixel_ypos_r < 684))pixel_data <= {rom_out[15:11],3'd0,rom_out[10:5],2'd0,rom_out[4:0],3'd0};else pixel_data <= WHITE;endelse beginpixel_data <= WHITE;endendendmodule

  对该模块进行仿真,仿真结果如下面几张图所示,pixel_req信号每拉高一次表示请求输入一行数据,同时pixel_xpos和pixel_ypos分别表示屏幕上像素点的横坐标和纵坐标,x和y表示图像显示区域的坐标,图14是一张宏观的仿真图,下游模块请求输入几行数据。

在这里插入图片描述

图14 整体仿真

在这里插入图片描述

图15 输出显示第一行数据

  由图15可知,当下游刷新到84行62列时,开始输出第一行数据,并且连续三列输出的数据时相等的,ROM IP的地址addr信号没经过三个x坐标变化一次,ROM输出的第一数据为16’h29e4,第二个数据为16‘h10e2,与ROM IP初始化的前两个数据也能对应上,如图16所示;观察信号可知输出像素数据滞后输入坐标两个时钟周期,满足前文的时序要求,该模块仿真通过。

在这里插入图片描述

图16 ROM IP初始化数据

  由图17可知,当列地址大于962时,不在从ROM IP中输出像素数据,与前文的显示区域规划一致。

在这里插入图片描述

图17 仿真第一行数据结束阶段

4、dvi_transmitter模块

该模块的接口信号如下表3所示:

表3 dvi_transmitter模块端口信号列表
信号名I/O位宽定义
clkI1像素时钟信号
Clk_5xI15倍clk频率的时钟信号
rstI1复位信号,高电平有效
video_hsyncI1场同步信号
video_vsyncI1行同步信号
video_deI1像素有效指示信号
video_dinI24像素输入数据
tmds_clk_pO1HDMI差分时钟P端
tmds_clk_nO1HDMI差分时钟n端
tmds_data_pO3HDMI三路差分数据线P端
tmds_data_nO3HDMI三路差分数据线n端
tmds_oenO1HDMI接口方向信号,高电平设置为输出

  HDMI驱动模块,该模块内部框图如下所示,利用前面文章编写的TMDS算法模块对输入的像素数据video_din、行、场同步信号video_vsync和video_hsync,像素有效指示信号video_de进行编码,输出三路10位并行编码数据tms_out数据。

  接下来就是把10位单沿采样的并行数据转换成串行的双沿采样数据,并行转串行采用计数器加时序逻辑实现,而单沿采样转双沿采样使用ODDR原语实现。首先通过拼接和计数器将10位并行数据转换成2路串行数据输入给ODDR原语,为什么转换成两路串行数据与ODDR原语的工作方式有关,可以查看前文对ODDR原语的讲解部分。
在这里插入图片描述

图18 HDMI编码接口模块框图

  此处ODDR使用SAME_EDGE模式,将时钟上升沿采集的2路串行数据,在时钟的上升沿和下降沿分别输出,生成oddr_out双沿信号。将该信号输入obufds原语,转换成差分信号输出FPGA芯片,通过HDMI接口直接传输。

  TMDS连接的时钟通道采用与数据通道相同的并转串逻辑来实现,通过对10位二进制序列10’b11111_00000在10倍像素时钟频率下进行并串转换,得到像素时钟频率下的TMDS参考时钟。

  注意并串转换和ODDR原语使用时钟信号的频率是TMDS算法模块时钟的5倍,原因是将10位并行数据转为双沿采样的串行数据。

  本文只使用HDMI作为输出接口,所以只需要将tmds_open信号赋值为高电平把HDMI接口设置为输出即可。

  该模块为了简化书写,使用了一些for循环模块,如果对for循环使用不是很了解,可以点击查看前文对for循环的讲解和使用。

  对应代码比较简单,参考代码如下所示:

module dvi_transmitter(input                   clk         ,//系统时钟信号,input                   clk_5x      ,//频率为系统时钟5倍的时钟信号;input                   rst         ,//系统复位,高电平有效;input   [23 : 0]        video_din   ,//RGB888视频输入信号;input                   video_hsync ,//行同步信号;input                   video_vsync ,//场同步信号;input                   video_de    ,//像素使能信号;output                  tmds_clk_p  ,// TMDS 时钟通道output                  tmds_clk_n  ,output  [2 : 0]         tmds_data_p ,// TMDS 数据通道output  [2 : 0]         tmds_data_n ,output                  tmds_oen     // TMDS 输出使能
); wire [9 : 0] tms_out    [3 : 0]         ;assign tmds_oen = 1'b1;//将双向的HDMI接口设置为输出。//对三个颜色通道进行编码dvi_tmds_encoder u_dvi_tmds_b (.clk    (clk            ),//系统时钟信号;.rst    (rst            ),//系统复位信号,高电平有效;.din    (video_din[7:0] ),//输入待编码数据;.c0	    (video_hsync    ),//控制信号C0;.c1	    (video_vsync    ),//控制信号c1;.de	    (video_de       ),//输入数据有效指示信号;;.q_out  (tms_out[0][9:0]) //编码输出数据;);dvi_tmds_encoder u_dvi_tmds_g (.clk    (clk            ),.rst    (rst            ),.din    (video_din[15:8]),.c0     (1'b0           ),.c1     (1'b0           ),.de     (video_de       ),.q_out  (tms_out[1][9:0]));dvi_tmds_encoder u_dvi_tmds_r (.clk    (clk            ),.rst    (rst            ),.din    (video_din[23:16]),.c0	    (1'b0           ),.c1	    (1'b0           ),.de	    (video_de       ),.q_out  (tms_out[2][9:0]));assign tms_out[3][9 : 0] = 10'b11_1110_0000;//时钟信号编码后的数据为10'b11_1110_0000;wire [4 : 0]    tms_out_l    [3 : 0];wire [4 : 0]    tms_out_h    [3 : 0];wire  [3 : 0]   oddr_out ;//将编码数据进行拼接,拼接成IDDR两路输入信号的数据格式。generategenvar i;for(i=0 ; i<4 ; i=i+1)begin : JOINTassign tms_out_l[i][4:0] = {tms_out[i][8],tms_out[i][6],tms_out[i][4],tms_out[i][2],tms_out[i][0]};assign tms_out_h[i][4:0] = {tms_out[i][9],tms_out[i][7],tms_out[i][5],tms_out[i][3],tms_out[i][1]};endendgeneratereg [2 : 0] cnt;////5进制计数器,用于将5位并行数据转换为串行数据;always@(posedge clk_5x)beginif(rst)begin//初始值为0;cnt <= 3'd0;endelse if(cnt == 3'd4)begincnt <= 3'd0;endelse begincnt <= cnt + 3'd1;endendreg  [3 : 0]    iddr_l      ;reg  [3 : 0]    iddr_h      ;wire [3 : 0]    obufds_out_p;wire [3 : 0]    obufds_out_n;generatefor(i=0 ; i<4 ; i=i+1)begin : ODDR//将编码拼接后的5位并行数据转换为串行数据;always@(posedge clk_5x)beginif(cnt > 3'd0)beginiddr_l[i] <= tms_out_l[i][cnt-1];iddr_h[i] <= tms_out_h[i][cnt-1];endelse beginiddr_l[i] <= tms_out_l[i][4];iddr_h[i] <= tms_out_h[i][4];endend//调用ODDR原语完成单沿转双沿;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" ) u_ODDR (.Q  (oddr_out[i]),// 1-bit DDR output.C  (clk_5x     ),// 1-bit clock input.CE (1'b1       ),// 1-bit clock enable input.D1 (iddr_l[i]  ),// 1-bit data input (positive edge).D2 (iddr_h[i]  ),// 1-bit data input (negative edge).R  (rst        ),// 1-bit reset.S  (1'b0       ) // 1-bit set);//调用OBUFDS原语,将ODDR输出的双沿信号转换为差分信号;OBUFDS #(.IOSTANDARD ("TMDS_33"  )//I/O电平标准为TMDS)u_obufds (.I  (oddr_out[i]    ),//ODDR输出的双沿信号;.O  (obufds_out_p[i]),.OB (obufds_out_n[i]) );endendgenerateassign tmds_clk_p = obufds_out_p[3];assign tmds_clk_n = obufds_out_n[3];assign tmds_data_p = obufds_out_p[2 : 0];assign tmds_data_n = obufds_out_n[2 : 0];endmodule

  该模块仿真截图如图19所示,经过验证没有问题,涉及差分信号之类的比较多,仿真不好分析,有兴趣的打开工程文件对照波形和代码自行分析。

在这里插入图片描述

图19 HDMI驱动模块仿真

5、锁相环模块

  本文使用显示器分辨率为1024*768,如果刷新率为60Hz,需要像素时钟65MHz,同时ODDR原语使用时钟为像素时钟5倍,即325MHz。所以需要利用锁相环将开发板输入的100MHz时钟信号转换为65MHz和325MHz的时钟信号给其余模块使用。

  锁相环模块设置如下所示:

在这里插入图片描述

图20 锁相环模块配置

6、上板测试

  顶层模块的参考代码如下所示:

module  top(input                   sys_clk     ,input                   sys_rst_n   ,output                  tmds_clk_p  ,// TMDS 时钟通道output                  tmds_clk_n  ,output                  tmds_oen    ,output [2 : 0]          tmds_data_p ,// TMDS 数据通道output [2 : 0]          tmds_data_n
);wire                    clk         ;wire                    rst         ;wire                    clk_5x      ;wire                    locked      ;wire  [10 : 0]          pixel_xpos_w;wire  [10 : 0]          pixel_ypos_w;wire  [23 : 0]          pixel_data_w;wire                    video_hs    ;wire                    video_vs    ;wire                    video_de    ;wire  [23 : 0]          video_rgb   ;assign rst = ~sys_rst_n;//例化锁相环IP;clk_wiz_0  U_clk_wiz_0(.clk_in1    ( sys_clk   ),//输入系统时钟;.clk_out1   ( clk       ),//像素时钟;.clk_out2   ( clk_5x    ),//5倍像素时钟;.resetn     ( sys_rst_n ),//系统复位,低电平有效;.locked     ( locked    ));//例化视频显示驱动模块video_driver  u_video_driver(.clk            ( clk           ),//系统时钟信号;.rst            ( rst           ),//系统复位信号,高电平有效;.video_hs       ( video_hs      ),//行同步信号;.video_vs       ( video_vs      ),//场同步信号;.video_de       ( video_de      ),//数据使能;.video_rgb      ( video_rgb     ),//RGB888颜色数据;.data_req		( data_req      ),//像素申请信号;.pixel_xpos     ( pixel_xpos_w  ),//像素点数据;.pixel_ypos     ( pixel_ypos_w  ),//像素点横坐标;.pixel_data     ( pixel_data_w  ) //像素点纵坐标;);//例化视频显示模块video_display  u_video_display(.clk            ( clk           ),//系统时钟信号;.rst            ( rst           ),//系统复位信号,高电平有效;.pixel_req      ( data_req      ),//请求输入像素数据;.pixel_xpos     ( pixel_xpos_w  ),//像素点横坐标.pixel_ypos     ( pixel_ypos_w  ),//像素点纵坐标.pixel_data     ( pixel_data_w  ) //像素点数据);//例化HDMI驱动模块dvi_transmitter u_dvi_transmitter(.clk           ( clk        ),//系统时钟信号,.clk_5x        ( clk_5x     ),//频率为系统时钟5倍的时钟信号;.rst           ( rst        ),//系统复位,高电平有效;.video_din     ( video_rgb  ),//RGB888视频输入信号;.video_hsync   ( video_hs   ),//行同步信号;.video_vsync   ( video_vs   ),//场同步信号;.video_de      ( video_de   ),//像素使能信号;.tmds_clk_p    ( tmds_clk_p ),//TMDS时钟通道;.tmds_clk_n    ( tmds_clk_n ),.tmds_data_p   ( tmds_data_p),//TMDS数据通道;.tmds_data_n   ( tmds_data_n), .tmds_oen      ( tmds_oen   ) //TMDS输出使能;);endmodule 

  对应的TestBench比较简单,参考代码如下所示:

`timescale 1 ns/1 ns
module test();parameter	CYCLE		=   10          ;//系统时钟周期,单位ns,默认10ns;parameter	RST_TIME	=   10          ;//系统复位持续时间,默认10个系统时钟周期;parameter	STOP_TIME	=   1000        ;//仿真运行时间,复位完成后运行1000个系统时钟后停止;reg			                sys_clk     ;//系统时钟,默认100MHz;reg			                sys_rst_n   ;//系统复位,默认低电平有效;wire                        tmds_clk_p  ;wire                        tmds_clk_n  ;wire                        tmds_oen    ;wire  [2 : 0]               tmds_data_p ;wire  [2 : 0]               tmds_data_n ;top  u_top (.sys_clk        ( sys_clk       ),.sys_rst_n      ( sys_rst_n     ),.tmds_clk_p     ( tmds_clk_p    ),.tmds_clk_n     ( tmds_clk_n    ),.tmds_oen       ( tmds_oen      ),.tmds_data_p    ( tmds_data_p   ),.tmds_data_n    ( tmds_data_n   ));//生成周期为CYCLE数值的系统时钟;initial beginsys_clk = 0;forever #(CYCLE/2) sys_clk = ~sys_clk;end//生成复位信号;initial beginsys_rst_n = 1;#2;sys_rst_n = 0;//开始时复位10个时钟;#(RST_TIME*CYCLE);sys_rst_n = 1;#(STOP_TIME*CYCLE);$stop;//停止仿真;endendmodule

  上板测试结果:

HDMI显示结果

在这里插入图片描述
  由于ROM大小限制,生产图片的时候像素的采样率第,加上图片被放大三倍,导致显示300*200像素的图片相比电脑文件夹里图片模糊,是正常现象,后续采用DDR将整张图片存储即可清晰显示。

  整个工程就完成了,通过HDMI接口主要是学习HDMI接口的硬件原理,TMDS算法实现,以及xilinx的一些原语,通过阅读手册,至少知道如何将单端信号和差分信号相互转换,高速的并行串行数据如何转换,怎么把触发器放在ILOGIC和OLOGIC中,如何对输入输出的信号进行延时的设计。

  其实该接口还可以进行简化,本文使用代码完成并行转串行,前文还学过一个原语,可以直接将并行单沿采集的数据直接转换为串行双沿采集的数据,代码比ODDR更简单,也更可靠,下一节将使用该原语实现HDMI接口。

  本文值讲述了HDMI的编码设计,别忘了开发板上的HDMI接口可是双向的,既然Verilog HDL能够实现HDMI编码,那也肯定可以实现HDMI解码的,到时候就会使用差分转单端,双沿转单沿,串行转并行。所以前文所讲解到的原语几乎可以全部使用在HDMI接口的设计中。但是此开发板上只有一个HDMI接口,解码后的数据无法视觉验证正确性,所以先放一放,在采购一个HDMI模块后进行HDMI解码。

  需要本文工程在后台回复”基于FPGA的HDMI接口设计”(不包括引号),选择ODDR实现的文件即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/229885.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Github 2023-12-18 开源项目周报 Top14

根据Github Trendings的统计&#xff0c;本周(2023-12-18统计)共有14个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量TypeScript项目4Python项目4Jupyter Notebook项目3非开发语言项目1JavaScript项目1Rust项目1Go项目1 基于项目…

【5G PHY】5G小区类型、小区组和小区节点的概念介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

前后端传参中遇见的问题

前后端传参经常容易出错&#xff0c;本文记录开发springBootMybatis-plusvuecli项目中出现的传参问题及解决办法 1.前后端没有跨域配置&#xff0c;报错 解决方法&#xff1a;后端进行跨域配置&#xff0c;拷贝CorsConfig类 package com.example.xxxx.config;import org.spr…

web服务器之——基于虚拟目录和用户控制的web网站

目录 一、虚拟目录 虚拟目录的作用&#xff1a; 二、搭建基于虚拟目录的web网站 1、www服务器配置 2、搭建静态网站 设置防火墙状态 关闭文件访问权限——SeLinux 3、编辑网页资源文件 4、设置虚拟目录 5、向虚拟目录中写入资源 6、重启httpd 三、搭建基…

Flink系列之:监控反压

Flink系列之&#xff1a;监控反压 一、反压二、Task 性能指标三、示例四、反压状态 Flink Web 界面提供了一个选项卡来监控正在运行 jobs 的反压行为。 一、反压 如果你看到一个 task 发生 反压警告&#xff08;例如&#xff1a; High&#xff09;&#xff0c;意味着它生产数…

什么是缓存击穿、缓存穿透、缓存雪崩?

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

postman脚本生成可执行文件(6)

一.通过Python脚本&#xff08;executescript.py&#xff09;执行newman指令 #!usr/bin/python import subprocess from datetime import datetimeclass Newman_automate():Newman_automate():该类主要是定义postman脚本执行__path:cmd命令行中执行newnan脚本指令&#xff08;…

C语言—每日选择题—Day50

一天一天的更新&#xff0c;也是达到50天了&#xff0c;精选的题有250道&#xff0c;博主累计做了不下500道选择题&#xff0c;最喜欢的题型就是指针和数组之间的计算呀&#xff0c;不知道关注我的小伙伴是不是一直在坚持呢&#xff1f;文末有投票&#xff0c;大家可以投票让博…

[Big Bird]论文解读:Big Bird: Transformers for Longer Sequences

文章目录 1 介绍2 模型架构3 结果 论文&#xff1a;Big Bird: Transformers for Longer Sequences 作者&#xff1a;Manzil Zaheer, Guru Guruganesh, Avinava Dubey, Joshua Ainslie, Chris Alberti, Santiago Ontanon, Philip Pham, Anirudh Ravula, Qifan Wang, Li Yang, Am…

【数据结构】树状数组总结

知识概览 树状数组有两个作用&#xff1a; 快速求前缀和 时间复杂度O(log(n))修改某一个数 时间复杂度O(log(n)) 例题展示 1. 单点修改&#xff0c;区间查询 题目链接 活动 - AcWing本活动组织刷《算法竞赛进阶指南》&#xff0c;系统学习各种编程算法。主要面向…

关于“Python”的核心知识点整理大全24

目录 ​编辑 10.1.6 包含一百万位的大型文件 pi_string.py 10.1.7 圆周率值中包含你的生日吗 10.2 写入文件 10.2.1 写入空文件 write_message.py programming.txt 10.2.2 写入多行 10.2.3 附加到文件 write_message.py programming.txt 10.3 异常 10.3.1 处理 Ze…

es6学习(一):变量声明的方式对比:var,let,const

前言 在let和const出现之前,js可以使用var为变量命令,如果是函数也可以用function命名,甚至你可以直接不用任何关键字命名 var a 1function fn() { }b 2console.log(a)console.log(fn)console.log(b) 结果如下 var的特性 1.window环境下,var在最外层定义的变量会直接赋值给…

【JVM从入门到实战】(八)垃圾回收(1)

内存泄漏&#xff1a;指的是不再使用的对象在系统中未被回收&#xff0c;内存泄漏的积累可能会导致内存溢出 什么是垃圾回收 Java中为了简化对象的释放&#xff0c;引入了自动的垃圾回收&#xff08;Garbage Collection简称GC&#xff09;机制。通过垃 圾回收器来对不再使用的…

力扣刷题-二叉树-平衡二叉树

110 平衡二叉树 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a;一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 示例 1: 给定二叉树 [3,9,20,null,null,15,7] 返回 true 。 给定二叉树 [1…

音画欣赏|《红尘入戏》

《红尘入戏》 46X68cm 陈可之2023年绘 《秋月》 【宋】朱熹 清溪流过碧山头&#xff0c;空水澄鲜一色秋。 隔断红尘三十里&#xff0c;白云红叶两悠悠。 《白日偶无客青山长对门》其四 【宋】韩淲 人生等戏剧&#xff0c;衮衮徒区区。 老身其回头&#xff0c;今有古非无。 -…

Kafka相关知识

一、kafka架构 Kafka基础知识 Kafka是最初由Linkedin公司开发&#xff0c;是一个分布式、分区的、多副本的、多生产者、多订阅者&#xff0c;基于zookeeper协 调的分布式日志系统(也可以当做MQ系统)&#xff0c;常见可以用于webynginx日志、访问日志&#xff0c;消息服务等等&…

Arma3/武装突袭3东风战役最后一关游戏无法保存的解决办法

Arma3这个游戏玩进去还是非常有可玩性的&#xff0c;可是在玩过了它本体自带的东风系列战役后&#xff0c;在最精髓的最后一关——game over这个关卡&#xff0c;却有个非常头疼的问题。 逃跑其实是非常简单的&#xff0c;但是想要无伤环游全岛确十分困难&#xff0c;因为这关卡…

游戏运行中突然掉线是什么原因导致的

游戏平稳运行的原因只有一个&#xff0c;掉线的原因各有个的不同。这些不同的原因有常见&#xff0c;也有不常见的。但不管出于什么原因的掉线&#xff0c;带来的损失又是相同的。 首先最常见的原因就是攻击造成的 像CC&#xff0c;DDOS。CC会造成服务器资源的浪费&…

超详细教程:使用React实现动态轮播图

前言 轮播组件是常见的一种方式&#xff0c;用来展示图像、信息或者是广告。我们可以使用React来创建一个轮播组件&#xff0c;并且利用其中的State和effect Hook来创建一款动态的、可以自动播放的轮播组件。 效果 轮播组件会展示一个平铺的图片列表。在图片列表下方是一组小…

Missing artifact org.wltea.analyzer:ik-analyzer:jar:5.0

没有找到【org.wltea.analyzer】 找到了【org.wltea.ik-analyzer】 https://github.com/wks/ik-analyzer https://github.com/wks/ik-analyzer.git https://code.google.com/archive/p/ik-analyzer/downloads?page2 C:\Users\Administrator\Desktop\ik-analyzer-master>m…