FC2カウンター FPGAの部屋 Ultra96のDisplayPortを使用するためのIPを作成する7(テストベンチ の作成3)
FC2ブログ

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

FPGAの部屋

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

Ultra96のDisplayPortを使用するためのIPを作成する7(テストベンチ の作成3)

Ultra96のDisplayPortを使用するためのIPを作成する6(テストベンチ の作成2)”の続き。

前回からブログが2日空いてしまった。土曜日が大切な仕事で出勤だったためもあるのだが、いろいろとプロジェクトを作って試していたが、どうしてもうまくテストバターンが表示されなかった。Vivado Analyzer を使って、実際の波形を確認したところ、アクティブローだと思っていたZynq UltraScale+ MPSoC のdp_video_out_hsync とdp_video_out_vsync がアクティブハイだということがわかった。逆の波形で実装していたので、これは表示できないというのが分かった。それを踏まえて、今回は、”Ultra96のDisplayPortを使用するためのIPを作成する6(テストベンチ の作成2)”で行ったシミュレーションをdp_video_out_hsync とdp_video_out_vsync 、dp_live_video_de_out のモデルを作って実行した。

dp_video_out_hsync とdp_video_out_vsync 、dp_live_video_de_out のモデルはVivado HLS 2018.3 を使って実装した。
なお、モデルは、最初に vsync をアサートしたほうがシミュレーションの実行時間が短くて済む。よって、最初に vsync をアサートするようにしたのだが、その際に static 変数を使用した。
ソースコードの XGA_sync_gen.cpp を示す。

// XGA_sync_gen.cpp
// 2019/01/20 by marsee
//

#include <stdio.h>
#include <string.h>
#include <ap_int.h>
#include "ap_utils.h"

// XGA 解像度 65 MHz
#define H_ACTIVE_VIDEO    1024
#define H_FRONT_PORCH    24
#define H_SYNC_PULSE    136
#define H_BACK_PORCH    160
#define H_SUM            (H_ACTIVE_VIDEO + H_FRONT_PORCH + H_SYNC_PULSE + H_BACK_PORCH)

#define V_ACTIVE_VIDEO    768
#define V_FRONT_PORCH    2
#define V_SYNC_PULSE    6
#define V_BACK_PORCH    29
#define V_SUM            (V_ACTIVE_VIDEO + V_FRONT_PORCH + V_SYNC_PULSE + V_BACK_PORCH)

void XGA_sync_gen(ap_uint<1> &de, ap_uint<1> &hsync, ap_uint<1> &vsync){
#pragma HLS INTERFACE ap_none port=vsyncx
#pragma HLS INTERFACE ap_none port=hsyncx
#pragma HLS INTERFACE ap_none port=de
#pragma HLS INTERFACE ap_ctrl_hs port=return
 ap_uint<16> h_count, v_set, v_count;
 static int flag = 0;

 if(flag == 0){
     v_set = 768;
     flag = 1;
 }else{
     v_set = 0;
 }

    for (v_count=v_set; v_count<V_SUM; v_count++){
        for (h_count=0; h_count<H_SUM; h_count++){
#pragma HLS PIPELINE II=1 rewind
            if (h_count >= (H_ACTIVE_VIDEO +H_FRONT_PORCH) && h_count < (H_ACTIVE_VIDEO + H_FRONT_PORCH + H_SYNC_PULSE))
                hsync = 1;
            else
                hsync = 0;

            if (v_count >= (V_ACTIVE_VIDEO + V_FRONT_PORCH) && v_count < (V_ACTIVE_VIDEO + V_FRONT_PORCH + V_SYNC_PULSE))
                vsync = 1;
            else
                vsync = 0;

            if (h_count < H_ACTIVE_VIDEO && v_count < V_ACTIVE_VIDEO)
                de = 1;
            else
                de = 0;
        }
    }
}


テストベンチの XGA_sync_gen_tb.cpp を示す。

// XGA_sync_gen.cpp
// 2019/01/20 by marsee
//

#include <stdio.h>
#include <string.h>
#include <ap_int.h>

void XGA_sync_gen(ap_uint<1> &de, ap_uint<1> &hsyncx, ap_uint<1> &vsyncx);

int main(){
    ap_uint<1> de;
    ap_uint<1> hsyncx;
    ap_uint<1> vsyncx;

    XGA_sync_gen(de, hsyncx, vsyncx);
    XGA_sync_gen(de, hsyncx, vsyncx);

    return 0;
}


XGA_sync_gen プロジェクトを示す。
DisplayPort_test_84_19020.png

C コードの合成を行った。結果を示す。
DisplayPort_test_85_19020.png

XGA_sync_gen.v と XGA_sync_gen_mul_mul_10ns_12ns_22_1_1.v が生成された。
DisplayPort_test_86_19020.png

C/RTL 協調シミュレーションを行った。結果を示す。
DisplayPort_test_92_19021.png

C/RTL 協調シミュレーション波形を示す。まずは全体の波形から。
DisplayPort_test_87_19020.png

最初に vsync_V が 1 に変化しているのが分かるだろうか?これで、vsync の立ち上がりからスタートするaxis2video_out.v の動作を確認するシミュレーション時間が短くて済む。

拡大してみよう。
DisplayPort_test_88_19020.png

これで、dp_video_out_hsync とdp_video_out_vsync 、dp_live_video_de_out のモデルのXGA_sync_gen.v と XGA_sync_gen_mul_mul_10ns_12ns_22_1_1.v ができた。

