【ZYNQ入门】第四篇、HDMI发送端的构成原理

本文介绍了HDMI相关知识,包括含义、结构和发送端组成。详细阐述了硬件部分,如1920*1080@60Hz的VGA控制模块、encode编码模块、SelectIO Interface Wizard IP模块及管脚约束等,还提及PLL时钟问题及解决办法,最后进行总结并给出工程文件相关说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

第一部分、关于HDMI的知识

1、HDMI的含义

2、HDMI的结构

3、HDMI发送端的组成

第二部分、硬件部分

1、1920*1080@60Hz的VGA控制模块

1.1、VGA的输入时钟 

1.2、VGA控制器的代码

2、encode编码模块

2.1、编码目的

2.2、连接原理

2.3、encode代码        

3、SelectIO Interface Wizard IP模块

3.1、IP说明

3.2、IP调用方法

3.3、IP构成原理

3.4、OSERDESE2级联原理        

3.5、PLL时钟的问题

3.5.1、PLL报错

3.5.2、问题的原因

3.5.3、解决办法

3.6、OSERDESE2原语例化模板的位置

3.7、IP仿真验证

4、管脚约束

第三部分、总结

1、写在最后

2、工程文件


第一部分、关于HDMI的知识

1、HDMI的含义

  高清多媒体接口(High Definition Multimedia Interface,HDMI),是一种全数字化视频和声音发送接口,可以发送音频及视频信号

        HDMI向下兼容DVI(Digital Visual Interface,数字视频接口),DVI只能传输视频信号

2、HDMI的结构

        HDMI分为发送端和接收端,一般情况下,我们只用对接HDMI的发送端即可

3、HDMI发送端的组成

        如下图就是HDMI发送端模块的组成,发送端的程序执行流程如下:

首先,提供24bit的图片或者图像数据源(一般是从DDR内读取)。

随后,由VGA控制器(1920*1080@60Hz)提供RGB三原色以及行场同步信号

接着,由encode编码模块对RGB数据进行8bit转10bit的编码。目的:保证直流平衡,把 8 比特的数据从新映射为 10bit 数据, 防止连续的 0 和 1 出现导致 直流不平衡造成误码率升高。

其次,由OSERDESE2原语将编码后的10bit并行数据转换为单bit的串行数据。注意:串行时钟为并行时钟的10倍,但是为了降低时钟,采用双沿传输数据,因此串行时钟为并行时钟的5倍。

最后,再将单端的串行的单bit数据进行差分,调用原语OBUFDS 将串行的数据转成差分信号。

第二部分、硬件部分

        hdmi发送端部分的bolck design设计原理图如下

1、1920*1080@60Hz的VGA控制模块

        VGA控制器直接和AXI HP读模块结合,直接将AXI HP读出来的32bit数据处理为24bit(舍弃最高位的0)的三通道数据,R占八位,G占八位,B占八位。

1.1、VGA的输入时钟 

        1920*1080@60Hz的所需时钟频率为 148.5MHz。关于计算原理可以参考这篇文章:【FPGA入门】第七篇、FPGA实现VGA接口驱动_vga驱动-优快云博客

        计算过程如下

              2000*1105*60 = 2200*1125*60 = 148,500,000Hz = 148.5MHz

        VESA 视频显示标准 VGA HDMI 不同分辨率刷新率对应的时钟频率和行列时序参数参考:

VESA-DMT-1.12.pdf (glenwing.github.io)

1.2、VGA控制器的代码

module	VGA_TIMING(
	input	wire		sclk,
	input	wire		rst_n,
	output	reg	[7:0]	po_vga_r,
	output	reg	[7:0]	po_vga_g,
	output	reg	[7:0]	po_vga_b,
	output	reg		po_de,
	output	reg		po_v_sync,
	output	reg		po_h_sync,
	input	wire	[31:0]	rgb_pixel,
	output	wire		rd_fifo_en
);
parameter	H_SYNC_TIME	=44;
parameter	H_BACK_PORCH	=148;
parameter	H_LEFT_BORDER	=0;
parameter	H_ACT_START	=H_SYNC_TIME + H_BACK_PORCH + H_LEFT_BORDER;
parameter	H_ACTIVE_TIME	=1920;
parameter	H_ACT_END	=H_ACT_START + H_ACTIVE_TIME;
parameter	H_TOTAL_TIME	=2200;
parameter	V_TOTAL_TIME	=1125;
parameter	V_SYNC_TIME	=5;
parameter	V_BACK_PORCH	=36;
parameter	V_TOP_BORDER	=0;
parameter	V_ACT_START	=V_SYNC_TIME + V_BACK_PORCH + V_TOP_BORDER;
parameter	V_ACTIVE_TIME	=1080;
parameter	V_ACT_END	=V_ACT_START + V_ACTIVE_TIME;


