本文演示如何用PL逻辑资源去驱动最小系统板上 HDMI部分的电路来实现点屏的操作
本文在 vivado2018.3版本上 演示, 其他版本请自行研究
(备注 此章节内容适用于 Smart ZYNQ SP和SL 版的板子,如果是Smart ZYNQ 标准版请看对应板子目录)
关于硬件部分:
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-28.png)
其中HDMI_TX_TMDS_DATA0-1-2 以及HDMI_CLK_TX_TMDS信号都是接到ZYNQ的PL 差分信号线上的。
另外板子的HDMI 都是接ESD 芯片的(PUSB3FR4) 这样插拔 HDMI的过程当中不会因为静电而打坏板子
想要驱动HDMI模块,我们还需要用到 digilent官方设计的一个IP核 RGB2DVI,这个IP核的官方下载地址如下:https://github.com/Digilent/vivado-library
这里为了方便使用,我也把这个IP核下下来放在本站供大家参考(IP版权归digilent所有)
http://www.hellofpga.com/wp-content/uploads/2021/07/rgb2dvi.zip
RGB2DVI 的输入时序满足VGA的标准,以下是VGA分辨率的参数
![](http://xilinx.eetrend.com/files-eetrend-xilinx/blog/201804/12782-36276-tu_9shi_xu_can_shu_.png)
时序图,以及消隐的概念,还有不同分辨率时,各个区域的参数
模块设计:
1.工程创建:
1)新建一个项目,芯片型号选择 XC7Z020CLG484-1
![](http://www.hellofpga.com/wp-content/uploads/2023/04/image-3-1024x846.png)
工程创建详细图文教程请参考 Smart ZYNQ(SP&SL 版) 工程一 用ZYNQ的PL资源点亮一个LED(完整图文)
2.时钟模块设计
我们尝试点亮800X600的屏幕分辨率,如VGA的分辨率参数表中标注的,800X600分辨率需要的工作的像素时钟为40MH,而我们板子上焊接的有源晶体是50MHZ,这里就需要用时钟管理模块MMCM来生成我们要的40MHZ频率和 5倍像素时钟的200Mhz频率(也可以通过always块来实现5倍关系的时钟,具体看本文最下方增加的部分内容,这种方式适用的分辨率更广)
VIVADO系统给我们内置了很多功能强大的模块,包括我们要的时钟模块,这里我们只需要调用时钟模块输入我们要的参数就好
1)第一步,点击IP Catalog 打开模块选择器, 在里面的搜索栏输入 CLOCKING ,系统会自动跳出符合的 Clocking Wizard选项,双击它
![](http://www.hellofpga.com/wp-content/uploads/2021/07/image-3.png)
2)在弹出的窗口中我们将input Frequence 输入频率修改为板子上焊接的50M时钟, 右边改为单端输入
![](http://www.hellofpga.com/wp-content/uploads/2021/07/image-11-1024x660.png)
3)在output Clocks选项中 将clk_out1改成40m,将clk_out2改成200m
![](http://www.hellofpga.com/wp-content/uploads/2021/07/image-12.png)
4)将界面托到最下面,因为我们这里的要求并不高,所以把时钟的复位reset,和locked选项去除,最后点击ok生成模块
![](http://www.hellofpga.com/wp-content/uploads/2021/07/image-15.png)
3.导入下下来的DVI2RGB模块
1)点击Setting
![](http://www.hellofpga.com/wp-content/uploads/2021/07/image-24.png)
2)在弹出的设置窗口,如下图,展开IP选项,选中Packager,在点击里面的加号增加目录,选中RGB2DVI的目录(这里已提前将RGB2DVI的目录复制到工程目录下),点击ok
![](http://www.hellofpga.com/wp-content/uploads/2023/01/image-65.png)
3)导入RGB2DVI IP,如下图所示,在IP管理器里搜索RGB 双击并打开RGB to DVI Video Encoder 选项
![](http://www.hellofpga.com/wp-content/uploads/2021/07/image-30.png)
4)设置RGB2DVI模块,因为我们已经在时钟模块中设置了 5倍的编码时钟,所以 模块里不需要再生成模块时钟,如下图去掉复位和内部串行时钟前面的勾,点击OK
![](http://www.hellofpga.com/wp-content/uploads/2021/07/image-31.png)
完成之后 我们便得到了两个模块
![](http://www.hellofpga.com/wp-content/uploads/2021/07/image-32.png)
4.增加我们的代码内容
1)这里创建一个显示模块显示模块负责输出标准的RGB时序,这里调用了一个网上的参考代码,具体如下( color_bar.v)
module color_bar( input clk, //pixel clock input rst, //reset signal high active output hs, //horizontal synchronization output vs, //vertical synchronization output de, //video valid output[7:0] rgb_r, //video red data output[7:0] rgb_g, //video green data output[7:0] rgb_b //video blue data ); `define VIDEO_800_600 //video timing parameter definition `ifdef VIDEO_1280_720 parameter H_ACTIVE = 16'd1280; //horizontal active time (pixels) parameter H_FP = 16'd110; //horizontal front porch (pixels) parameter H_SYNC = 16'd40; //horizontal sync time(pixels) parameter H_BP = 16'd220; //horizontal back porch (pixels) parameter V_ACTIVE = 16'd720; //vertical active Time (lines) parameter V_FP = 16'd5; //vertical front porch (lines) parameter V_SYNC = 16'd5; //vertical sync time (lines) parameter V_BP = 16'd20; //vertical back porch (lines) parameter HS_POL = 1'b1; //horizontal sync polarity, 1 : POSITIVE,0 : NEGATIVE; parameter VS_POL = 1'b1; //vertical sync polarity, 1 : POSITIVE,0 : NEGATIVE; `endif //480x272 9Mhz `ifdef VIDEO_480_272 parameter H_ACTIVE = 16'd480; parameter H_FP = 16'd2; parameter H_SYNC = 16'd41; parameter H_BP = 16'd2; parameter V_ACTIVE = 16'd272; parameter V_FP = 16'd2; parameter V_SYNC = 16'd10; parameter V_BP = 16'd2; parameter HS_POL = 1'b0; parameter VS_POL = 1'b0; `endif //640x480 25.175Mhz `ifdef VIDEO_640_480 parameter H_ACTIVE = 16'd640; parameter H_FP = 16'd16; parameter H_SYNC = 16'd96; parameter H_BP = 16'd48; parameter V_ACTIVE = 16'd480; parameter V_FP = 16'd10; parameter V_SYNC = 16'd2; parameter V_BP = 16'd33; parameter HS_POL = 1'b0; parameter VS_POL = 1'b0; `endif //800x480 33Mhz `ifdef VIDEO_800_480 parameter H_ACTIVE = 16'd800; parameter H_FP = 16'd40; parameter H_SYNC = 16'd128; parameter H_BP = 16'd88; parameter V_ACTIVE = 16'd480; parameter V_FP = 16'd1; parameter V_SYNC = 16'd3; parameter V_BP = 16'd21; parameter HS_POL = 1'b0; parameter VS_POL = 1'b0; `endif //800x600 40Mhz `ifdef VIDEO_800_600 parameter H_ACTIVE = 16'd800; parameter H_FP = 16'd40; parameter H_SYNC = 16'd128; parameter H_BP = 16'd88; parameter V_ACTIVE = 16'd600; parameter V_FP = 16'd1; parameter V_SYNC = 16'd4; parameter V_BP = 16'd23; parameter HS_POL = 1'b1; parameter VS_POL = 1'b1; `endif //1024x768 65Mhz `ifdef VIDEO_1024_768 parameter H_ACTIVE = 16'd1024; parameter H_FP = 16'd24; parameter H_SYNC = 16'd136; parameter H_BP = 16'd160; parameter V_ACTIVE = 16'd768; parameter V_FP = 16'd3; parameter V_SYNC = 16'd6; parameter V_BP = 16'd29; parameter HS_POL = 1'b0; parameter VS_POL = 1'b0; `endif //1920x1080 148.5Mhz `ifdef VIDEO_1920_1080 parameter H_ACTIVE = 16'd1920; parameter H_FP = 16'd88; parameter H_SYNC = 16'd44; parameter H_BP = 16'd148; parameter V_ACTIVE = 16'd1080; parameter V_FP = 16'd4; parameter V_SYNC = 16'd5; parameter V_BP = 16'd36; parameter HS_POL = 1'b1; parameter VS_POL = 1'b1; `endif parameter H_TOTAL = H_ACTIVE + H_FP + H_SYNC + H_BP;//horizontal total time (pixels) parameter V_TOTAL = V_ACTIVE + V_FP + V_SYNC + V_BP;//vertical total time (lines) //define the RGB values for 8 colors parameter WHITE_R = 8'hff; parameter WHITE_G = 8'hff; parameter WHITE_B = 8'hff; parameter YELLOW_R = 8'hff; parameter YELLOW_G = 8'hff; parameter YELLOW_B = 8'h00; parameter CYAN_R = 8'h00; parameter CYAN_G = 8'hff; parameter CYAN_B = 8'hff; parameter GREEN_R = 8'h00; parameter GREEN_G = 8'hff; parameter GREEN_B = 8'h00; parameter MAGENTA_R = 8'hff; parameter MAGENTA_G = 8'h00; parameter MAGENTA_B = 8'hff; parameter RED_R = 8'hff; parameter RED_G = 8'h00; parameter RED_B = 8'h00; parameter BLUE_R = 8'h00; parameter BLUE_G = 8'h00; parameter BLUE_B = 8'hff; parameter BLACK_R = 8'h00; parameter BLACK_G = 8'h00; parameter BLACK_B = 8'h00; reg hs_reg; //horizontal sync register reg vs_reg; //vertical sync register reg hs_reg_d0; //delay 1 clock of 'hs_reg' reg vs_reg_d0; //delay 1 clock of 'vs_reg' reg[11:0] h_cnt; //horizontal counter reg[11:0] v_cnt; //vertical counter reg[11:0] active_x; //video x position reg[11:0] active_y; //video y position reg[7:0] rgb_r_reg; //video red data register reg[7:0] rgb_g_reg; //video green data register reg[7:0] rgb_b_reg; //video blue data register reg h_active; //horizontal video active reg v_active; //vertical video active wire video_active; //video active(horizontal active and vertical active) reg video_active_d0; //delay 1 clock of video_active assign hs = hs_reg_d0; assign vs = vs_reg_d0; assign video_active = h_active & v_active; assign de = video_active_d0; assign rgb_r = rgb_r_reg; assign rgb_g = rgb_g_reg; assign rgb_b = rgb_b_reg; always@(posedge clk or posedge rst) begin if(rst == 1'b1) begin hs_reg_d0 <= 1'b0; vs_reg_d0 <= 1'b0; video_active_d0 <= 1'b0; end else begin hs_reg_d0 <= hs_reg; vs_reg_d0 <= vs_reg; video_active_d0 <= video_active; end end always@(posedge clk or posedge rst) begin if(rst == 1'b1) h_cnt <= 12'd0; else if(h_cnt == H_TOTAL - 1)//horizontal counter maximum value h_cnt <= 12'd0; else h_cnt <= h_cnt + 12'd1; end always@(posedge clk or posedge rst) begin if(rst == 1'b1) active_x <= 12'd0; else if(h_cnt >= H_FP + H_SYNC + H_BP - 1)//horizontal video active active_x <= h_cnt - (H_FP[11:0] + H_SYNC[11:0] + H_BP[11:0] - 12'd1); else active_x <= active_x; end always@(posedge clk or posedge rst) begin if(rst == 1'b1) v_cnt <= 12'd0; else if(h_cnt == H_FP - 1)//horizontal sync time if(v_cnt == V_TOTAL - 1)//vertical counter maximum value v_cnt <= 12'd0; else v_cnt <= v_cnt + 12'd1; else v_cnt <= v_cnt; end always@(posedge clk or posedge rst) begin if(rst == 1'b1) hs_reg <= 1'b0; else if(h_cnt == H_FP - 1)//horizontal sync begin hs_reg <= HS_POL; else if(h_cnt == H_FP + H_SYNC - 1)//horizontal sync end hs_reg <= ~hs_reg; else hs_reg <= hs_reg; end always@(posedge clk or posedge rst) begin if(rst == 1'b1) h_active <= 1'b0; else if(h_cnt == H_FP + H_SYNC + H_BP - 1)//horizontal active begin h_active <= 1'b1; else if(h_cnt == H_TOTAL - 1)//horizontal active end h_active <= 1'b0; else h_active <= h_active; end always@(posedge clk or posedge rst) begin if(rst == 1'b1) vs_reg <= 1'd0; else if((v_cnt == V_FP - 1) && (h_cnt == H_FP - 1))//vertical sync begin vs_reg <= HS_POL; else if((v_cnt == V_FP + V_SYNC - 1) && (h_cnt == H_FP - 1))//vertical sync end vs_reg <= ~vs_reg; else vs_reg <= vs_reg; end always@(posedge clk or posedge rst) begin if(rst == 1'b1) v_active <= 1'd0; else if((v_cnt == V_FP + V_SYNC + V_BP - 1) && (h_cnt == H_FP - 1))//vertical active begin v_active <= 1'b1; else if((v_cnt == V_TOTAL - 1) && (h_cnt == H_FP - 1)) //vertical active end v_active <= 1'b0; else v_active <= v_active; end always@(posedge clk or posedge rst) begin if(rst == 1'b1) begin rgb_r_reg <= 8'h00; rgb_g_reg <= 8'h00; rgb_b_reg <= 8'h00; end else if(video_active) if(active_x == 12'd0) begin rgb_r_reg <= WHITE_R; rgb_g_reg <= WHITE_G; rgb_b_reg <= WHITE_B; end else if(active_x == (H_ACTIVE/8) * 1) begin rgb_r_reg <= YELLOW_R; rgb_g_reg <= YELLOW_G; rgb_b_reg <= YELLOW_B; end else if(active_x == (H_ACTIVE/8) * 2) begin rgb_r_reg <= CYAN_R; rgb_g_reg <= CYAN_G; rgb_b_reg <= CYAN_B; end else if(active_x == (H_ACTIVE/8) * 3) begin rgb_r_reg <= GREEN_R; rgb_g_reg <= GREEN_G; rgb_b_reg <= GREEN_B; end else if(active_x == (H_ACTIVE/8) * 4) begin rgb_r_reg <= MAGENTA_R; rgb_g_reg <= MAGENTA_G; rgb_b_reg <= MAGENTA_B; end else if(active_x == (H_ACTIVE/8) * 5) begin rgb_r_reg <= RED_R; rgb_g_reg <= RED_G; rgb_b_reg <= RED_B; end else if(active_x == (H_ACTIVE/8) * 6) begin rgb_r_reg <= BLUE_R; rgb_g_reg <= BLUE_G; rgb_b_reg <= BLUE_B; end else if(active_x == (H_ACTIVE/8) * 7) begin rgb_r_reg <= BLACK_R; rgb_g_reg <= BLACK_G; rgb_b_reg <= BLACK_B; end else begin rgb_r_reg <= rgb_r_reg; rgb_g_reg <= rgb_g_reg; rgb_b_reg <= rgb_b_reg; end else begin rgb_r_reg <= 8'h00; rgb_g_reg <= 8'h00; rgb_b_reg <= 8'h00; end end endmodule
2)创建一个顶层模块,分别调用时钟、RGB2DVI、以及彩条纹显示模块(top.v)
module top( input clk, output[2:0] TMDS_DATA_p, output[2:0] TMDS_DATA_n, output TMDS_CLK_p, output TMDS_CLK_n ); wire clk_40m; wire clk_200m; clk_wiz_0 u2( .clk_in1(clk), .clk_out1(clk_40m), .clk_out2(clk_200m) ); wire VGA_HS,VGA_VS,VGA_DE; wire[7:0] R,G,B; color_bar u4( .clk(clk_40m), //pixel clock .rst(1'b0), //reset signal high active .hs(VGA_HS), //horizontal synchronization .vs(VGA_VS), //vertical synchronization .de(VGA_DE), //video valid .rgb_r(R), //video red data .rgb_g(G), //video green data .rgb_b(B) //video blue data ); rgb2dvi_0 u1( .aRst_n(1'b1), .SerialClk(clk_200m), .PixelClk(clk_40m), .TMDS_Clk_p(TMDS_CLK_p), .TMDS_Clk_n(TMDS_CLK_n), .TMDS_Data_p(TMDS_DATA_p), .TMDS_Data_n(TMDS_DATA_n), .vid_pData({R,B,G}), .vid_pHSync(VGA_HS), .vid_pVSync(VGA_VS), .vid_pVDE(VGA_DE) ); endmodule
最后再增加约束文件 (HDMI_TEST.XDC) 因V1.3版本硬件调整了HDMI CLK 引脚,所以请根据实际板子的版本调用对应的约束
#V1.2 or Earlier
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property PACKAGE_PIN M21 [get_ports {TMDS_DATA_p[0]}]
set_property PACKAGE_PIN L21 [get_ports {TMDS_DATA_p[1]}]
set_property PACKAGE_PIN J21 [get_ports {TMDS_DATA_p[2]}]
set_property PACKAGE_PIN N22 [get_ports TMDS_CLK_p]
set_property PACKAGE_PIN M19 [get_ports clk]
#V1.3
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property PACKAGE_PIN M21 [get_ports {TMDS_DATA_p[0]}]
set_property PACKAGE_PIN L21 [get_ports {TMDS_DATA_p[1]}]
set_property PACKAGE_PIN J21 [get_ports {TMDS_DATA_p[2]}]
set_property PACKAGE_PIN N22 [get_ports TMDS_CLK_p]
set_property PACKAGE_PIN N19 [get_ports clk]
6.编译综合,并运行代码
最终显示效果如图
完整工程下载: (压缩包中包含了两个版本的工程,请根据自己的主板版本号进行选择)
以上代码实现 800X600的 60hz HDMI 稳定输出
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-30-1024x766.png)
章节内容补, 增加always方式来实现分频,将分辨率提高到1080P 60HZ (只测试了部分显示器,这边测试稳定输出,不代表全部显示器都能兼容,请自行尝试)
经过测试本例程通过修改时钟可以 实现低到640X480 60HZ 高到 1080P 60HZ (60HZ 在我自己的三星曲面屏上可以实可以稳定输出)
为了实现不同分辨率,用PLL 来输出两个5倍关系的时钟并不一定高度同步,这边可以换个思路,用PLL 生成5倍频时钟,然后用always块来分频生成像素时钟
具体如下
reg PIX_CLK; wire PIX_CLK_5X; clk_wiz_0 u2( .clk_in1(clk), .clk_out1(PIX_CLK_5X) ); reg [2:0]count; always@(posedge PIX_CLK_5X)begin if(count>=3'd4)begin PIX_CLK<=1'b1;count<=3'd0;end else begin PIX_CLK<=1'b0; count<=count+1'b1;end end
通过上述方式 PLL 生成一个5倍的像素时钟 PIX_CLK_5X的时钟,而经过always块的5拍分频,生成了个单倍的时钟PIX_CLK,通过这种方式,受PLL的限制比较小。
主分辨率设置成742.5M(148.5M的5倍)实际PLL倍频出的结果是742.188M 稍有区别,这里可能会导致部分显示器不兼容
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-31.png)
下面是 1080P 60HZ 的例子,用了上述时钟生成的方法(备注,因为1080P情况下mmcm的时钟已经超过mmcm IP设置的最大推荐值了,增加不同的显示功能时对逻辑的时序约束要求比较高,所以增加自己的功能可能会出现不可预料的结果,建议大家自行尝试,如果是学习调试,可以先从720P的代码入手)
![](http://www.hellofpga.com/wp-content/uploads/2022/10/image-32.png)
以下是 1080P 60HZ的完整代码 :
备注 :如遇到自己的工程显示不稳定的情况(1080P情况下),可尝试在时序约束向导Timing Constraints Wizard 里对单倍图像时钟进行约束(在Generated Clocks页面下),设置成5分频即可(1/5),之后再重新生成bit
![](http://www.hellofpga.com/wp-content/uploads/2023/04/image-52-1024x461.png)