HDMI-FMC子卡的使用(基于VC707)(下)

HDMI-FMC子卡的使用(基于VC707)(下)


解决了配置初始化的问题,接下来是进行最后的4k读写环路测试。

HDMI端口检测

首先看看HDMI线能否被正确检测到:由于ADV7619芯片允许两路HDMI输入端口,因此我们可以通过i2c寄存器的值,判断哪个端口有接入HDMI,检测结果如下:
在这里插入图片描述

移植硬件部分代码

硬件部分有另一个问题,是原来的工程使用了一些Quartus的IP,包括fifo、pll和ddio,其中:

  1. pll是用于产生两个参考时钟,一个与输入时钟同频,另一个频率x2,同时有130度的相移
  2. fifo用于跨时钟域的异步数据传输(输入HDMI的时钟—输出hdmi的时钟)
  3. ddio是quartus中用于ddr(double data rate)输出数据的IP
fifo fifo_inst(

    .wrclk(RX_PCLK),
    .wrreq(1'b1),
    .data({RX_DE, RX_HS, RX_VS, RX_R1, RX_G1, RX_B1, RX_DE, RX_HS, RX_VS, RX_R0, RX_G0, RX_B0}),

    .rdclk(tx_clk),
    .rdreq(rdusedw >= 5'd8),
    .rdusedw(rdusedw),
    .q({rx_de_1,rx_hs_1,rx_vs_1,rx_r_1,rx_g_1,rx_b_1,
        rx_de_0,rx_hs_0,rx_vs_0,rx_r_0,rx_g_0,rx_b_0})
);

wire tx_pll_locked;
wire tx_clk;
pll tx_pll_inst (
    .refclk   (RX_PCLK),        //  refclk.clk
    .rst      (~BUTTON[0]),     //   reset.reset
    .outclk_0 (tx_clk),         // outclk0.clk
    .outclk_1 (TX_PCLK),        // outclk1.clk
    .locked   (tx_pll_locked)   //  locked.export
);

tx_ddio	tx_ddio_inst (
    .datain_h ({rx_de_0,rx_hs_0,rx_vs_0,rx_r_0,rx_g_0,rx_b_0}),
    .datain_l ({rx_de_1,rx_hs_1,rx_vs_1,rx_r_1,rx_g_1,rx_b_1}),
    .outclock (tx_clk),
    .dataout ({TX_DE, TX_HS, TX_VS, TX_RD[11: 4], TX_GD[11: 4], TX_BD[11: 4]})
);

而在vivado中,同样提供了这三种功能的IP,下面依次做简要介绍:

ODDR

前面我们提到,ddio是altera芯片中用于双倍数据速率输出的硬件资源,而在vivado中,同样有这种功能的器件,就是下面这个东西:
在这里插入图片描述
在vivado中,有专门用于ddr数据传输的原语:ODDR和IDDR,这里我们主要用的是ODDR,它包括了两种模式:
在这里插入图片描述

在这里插入图片描述
通过对比quartus和vivado 的相关文档(selectIO.pdfaltddio.pdf),发现这里应该采用 SAME_EDGE_MODE。

但是,xilinx和altera提供的oddr在使用的时候还有一点区别:xlinx的oddr例化一次只能实现一位的输出,因此对于多个端口,使用generate for代码块进行批量例化,例化部分代码如下:

ODDR #(.DDR_CLK_EDGE("SAME_EDGE"), .INIT(1'b0), .SRTYPE("SYNC")) 
    de (
    .C(tx_clk),  
    .Q(TX_HS), 
    .CE(ODDR_EN), .D1(rx_hs_0), .D2(rx_hs_1), .R(1'b0), .S(1'b0));
    
ODDR #(.DDR_CLK_EDGE("SAME_EDGE"), .INIT(1'b0), .SRTYPE("SYNC")) 
hs (
    .C(tx_clk), 
    .Q(TX_DE),  
    .CE(ODDR_EN), .D1(rx_de_0), .D2(rx_de_1), .R(1'b0), .S(1'b0));
    
ODDR #(.DDR_CLK_EDGE("SAME_EDGE"), .INIT(1'b0), .SRTYPE("SYNC")) 
vs (
    .C(tx_clk),
    .Q(TX_VS), 
    .CE(ODDR_EN), .D1(rx_vs_0), .D2(rx_vs_1), .R(1'b0), .S(1'b0));

genvar i;
generate
 for (i=0; i<8; i=i+1) begin:genblock
   ODDR #(.DDR_CLK_EDGE("SAME_EDGE"), .INIT(1'b0), .SRTYPE("SYNC")) 
   TX_RD_ddr(.C(tx_clk), .CE(ODDR_EN), .R(1'b0), .S(1'b0),
             .Q(TX_RD[i+4]), .D1(rx_r_0[i]), .D2(rx_r_1[i]));

       ODDR #(.DDR_CLK_EDGE("SAME_EDGE"), .INIT(1'b0), .SRTYPE("SYNC")) 
   TX_GD_ddr(.C(tx_clk), .CE(ODDR_EN), .R(1'b0), .S(1'b0),
             .Q(TX_GD[i+4]), .D1(rx_g_0[i]), .D2(rx_g_1[i]));

       ODDR #(.DDR_CLK_EDGE("SAME_EDGE"), .INIT(1'b0), .SRTYPE("SYNC")) 
   TX_BD_ddr(.C(tx_clk), .CE(ODDR_EN), .R(1'b0), .S(1'b0),
             .Q(TX_BD[i+4]), .D1(rx_b_0[i]), .D2(rx_b_1[i]));
 end
endgenerate

其中,前面首先例化了HDMI的VS、HS、DE信号(单比特),对于RGB三通道多比特信号,则使用generate for批量例化。要注意的是,我增加了门控信号ODDR_EN,这个信号可以被microblaze通过一个gpio进行控制,当在对ADV7619芯片进行初始化程序的时候,需要暂时disable这些ODDR门,防止信号出现异常。
ODDR门在硬件实现上是由专门的ODDR硬件实现,不会占据其他的逻辑资源,ODDR门的布局一般是固定在IO口旁边,便于布局实现。
在这里插入图片描述
如上图所示,在M41输出引脚旁边的这个高亮的小方块就是FPGA的输出逻辑门(OLOGIC),可以被使用成不同输出逻辑功能,如:
在这里插入图片描述
注意到这个器件不是必须的器件,可用可不用,对于很多普通的输出,只需要直接把寄存器或组合逻辑输出接到OBUF即可。

Clock Buf

HDMI视频输入芯片在输入图形数据时,会同步输入一个像素时钟(RX_PCLK,148.5MHz),我们要用这个时钟做一些有用的事,像对数据进行采样等处理,因此需要引入这个时钟到FPGA的内部电路。
我在这里使用的方法是,把时钟信号接入FPGA的MMCM,输出一个与该时钟同频的信号和一个2倍频信号。其中同频信号是为了让信号更加稳定,毛刺更少,倍频信号是输出芯片的要求,这里暂时忽略。经过MMCM的处理,两个时钟经过BUFG,进入全局时钟树,这样就可以被整个FPGA上的逻辑资源调用。
当然这里我认为还有优化的空间:因为FPGA的BUFG资源是比较稀缺,通过查阅资料,这里应该能使用BUFIO、BUFR、BUFH这些时钟入口替代,代价可能是作用范围和传输性能有所下降,但是鉴于目前时间有限且资源还够的情况,就先这样用着吧。

mmcm pll_hdmi_clk(
  .clk_in1(RX_PCLK), // 148.5 MHz
  .resetn(RX_PCLK_CLOCK_IN_RST_N),

  .clk_out1(tx_clk), // 148.5 MHz
  .clk_out2(TX_PCLK) // 297   MHz
  );

这里的mmcm模块同样使用了一个resetn信号,用于在对芯片初始化时暂时disable这个模块。
在这里插入图片描述
在实现的时候,vivado一般会自动分配距离输入port最近的一个MMCM资源,如上图所示,可以看到输入的信号RX_PCLK的IBUF直接接入了同个bank的MMCM,并且输出到BUFG。

FIFO

FIFO的用法应该大家都差不多,我直接贴代码:

fifo_generator_0 fifo_inst(
 .wr_clk(tx_clk),
 .wr_en(FIFO_WRITE_EN),
 .din({RX_DE, RX_HS, RX_VS, RX_R1, RX_G1, RX_B1, 
       RX_DE, RX_HS, RX_VS, RX_R0, RX_G0, RX_B0}),

 .rd_clk(tx_clk),
 .rd_en(rdusedw >= 5'd8),
 .rd_data_count(rdusedw),
 .dout({rx_de_1,rx_hs_1,rx_vs_1,rx_r_1,rx_g_1,rx_b_1,
     rx_de_0,rx_hs_0,rx_vs_0,rx_r_0,rx_g_0,rx_b_0})
 );

FIFO的写使能端口,我同样从microblaze接了一个GPIO用于控制,在芯片的初始化器件暂时disable它。
但是这中做法会带来一个跨时钟域的问题:

  1. 在这段程序中,我的FIFO_WRITE_EN是由控制系统发出的,使用的时钟源头是板上200M的差分时钟信号
  2. 而我的FIFO的读写信号则是由输入的时钟RX_PCLK经过MMCM产生的时钟,因此源头是RX_PCLK,频率是148.5MHz
    因此,这两个信号属于异步信号,在正常情况下,要考虑亚稳态产生的可能性。
    但是,由于我的初始化只在开始时刻进行一次,因此这种异步信号也只会出现一次,对整个设计功能的影响很小,因此我认为在这种情况可以不用采取额外的处理。

但是!!
EDA软件可不会这么认为,在综合和布线的过程中,它会进行静态时序分析,而这个地方由于是源时钟和目的时钟是异步的,因此会被软件检测出来并报错,我们的策略就是让软件跳过这条路径的检查:set_false_path

上板结果

最后展示一下我的上板结果:
首先是资源使用,使用比较多的就是IO、BUFG、MMCM这三项资源,具体为什么多,在哪个部分使用的前面也已经阐述过了
在这里插入图片描述
然后是时序分析:
在这里插入图片描述
看起来也没有大问题,还有一定的裕量

资源的分布情况如下所示:
在这里插入图片描述
其中高亮(天蓝色)部分为使用的器件,基本集中在X1Y2和X1Y1这两个区域,另外X0Y6区域是RX_PCLK的输入,使用了一个MMCM,X1Y5区域是系统时钟的输入,使用了另一个MMCM。
在这里插入图片描述

上面是上板的结果,可以看到分辨率和画面显示是正确的,成功显示原色画面,并且达到了4k的分辨率在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值