このモデルを使って、”Ultra96のDisplayPortを使用するためのIPを作成する6(テストベンチ の作成2)”のテストベンチのaxis2video_out_tb.v を変更した。
テストベンチのaxis2video_out_tb.v を示す。

`default_nettype none
`timescale 100ps / 1ps

// axis2video_out_tb.v
// 2019/01/17 by marsee
//

module axis2video_out_tb;

    reg  axi_clk;
    reg  axi_rst_n;
    wire ip_start;
    wire ip_done;
    wire [31:0] TDATA;
    wire TVALID;
    wire TREADY;
    wire [3:0] TKEEP;
    wire [3:0] TSTRB;
    wire TUSER;
    wire TLAST;
    wire TID;
    wire TDEST;
    reg  init_done;
    wire init_done_out;
    reg  disp_clk;
    wire  de_in;
    wire  vsync_in;
    wire  hsync_in;
    wire [35:0] disp_pixel;
    wire de_out;
    wire vsync_out;
    wire hsync_out;
    reg  ap_start;
    
    // pattern_gen_axis instance
    pattern_gen_axis pga_inst(
        .ap_clk(axi_clk),
        .ap_rst_n(axi_rst_n),
        .ap_start(ip_start),
        .ap_done(ip_done),
        .ap_idle(),
        .ap_ready(),
        .outs_TDATA(TDATA),
        .outs_TVALID(TVALID),
        .outs_TREADY(TREADY),
        .outs_TKEEP(TKEEP),
        .outs_TSTRB(TSTRB),
        .outs_TUSER(TUSER),
        .outs_TLAST(TLAST),
        .outs_TID(TID),
        .outs_TDEST(TDEST),
        .init_done_V(init_done),
        .init_done_out_V(init_done_out),
        .ap_return()
    );
    
    XGA_sync_gen xgasg (
        .ap_clk(disp_clk),
        .ap_rst(!axi_rst_n),
        .ap_start(ap_start),
        .ap_done(),
        .ap_idle(),
        .ap_ready(),
        .de_V(de_in),
        .hsync_V(hsync_in),
        .vsync_V(vsync_in)
    );
    
    // axis2video_out instance
    axis2video_out axis2vo_inst(
        .disp_clk(disp_clk),
        .axi_clk(axi_clk),
        .axi_rst_n(axi_rst_n),
        .init_done(init_done),
        .axis_tdata(TDATA),
        .axis_tvalid(TVALID),
        .axis_tready(TREADY),
        .axis_tkeep(TKEEP),
        .axis_tstrb(TSTRB),
        .axis_tuser(TUSER),
        .axis_tlast(TLAST),
        .axis_tid(TID),
        .axis_tdest(TDEST),
        .ip_start(ip_start),
        .ip_done(ip_done),
        .de_in(de_in),
        .vsync_in(vsync_in),
        .hsync_in(hsync_in),
        .disp_pixel(disp_pixel),
        .de_out(de_out),
        .vsync_out(vsync_out),
        .hsync_out(hsync_out)
    );
    
    // axi_clk = 200 MHz (5 ns)
    always #25
        axi_clk = ~axi_clk;
    
    // disp_clk = 65 MHz (13.4 ns)
    always #77
        disp_clk = ~disp_clk;
        
    initial  begin
        axi_clk = 1'b1;
        disp_clk = 1'b1;
        axi_rst_n = 1'b0;
        init_done = 1'b0;
        ap_start = 1'b0;

        // Wait 100 ns for global reset to finish
        #1000;
        axi_rst_n = 1'b1;
        
        #1000;
        init_done = 1'b1; 
        ap_start = 1'b1;     
    end
endmodule

`default_nettype wire


hsync_in と vsync_in をアクティブ・ハイに変更した axis2video_out.v を示す。
(2019/01/25 :修正)このソースコードにはバグがあります。バグ修正後のソースコードについては、”Ultra96のDisplayPortを使用するためのプロジェクトを作成する4(完成)”をご覧ください。

// axis2video_out.v
// 2019/01/14 by marsee
//

`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;
    reg     vsync_axi_1b, vsync_axi_2b;
    wire    vsync_axi;
    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;
    
    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
    
    // reset signals    
    always @(posedge axi_clk) begin
        fifo_reset_axi_2b <= ~init_done | ~axi_rst_n | vsync_1d;
        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 | ~axi_rst_n | 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 | ~axi_rst_n;
        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)
                        cs_start <= IP_START_1;
                end
                IP_START_1 : begin
                    ip_start <= 1'b1;
                    if (ip_done)
                        cs_start <= IDLE_START;
                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[23:16], 4'd0, pfifo_dout[15:8], 4'd0, pfifo_dout[7:0], 4'd0};
    assign de_out = de_1d;
    assign vsync_out = vsync_1d;
    assign hsync_out = hsync_1d;
endmodule

`default_nettype wire


axis2video_out のVivado 2018.3 プロジェクトを示す。
DisplayPort_test_92_19020.png

シミュレーションの階層を示す。
DisplayPort_test_91_19020.png

論理シミュレーションの全体波形を示す。
DisplayPort_test_89_19020.png

うまく動いていそうだ。

拡大波形を示す。
DisplayPort_test_90_19020.png

水平 1 ラインの時間が 20.6976 us であることが分かる。
これは、1クロック、 15.4 us X (1024 + 24 + 136 + 160) = 20.6976 us で計算が合う。
  1. 2019年01月21日 04:02 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック URL
http://marsee101.blog.fc2.com/tb.php/4419-6f580f76
この記事にトラックバックする(FC2ブログユーザー)