reg	[11:0]	hor_cnt			= 12'd0;//水平方向像素计数器
reg	[11:0]	ver_cnt 		= 12'd0;//垂直方向行计数器
reg		hor_end;
reg		hor_end_t;
reg		ver_end;
reg		h_sync_start_flag;
reg		h_sync_end_flag;
reg		h_active_flag;
reg	[11:0]	h_act_num;
reg		v_sync_start_flag;
reg		v_sync_end_flag;
reg		v_active_flag;
reg	[11:0]	v_act_num;

always	@(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		hor_cnt <= 'd0;
	else if(hor_end == 1'b1)
		hor_cnt <= 'd0;
	else 
		hor_cnt <= hor_cnt + 1'b1;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		hor_end <= 1'b0;
	else if(hor_cnt == H_TOTAL_TIME-2)
		hor_end <= 1'b1;
	else 
		hor_end <= 1'b0;
always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		hor_end_t<= 1'b0;
	else 
		hor_end_t<= hor_end;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		ver_cnt <= 'd0;
	else if(ver_end == 1'b1) //&& hor_end_t == 1'b1)
		ver_cnt <= 'd0;
	else  if(hor_end == 1'b1)
		ver_cnt <= ver_cnt + 1'b1;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		ver_end <= 1'b0;
	else if(ver_cnt == V_TOTAL_TIME -1 && hor_cnt == H_TOTAL_TIME-2)
		ver_end <= 1'b1;
	else
		ver_end <= 1'b0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		h_sync_start_flag <= 1'b0;
	else if(hor_cnt == 'd0)
		h_sync_start_flag <= 1'b1;
	else
		h_sync_start_flag <= 1'b0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		h_sync_end_flag <= 1'b0;
	else if(hor_cnt == H_SYNC_TIME)
		h_sync_end_flag <= 1'b1;
	else 
		h_sync_end_flag <= 1'b0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		h_active_flag <= 1'b0;
	else if(hor_cnt == H_ACT_START)
		h_active_flag <= 1'b1;
	else if(hor_cnt == H_ACT_END)
		h_active_flag <= 1'b0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		po_h_sync <= 1'b1;
	else if(h_sync_start_flag == 1'b1)
		po_h_sync <= 1'b1;
	else if(h_sync_end_flag == 1'b1)
		po_h_sync <= 1'b0;

always @*
	if(h_active_flag == 1'b1)
		h_act_num <= hor_cnt - H_ACTIVE_TIME;
	else
		h_act_num <='d0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		v_sync_start_flag <= 1'b0;
	else if(ver_cnt == V_TOTAL_TIME -1 && hor_end_t == 1'b1)
		v_sync_start_flag <= 1'b1;
	else if(ver_cnt == 'd0 && hor_cnt == 'd0)
		v_sync_start_flag <= 1'b1;
	else 
		v_sync_start_flag <= 1'b0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		v_sync_end_flag <= 1'b0;
	else if(ver_cnt == V_SYNC_TIME -1 && hor_end_t == 1'b1)
		v_sync_end_flag <= 1'b1;
	else 
		v_sync_end_flag <= 1'b0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		v_active_flag <= 1'b0;
	else if(ver_cnt == V_ACT_START -1 && hor_end == 1'b1)
		v_active_flag <= 1'b1;
	else if(ver_cnt == V_ACT_END -1 && hor_end == 1'b1)
		v_active_flag <= 1'b0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		po_v_sync <= 1'b1;
	else if(v_sync_start_flag == 1'b1)
		po_v_sync <= 1'b1;
	else if(v_sync_end_flag == 1'b1)
		po_v_sync <= 1'b0;

always @*
	if(v_active_flag == 1'b1 && h_active_flag == 1'b1)
		v_act_num <= ver_cnt - V_ACT_START;
	else 
		v_act_num <= 'd0;

always @(posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)
		po_de <=1'b0;
	else if(h_active_flag == 1'b1 && v_active_flag == 1'b1)
		po_de <= 1'b1;
	else
		po_de <= 1'b0;

assign	rd_fifo_en = h_active_flag & v_active_flag ;

always @ (posedge sclk or negedge rst_n)
	if(rst_n == 1'b0)begin
		po_vga_r <= 'd0;
		po_vga_g <= 'd0;
		po_vga_b <= 'd0;
	end
	else if(h_active_flag == 1'b1 && v_active_flag == 1'b1) begin
		po_vga_r <=rgb_pixel[23:16];
		po_vga_g <=rgb_pixel[15:8];
		po_vga_b <=rgb_pixel[7:0]; 
	end
	else begin
		po_vga_r <= 'd0;
		po_vga_g <= 'd0;
		po_vga_b <= 'd0;
	end
endmodule

2、encode编码模块

2.1、编码目的

        将八位的RGB三通道数据转换为10bit编码数据。

        编码目的:保证直流平衡,把 8 比特的数据从新映射为 10bit 数据, 防止连续的 0 和 1 出现导致直流不平衡造成误码率升高。

2.2、连接原理

        hdmi的连接架构如下,参考《高清晰度多媒体接口规范 1.4》。

        注意:只有blue通道的编码模块c0和c1需要连接,hsync连接c0,vsync连接c1。其它两个通道的c0和c1输入0。

2.3、encode代码        

`timescale 1 ps / 1ps

module encode (
  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 <= din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];

    din_q <= 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  <= 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  <= 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    <= de;
    de_reg  <= de_q;
    
    c0_q    <= c0;
    c0_reg  <= c0_q;
    c1_q    <= c1;
    c1_reg  <= c1_q;

    q_m_reg <= 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]   <= ~q_m_reg[8]; 
          dout[8]   <= q_m_reg[8]; 
          dout[7:0] <= (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'b1;
            dout[8]   <= q_m_reg[8];
            dout[7:0] <= ~q_m_reg[7:0];

            cnt <=#1 cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);
          end else begin
            dout[9]   <= 1'b0;
            dout[8]   <= q_m_reg[8];
            dout[7:0] <= q_m_reg[7:0];

            cnt <= cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);
          end
        end
      end else begin
        case ({c1_reg, c0_reg})
          2'b00:   dout <= CTRLTOKEN0;
          2'b01:   dout <= CTRLTOKEN1;
          2'b10:   dout <= CTRLTOKEN2;
          default: dout <= CTRLTOKEN3;
        endcase

        cnt <= 5'h0;
      end
    end
  end
  
endmodule

3、SelectIO Interface Wizard IP模块

3.1、IP说明

        这个IP实际上是由原语OSERDESR2并转串模块OBUFDS单端转差分模块构成的。也就是这个IP包含了这两种原语。

        大家可以参考这个博主的这篇博客,写的很详细:Xilinx中oserdes的原语及IP的使用

3.2、IP调用方法

第一步、配置第一个界面,输入/输出数据位宽,电平标准等

第二步、配置第二个界面

第三步、配置第三个界面,保持默认

 第四步、最后一个界面

        注意:clk_in端口输入的是高速时钟(5倍并行时钟),clk_div_in为慢速的并行时钟。

3.3、IP构成原理

        在IP生成好之后,实际的代码如下

`timescale 1ps/1ps

module design_1_selectio_wiz_0_3_selectio_wiz
   // width of the data for the system
 #(parameter SYS_W = 1,
   // width of the data for the device
   parameter DEV_W = 10)
 (
  // From the device out to the system
  input  [DEV_W-1:0] data_out_from_device,
  output [SYS_W-1:0] data_out_to_pins_p,
  output [SYS_W-1:0] data_out_to_pins_n,
  input              clk_in,        // Fast clock input from PLL/MMCM
  input              clk_div_in,    // Slow clock input from PLL/MMCM
  input              io_reset);
  localparam         num_serial_bits = DEV_W/SYS_W;
  wire clock_enable = 1'b1;
  // Signal declarations
  ------------------------------
  // Before the buffer
  wire   [SYS_W-1:0] data_out_to_pins_int;
  // Between the delay and serdes
  wire   [SYS_W-1:0] data_out_to_pins_predelay;
  // Array to use intermediately from the serdes to the internal
  //  devices. bus "0" is the leftmost bus
  wire [SYS_W-1:0]  oserdes_d[0:13];   // fills in starting with 13
  // Create the clock logic


  // We have multiple bits- step over every bit, instantiating the required elements
  genvar pin_count;
  genvar slice_count;
  generate for (pin_count = 0; pin_count < SYS_W; pin_count = pin_count + 1) begin: pins
    // Instantiate the buffers
    ------------------------------
    // Instantiate a buffer for every bit of the data bus
    OBUFDS
      #(.IOSTANDARD ("TMDS_33"))
     obufds_inst
       (.O          (data_out_to_pins_p  [pin_count]),
        .OB         (data_out_to_pins_n  [pin_count]),
        .I          (data_out_to_pins_int[pin_count]));

    // Pass through the delay
    -------------------------------
   assign data_out_to_pins_int[pin_count]    = data_out_to_pins_predelay[pin_count];
 
     // Instantiate the serdes primitive
     ------------------------------

     wire [SYS_W-1:0]  ocascade_sm_d;
     wire [SYS_W-1:0]  ocascade_sm_t;
     // declare the oserdes
     OSERDESE2
       # (
         .DATA_RATE_OQ   ("DDR"),
         .DATA_RATE_TQ   ("SDR"),
         .DATA_WIDTH     (10),
         .TRISTATE_WIDTH (1),
         .SERDES_MODE    ("MASTER"))
       oserdese2_master (
         .D1             (oserdes_d[13][pin_count]),
         .D2             (oserdes_d[12][pin_count]),
         .D3             (oserdes_d[11][pin_count]),
         .D4             (oserdes_d[10][pin_count]),
         .D5             (oserdes_d[9][pin_count]),
         .D6             (oserdes_d[8][pin_count]),
         .D7             (oserdes_d[7][pin_count]),
         .D8             (oserdes_d[6][pin_count]),
         .T1             (1'b0),
         .T2             (1'b0),
         .T3             (1'b0),
         .T4             (1'b0),
         .SHIFTIN1       (ocascade_sm_d[pin_count]),
         .SHIFTIN2       (ocascade_sm_t[pin_count]),
         .SHIFTOUT1      (),
         .SHIFTOUT2      (),
         .OCE            (clock_enable),
         .CLK            (clk_in),
         .CLKDIV         (clk_div_in),
         .OQ             (data_out_to_pins_predelay[pin_count]),
         .TQ             (),
         .OFB            (),
         .TFB            (),
         .TBYTEIN        (1'b0),
         .TBYTEOUT       (),
         .TCE            (1'b0),
         .RST            (io_reset));

     OSERDESE2
     #  (
         .DATA_RATE_OQ   ("DDR"),
         .DATA_RATE_TQ   ("SDR"),
         .DATA_WIDTH     (10),
         .TRISTATE_WIDTH (1),
         .SERDES_MODE    ("SLAVE"))
       oserdese2_slave (
         .D1             (1'b0), 
         .D2             (1'b0),
         .D3             (oserdes_d[5][pin_count]),
         .D4             (oserdes_d[4][pin_count]),
         .D5             (oserdes_d[3][pin_count]),
         .D6             (oserdes_d[2][pin_count]),
         .D7             (oserdes_d[1][pin_count]),
         .D8             (oserdes_d[0][pin_count]),
         .T1             (1'b0),
         .T2             (1'b0),
         .T3             (1'b0),
         .T4             (1'b0),
         .SHIFTOUT1       (ocascade_sm_d[pin_count]),
         .SHIFTOUT2       (ocascade_sm_t[pin_count]),
         .SHIFTIN1        (1'b0),
         .SHIFTIN2        (1'b0),
         .OCE            (clock_enable),
         .CLK            (clk_in),
         .CLKDIV         (clk_div_in),
         .OQ             (), //data_out_to_pins_predelay[pin_count]),
         .TQ             (),
         .OFB            (),
         .TFB            (),
         .TBYTEIN        (1'b0),
         .TBYTEOUT       (),
         .TCE            (1'b0),
         .RST            (io_reset));
     // Concatenate the serdes outputs together. Keep the timesliced
     //   bits together, and placing the earliest bits on the right
     //   ie, if data comes in 0, 1, 2, 3, 4, 5, 6, 7, ...
     //       the output will be 3210, 7654, ...
     ---------------------------------------------------------
     for (slice_count = 0; slice_count < num_serial_bits; slice_count = slice_count + 1) begin: out_slices
        // This places the first data in time on the right
        assign oserdes_d[14-slice_count-1] =
           data_out_from_device[slice_count];
        // To place the first data in time on the left, use the
        //   following code, instead
        // assign oserdes_d[slice_count] =
        //    data_out_from_device[slice_count];
     end
  end
  endgenerate

endmodule

3.4、OSERDESE2级联原理        

        原语中的OSERDESE2单个模块只支持8:1的并转串,不支持10:1的并转串。

        因此这里需要拓展的并转串,因此需要级联一个OSERDESE2模块。结构图如下:

3.5、PLL时钟的问题

        并转串为10:1的时候,那么需要准备两个时钟,一个时钟为并行数据的时钟,另一个是串行数据时钟(正常情况下为并行时钟的10倍,但是频率太高。系统内默认双沿采样,因此时钟变为原来的5倍)。

        因此串行时钟 = 148.5MHz * 5 = 742.5MHz。

        而PLL在产生742.5MHz的时钟会爆红,因为超出了最高时钟频率范围,如下图所示。

        但是可以正常使用上,因为是给出去的管脚时钟,所以没有关系。

3.5.1、PLL报错

其次,我的编译器在生成bit流文件的时候,PLL会报错如下

3.5.2、问题的原因

        当把FPGA中的时钟(比如DCM和PLL输出的时钟)输出到普通I/O口时,不能直接输出,要经过BUFGODDR2,但是需要注意的是如果我们调用的是IP核那么不需要调用BUFG的原语,因为IP核已经包括了BUFG,如果这个IP没有包含BUFG,那就需要BUFG。

        感觉这玩意就是让时钟更稳定。

        具体可参考这篇博客:FPGA-全局时钟缓冲IBUFG BUFG IBUFGDS ODDR2

3.5.3、解决办法

        将PLL的时钟源改为Global buffer全局时钟缓冲。

3.6、OSERDESE2原语例化模板的位置

        OSERDESE2原语也可以单独例化,其中例化的模板路径如下图

3.7、IP仿真验证

        验证这个IP,如何把串行10bit11111_00000,变为差分输出的时钟,因为分辨率为1920*1080@60Hz的VGA的时钟为178.5MHz,因此串行10bit11111_00000形成的时钟也是178.5MHz。

        仿真代码如下

`timescale 1ns / 1ps

module sim;
reg clk;
reg rst;
reg [9:0] din;
wire do_p;
wire do_n;

initial begin
    clk = 0;
    rst = 1;
    din = 10'b0;
    #100;
    rst = 0;
    din = 10'b11111_00000;
end

always #5 clk = ~clk;

design_1_selectio_wiz_0_3_selectio_wiz inst_test (
    .clk(clk), 
    .rst(rst), 
    .din(din),
    .do_p(do_p), 
    .do_n(do_n)
     );


endmodule

        仿真波形如下

        因为oserdese2的串行时钟是并行时钟的5倍,10'b11111_00000在串行时钟下就变成了并行时钟。

4、管脚约束

        管脚约束代码如下,关于差分端口,只需要约束一个端口,后面一个端口会自动被约束。

set_property PACKAGE_PIN E19 [get_ports HDMI_OUT_EN]
set_property IOSTANDARD LVCMOS33 [get_ports HDMI_OUT_EN]
set_property PACKAGE_PIN K17 [get_ports HDMI_CLK_P]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_CLK_P]
set_property PACKAGE_PIN F19 [get_ports HDMI_D0_P]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_D0_P]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_D1_P]
set_property PACKAGE_PIN J18 [get_ports HDMI_D1_P]
set_property PACKAGE_PIN H16 [get_ports HDMI_D2_P]
set_property IOSTANDARD TMDS_33 [get_ports HDMI_D2_P]

        其次,这里HDMI_OUT_EN, 我的开发板原理图中默认关闭的,因此在block design中要给一个常量1,保证hdmi处于工作状态。

第三部分、总结

1、写在最后

        该篇文章介绍了HDMI发送端的构成原理、接着是Block design的设计原理以及内部模块构成原理。

       主要是我调试过程中的笔记,希望能够给你提供一定的思路。

2、工程文件

        这篇文章是为了下面这篇文章的内容做铺垫,关于工程下载链接以及实验现象,请参考这篇文章【ZYNQ实验】第一篇、ZYNQ驱动HDMI显示图片-优快云博客

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大屁桃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值