解决了配置初始化的问题,接下来是进行最后的4k读写环路测试。
HDMI端口检测
首先看看HDMI线能否被正确检测到:由于ADV7619芯片允许两路HDMI输入端口,因此我们可以通过i2c寄存器的值,判断哪个端口有接入HDMI,检测结果如下:
移植硬件部分代码
硬件部分有另一个问题,是原来的工程使用了一些Quartus的IP,包括fifo、pll和ddio,其中:
- pll是用于产生两个参考时钟,一个与输入时钟同频,另一个频率x2,同时有130度的相移
- fifo用于跨时钟域的异步数据传输(输入HDMI的时钟—输出hdmi的时钟)
- 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.pdf 和 altddio.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它。
但是这中做法会带来一个跨时钟域的问题:
- 在这段程序中,我的FIFO_WRITE_EN是由控制系统发出的,使用的时钟源头是板上200M的差分时钟信号
- 而我的FIFO的读写信号则是由输入的时钟RX_PCLK经过MMCM产生的时钟,因此源头是RX_PCLK,频率是148.5MHz
因此,这两个信号属于异步信号,在正常情况下,要考虑亚稳态产生的可能性。
但是,由于我的初始化只在开始时刻进行一次,因此这种异步信号也只会出现一次,对整个设计功能的影响很小,因此我认为在这种情况可以不用采取额外的处理。
但是!!
EDA软件可不会这么认为,在综合和布线的过程中,它会进行静态时序分析,而这个地方由于是源时钟和目的时钟是异步的,因此会被软件检测出来并报错,我们的策略就是让软件跳过这条路径的检查:set_false_path
上板结果
最后展示一下我的上板结果:
首先是资源使用,使用比较多的就是IO、BUFG、MMCM这三项资源,具体为什么多,在哪个部分使用的前面也已经阐述过了
然后是时序分析:
看起来也没有大问题,还有一定的裕量
资源的分布情况如下所示:
其中高亮(天蓝色)部分为使用的器件,基本集中在X1Y2和X1Y1这两个区域,另外X0Y6区域是RX_PCLK的输入,使用了一个MMCM,X1Y5区域是系统时钟的输入,使用了另一个MMCM。
上面是上板的结果,可以看到分辨率和画面显示是正确的,成功显示原色画面,并且达到了4k的分辨率