FC2カウンター FPGAの部屋 2019年07月26日
fc2ブログ

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

Ultra96 MIPI拡張ボードに接続したPcam5C の画像をDisplayPort に表示する10(CDCでUnsafeにならない記述)

Ultra96 MIPI拡張ボードに接続したPcam5C の画像をDisplayPort に表示する9(DisplayPort のクロック制約を追加)”の続き。

前回は、DisplayPort 用のクロックのdp_video_ref_clk の制約を追加していなかったので、追加した。これで十分だとは思うが、今回は、CDC でUnsafe を 0 にすることを目標にaxis2video_out.v の Verilog HDL コードを修正してみよう。

シンクロナイザの前に組み合わせ回路を置くと、CDC でUnsafe 判定されてしまう。つまり、組み合わせ回路に入力されるすべての信号を後段のシンクロナイザで使用するクロックに同期しておけば良いのではないだろうか?
そこで、axis2video_out_v1 IP のaxis2video_out.v を以下のように修正した。
MIPI_DP_69_190724.png

MIPI_DP_70_190724.png

axis2video_out.v を示す。

// axis2video_out.v
// 2019/01/14 by marsee
// 2019/01/23 : Fixed IP start state machine bug
// 2019/07/25 : Added (* ASYNC_REG = "TRUE" *) 
//

`default_nettype none

module axis2video_out
    (
        // Clock and Reset
        input wire  disp_clk,
        input wire  axi_clk,
        input wire  axi_rst_n,
        input wire  init_done,
        
        // AXI4-Stream
        input wire  [31:0] axis_tdata,
        input wire  axis_tvalid,
        output wire axis_tready,
        input wire  [3:0] axis_tkeep,
        input wire  [3:0] axis_tstrb,
        input wire  axis_tuser,
        input wire  axis_tlast,
        input wire  axis_tid,
        input wire  axis_tdest,
        
        // IP
        output reg  ip_start,
        input wire  ip_done,
        
        // video in
        input wire  de_in,
        input wire  vsync_in,
        input wire  hsync_in,
        
        // video_out
        output wire  [35:0] disp_pixel,
        output wire  de_out,
        output wire vsync_out,
        output wire  hsync_out
    );

    parameter       IDLE_START =    1'b0,
                    IP_START_1 =    1'b1;
    
    reg reset_disp_2b = 1'b1, reset_disp_1b = 1'b1;
    wire    reset_disp;
    reg fifo_reset_axi_2b = 1'b0, fifo_reset_axi_1b = 1'b0;
    wire    fifo_reset_axi;
    reg fifo_reset_disp_2b = 1'b0, fifo_reset_disp_1b = 1'b0;
    wire    fifo_reset_disp;
    reg     de_1d, vsync_1d, hsync_1d;
    (* ASYNC_REG = "TRUE" *) reg vsync_axi_1b, vsync_axi_2b;
    wire    vsync_axi;
    (* ASYNC_REG = "TRUE" *) reg vsync_axi_1d, vsync_axi_2d;
    reg     cs_start;
    wire    pfifo_empty, pfifo_full;
    wire [33:0] pfifo_dout;
    reg    vsync_rising_edge_axi, vsync_falling_edge_axi;
    (* ASYNC_REG = "TRUE" *) reg init_done_axi_1d, axi_rst_n_axi_1d;
    (* ASYNC_REG = "TRUE" *) reg init_done_axi_2d, axi_rst_n_axi_2d;
    (* ASYNC_REG = "TRUE" *) reg init_done_disp_1d, axi_rst_n_disp_1d;
    (* ASYNC_REG = "TRUE" *) reg init_done_disp_2d, axi_rst_n_disp_2d;
    
    always @(posedge disp_clk) begin
        if(reset_disp) begin
            de_1d <= 1'b0;
            vsync_1d <= 1'b0;
            hsync_1d <= 1'b0;
        end else begin
            de_1d <= de_in;
            vsync_1d <= vsync_in;
            hsync_1d <= hsync_in;
        end
    end
    always @(posedge axi_clk) begin
        init_done_axi_1d <= init_done;
        init_done_axi_2d <= init_done_axi_1d;
        axi_rst_n_axi_1d <= axi_rst_n;
        axi_rst_n_axi_2d <= axi_rst_n_axi_1d;
    end
    always @(posedge disp_clk) begin
        init_done_disp_1d <= init_done;
        init_done_disp_2d <= init_done_disp_1d;
        axi_rst_n_disp_1d <= axi_rst_n;
        axi_rst_n_disp_2d <= axi_rst_n_disp_1d;
    end
    
    // reset signals    
    always @(posedge axi_clk) begin
        fifo_reset_axi_2b <= ~init_done_axi_2d | ~axi_rst_n_axi_2d | vsync_axi;
        fifo_reset_axi_1b <= fifo_reset_axi_2b;
    end
    assign fifo_reset_axi = fifo_reset_axi_1b;
        
    always @(posedge disp_clk) begin
        fifo_reset_disp_2b <= ~init_done_disp_2d | ~axi_rst_n_disp_2d | vsync_1d;
        fifo_reset_disp_1b <= fifo_reset_disp_2b;
    end
    assign fifo_reset_disp = fifo_reset_disp_1b;
        
    always @(posedge disp_clk) begin
        reset_disp_2b <= ~init_done_disp_2d | ~axi_rst_n_disp_2d;
        reset_disp_1b <= reset_disp_2b;
    end
    assign reset_disp = reset_disp_1b;
    
    // vsync_rising_edge, vsync_falling_edge
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_axi_2b <= 1'b1;
            vsync_axi_1b <= 1'b1;
        end else begin
            vsync_axi_2b <= vsync_1d;
            vsync_axi_1b <= vsync_axi_2b;
        end
    end
    assign vsync_axi = vsync_axi_1b;
    
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_axi_1d <= 1'b1;
            vsync_axi_2d <= 1'b1;
        end else begin
            vsync_axi_1d <= vsync_axi;
            vsync_axi_2d <= vsync_axi_1d;
        end
    end

    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_rising_edge_axi = 1'b0;
            vsync_falling_edge_axi = 1'b0;
        end else begin
            vsync_rising_edge_axi <= ~vsync_axi_2d & vsync_axi_1d;
            vsync_falling_edge_axi <= vsync_axi_2d & ~vsync_axi_1d;
        end
    end
    
    // IP start State Machine
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            cs_start <= IDLE_START;
            ip_start <= 1'b0;
        end else begin
            case (cs_start)
                IDLE_START : begin
                    ip_start <= 1'b0;
                    if (vsync_falling_edge_axi) begin
                        cs_start <= IP_START_1;
                        ip_start <= 1'b1;
                    end
                end
                IP_START_1 : begin
                    if (ip_done) begin
                        cs_start <= IDLE_START;
                        ip_start <= 1'b0;
                    end
                end
            endcase
        end
    end
    
    // data width 34 bits, 512 depth
    pixel_fifo pixel_fifo_i (
        .wr_rst(fifo_reset_axi),
        .wr_clk(axi_clk),
        .rd_rst(fifo_reset_disp),
        .rd_clk(disp_clk),
        .din({axis_tuser, axis_tlast, axis_tdata}),
        .dout(pfifo_dout),
        .wr_en(~pfifo_full & axis_tvalid),
        .full(pfifo_full),
        .rd_en(de_1d),
        .empty(pfifo_empty)
    );
    assign axis_tready = ~pfifo_full;
    
    assign disp_pixel = {pfifo_dout[7:0], 4'd0, pfifo_dout[23:16], 4'd0, pfifo_dout[15:8], 4'd0}; //BRG
    assign de_out = de_1d;
    assign vsync_out = vsync_1d;
    assign hsync_out = hsync_1d;
endmodule

`default_nettype wire


このVerilog HDL コードについて解説する。
まずは、CDC のタイミングレポートの読み方は、Xilinx 社のUsers Guide の”デザイ ン解析およびクロージャテクニック UG906 (v2018.2) 2018 年 6 月 6 日”の 72 ページからの”クロック乗せ換えレポー ト”に載っているので、それを参照のこと。
そして、76 ページの”表 2‐3: CDC ルールおよび説明”で、ASYNC_REG プロパティ とあるがこれは何だろうか?と調べてみると、”Vivado プロパテ ィ リファレンス UG912 (v2018.1) 2018 年 5 月 4 日”の 141 ページの”ASYNC_REG”に書いてあった。
それによると、ASYNC_REG は、普通のFF とは様々な違いがあるそうだ。一番大きな違いと思われる部分を引用する。

ASYNC_REG では、 タイミング違反が発生しても最後の既知の値を出力するようにレジスタが変更されます。


ASYNC REG に指定するには、Verilog HDL の場合は、reg 宣言の前に” (* ASYNC_REG = "TRUE" *)”を付加する。axis2video_out.v でもシンクロナイザで使用するレジスタの reg 宣言の前に” (* ASYNC_REG = "TRUE" *)”を書いておいた。

さらに、axis2video_out.v では、組み合わせ回路に入力するすべての非同期信号(init_done, axi_rst_n)毎にシンクロナイザで同期してから、組み合わせ回路に入力するようにした。

これで、もう一度、論理合成、インプリメンテーション、ビットストリームの生成を行った。結果を示す。
MIPI_DP_73_190726.png

CDC レポートを示す。
まずは、 clk_pl_0 から dp_clik の場合のUnsafe は 0 個だった。
MIPI_DP_71_190725.png

dp_clk から clk_pl_0 の場合のUnsafe も 0 個だった。
MIPI_DP_72_190725.png

これで、Vivado のCDC レポートも公認の回路になったようだ。
  1. 2019年07月26日 04:45 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0