FC2カウンター FPGAの部屋 Vivado HLS
FC2ブログ

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

FPGAの部屋

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

Vivado HLS 2019.1 の tgamma 関数をテストする

Vivado HLS 2019.1 の tgamma 関数がおかしいというツィートが流れてきたので、自分でもやってみた。

@lp6m2 さんのツィートを示す。


tgamma 関数を知らなかったので、調べてみた。
tgamma, tgammaf, tgammal - 本当のガンマ ... - Ubuntu Manpage
C/C++でガンマ関数tgamma()
どうやら tgamma 関数は階乗を整数から整数以外に拡張したもので、(n-1)! ということのようだ。つまり、tgamma(3) = 2 ということらしい?

コードも一部ツィートから引用した。
テストベンチを示す。

// tgamma_tb.cpp
// 2019/10/27 by marsee

#include <cmath>
#include <hls_math.h>
#include <ap_fixed.h>
using namespace std;

int tgamma_ip(ap_fixed<10,5> x, ap_fixed<10,5> &result);

int main(){
    ap_fixed<10,5> x, result;
    ap_fixed<10,5> x_soft, result_soft;

    x = ap_fixed<10,5>(3.0);
    tgamma_ip(x, result);

    x_soft = ap_fixed<10,5>(3.0);
    result_soft = hls::tgamma(x_soft);

    printf("result = %f\n", (float)result);
    printf("result_soft = %f\n", (float)result_soft);
    printf("%d\n", hls::tgamma((int)3));
    printf("%e\n", hls::tgamma((float)3.0));
    printf("%e\n", hls::tgamma((double)3.0));
    printf("%e\n", hls::tgamma((float)(-3.0)));

}


ソースコードを示す。

// tgamma.cpp
// 2019/10/27 by marsee

#include <cmath>
#include <hls_math.h>
#include <ap_fixed.h>
using namespace std;

int tgamma_ip(ap_fixed<10,5> x, ap_fixed<10,5> &result){
    result = hls::tgamma(x);

    return(0);
}


tgamma_1_191027.png

C シミュレーションを行った。
tgamma_2_191027.png

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
   Compiling ../../../tgamma_tb.cpp in debug mode
   Compiling ../../../tgamma.cpp in debug mode
   Generating csim.exe
result = 0.000000
result_soft = 0.000000
0
inf
inf
0.000000e+00
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


やはり値がおかしい。

C コードの合成を行ってみた。
tgamma_3_191027.png

Ultra96 では、DSP48E とLUT が 100 % をオーバーしている。相当リソースを消費している。
  1. 2019年10月27日 05:43 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS でAXI4 Lite のレジスタのHDL コードを生成しよう

レジスタを生成できるツールがあるようだが、Vivado HLS でも同様に AXI4 Lite インターフェースのレジスタを C ソースコードから生成することができる。これを使って、 AXI4 Lite インターフェースのレジスタを Verilog HDL と VHDL のソースコードとして簡単に生成することができるので紹介する。

紹介するVivado HLS のプロジェクトは”Ultra96のDisplayPortを使用するためのIPを作成する2(pattern_gen_axis IP)”の pattern_gen_axis IP とする。

pattern_gen_axis IP の C ソースコードの一部分を抜き出す。

int pattern_gen_axis(hls::stream<ap_axis<32,1,1,1> >& outs,
        int v_size, int h_size,
        ap_uint<1> &init_done, ap_uint<1> &init_done_out
){
#pragma HLS INTERFACE ap_none register port=init_done_out
#pragma HLS INTERFACE s_axilite port=init_done
#pragma HLS INTERFACE s_axilite port=v_size
#pragma HLS INTERFACE s_axilite port=h_size
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE s_axilite port=return


上のソースコードを見ると、ブロックレベルのインターフェースは s_axilite で、init_done と v_size, h_size が s_axilite つまりAXI4 Lite インターフェースとなっている。
これを合成すると、solution1/syn の verilog ディレクトリと vhdl ディレクトリにHDL ファイルが生成される。
vivado_hls_reg_1_190926.png

verilog を見てみると、

pattern_gen_axis.v
pattern_gen_axis_AXILiteS_s_axi.v


があるのが分かる。pattern_gen_axis.v がトップのVerilog HDL ファイルだが、そのAXI4 Lite インターフェース部分は、pattern_gen_axis_AXILiteS_s_axi.v にまとめられている。
pattern_gen_axis_AXILiteS_s_axi.v を見ると、とても読みやすいのが分かるだろうか?
pattern_gen_axis_AXILiteS_s_axi.v を示す。

// ==============================================================
// File generated on Tue Jan 22 04:34:10 JST 2019
// Vivado(TM) HLS - High-Level Synthesis from C, C++ and SystemC v2018.3 (64-bit)
// SW Build 2405991 on Thu Dec  6 23:36:41 MST 2018
// IP Build 2404404 on Fri Dec  7 01:43:56 MST 2018
// Copyright 1986-2018 Xilinx, Inc. All Rights Reserved.
// ==============================================================
`timescale 1ns/1ps
module pattern_gen_axis_AXILiteS_s_axi
#(parameter
    C_S_AXI_ADDR_WIDTH = 6,
    C_S_AXI_DATA_WIDTH = 32
)(
    // axi4 lite slave signals
    input  wire                          ACLK,
    input  wire                          ARESET,
    input  wire                          ACLK_EN,
    input  wire [C_S_AXI_ADDR_WIDTH-1:0] AWADDR,
    input  wire                          AWVALID,
    output wire                          AWREADY,
    input  wire [C_S_AXI_DATA_WIDTH-1:0] WDATA,
    input  wire [C_S_AXI_DATA_WIDTH/8-1:0] WSTRB,
    input  wire                          WVALID,
    output wire                          WREADY,
    output wire [1:0]                    BRESP,
    output wire                          BVALID,
    input  wire                          BREADY,
    input  wire [C_S_AXI_ADDR_WIDTH-1:0] ARADDR,
    input  wire                          ARVALID,
    output wire                          ARREADY,
    output wire [C_S_AXI_DATA_WIDTH-1:0] RDATA,
    output wire [1:0]                    RRESP,
    output wire                          RVALID,
    input  wire                          RREADY,
    // user signals
    output wire [10:0]                   v_size_V,
    output wire [10:0]                   h_size_V,
    output wire [0:0]                    init_done_V
);
//------------------------Address Info-------------------
// 0x00 : reserved
// 0x04 : reserved
// 0x08 : reserved
// 0x0c : reserved
// 0x10 : Data signal of v_size_V
//        bit 10~0 - v_size_V[10:0] (Read/Write)
//        others   - reserved
// 0x14 : reserved
// 0x18 : Data signal of h_size_V
//        bit 10~0 - h_size_V[10:0] (Read/Write)
//        others   - reserved
// 0x1c : reserved
// 0x20 : Data signal of init_done_V
//        bit 0  - init_done_V[0] (Read/Write)
//        others - reserved
// 0x24 : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)

//------------------------Parameter----------------------
localparam
    ADDR_V_SIZE_V_DATA_0    = 6'h10,
    ADDR_V_SIZE_V_CTRL      = 6'h14,
    ADDR_H_SIZE_V_DATA_0    = 6'h18,
    ADDR_H_SIZE_V_CTRL      = 6'h1c,
    ADDR_INIT_DONE_V_DATA_0 = 6'h20,
    ADDR_INIT_DONE_V_CTRL   = 6'h24,
    WRIDLE                  = 2'd0,
    WRDATA                  = 2'd1,
    WRRESP                  = 2'd2,
    WRRESET                 = 2'd3,
    RDIDLE                  = 2'd0,
    RDDATA                  = 2'd1,
    RDRESET                 = 2'd2,
    ADDR_BITS         = 6;

//------------------------Local signal-------------------
    reg  [1:0]                    wstate = WRRESET;
    reg  [1:0]                    wnext;
    reg  [ADDR_BITS-1:0]          waddr;
    wire [31:0]                   wmask;
    wire                          aw_hs;
    wire                          w_hs;
    reg  [1:0]                    rstate = RDRESET;
    reg  [1:0]                    rnext;
    reg  [31:0]                   rdata;
    wire                          ar_hs;
    wire [ADDR_BITS-1:0]          raddr;
    // internal registers
    reg  [10:0]                   int_v_size_V = 'b0;
    reg  [10:0]                   int_h_size_V = 'b0;
    reg  [0:0]                    int_init_done_V = 'b0;

//------------------------Instantiation------------------

//------------------------AXI write fsm------------------
assign AWREADY = (wstate == WRIDLE);
assign WREADY  = (wstate == WRDATA);
assign BRESP   = 2'b00;  // OKAY
assign BVALID  = (wstate == WRRESP);
assign wmask   = { {8{WSTRB[3]}}, {8{WSTRB[2]}}, {8{WSTRB[1]}}, {8{WSTRB[0]}} };
assign aw_hs   = AWVALID & AWREADY;
assign w_hs    = WVALID & WREADY;

// wstate
always @(posedge ACLK) begin
    if (ARESET)
        wstate <= WRRESET;
    else if (ACLK_EN)
        wstate <= wnext;
end

// wnext
always @(*) begin
    case (wstate)
        WRIDLE:
            if (AWVALID)
                wnext = WRDATA;
            else
                wnext = WRIDLE;
        WRDATA:
            if (WVALID)
                wnext = WRRESP;
            else
                wnext = WRDATA;
        WRRESP:
            if (BREADY)
                wnext = WRIDLE;
            else
                wnext = WRRESP;
        default:
            wnext = WRIDLE;
    endcase
end

// waddr
always @(posedge ACLK) begin
    if (ACLK_EN) begin
        if (aw_hs)
            waddr <= AWADDR[ADDR_BITS-1:0];
    end
end

//------------------------AXI read fsm-------------------
assign ARREADY = (rstate == RDIDLE);
assign RDATA   = rdata;
assign RRESP   = 2'b00;  // OKAY
assign RVALID  = (rstate == RDDATA);
assign ar_hs   = ARVALID & ARREADY;
assign raddr   = ARADDR[ADDR_BITS-1:0];

// rstate
always @(posedge ACLK) begin
    if (ARESET)
        rstate <= RDRESET;
    else if (ACLK_EN)
        rstate <= rnext;
end

// rnext
always @(*) begin
    case (rstate)
        RDIDLE:
            if (ARVALID)
                rnext = RDDATA;
            else
                rnext = RDIDLE;
        RDDATA:
            if (RREADY & RVALID)
                rnext = RDIDLE;
            else
                rnext = RDDATA;
        default:
            rnext = RDIDLE;
    endcase
end

// rdata
always @(posedge ACLK) begin
    if (ACLK_EN) begin
        if (ar_hs) begin
            rdata <= 1'b0;
            case (raddr)
                ADDR_V_SIZE_V_DATA_0: begin
                    rdata <= int_v_size_V[10:0];
                end
                ADDR_H_SIZE_V_DATA_0: begin
                    rdata <= int_h_size_V[10:0];
                end
                ADDR_INIT_DONE_V_DATA_0: begin
                    rdata <= int_init_done_V[0:0];
                end
            endcase
        end
    end
end


//------------------------Register logic-----------------
assign v_size_V    = int_v_size_V;
assign h_size_V    = int_h_size_V;
assign init_done_V = int_init_done_V;
// int_v_size_V[10:0]
always @(posedge ACLK) begin
    if (ARESET)
        int_v_size_V[10:0] <= 0;
    else if (ACLK_EN) begin
        if (w_hs && waddr == ADDR_V_SIZE_V_DATA_0)
            int_v_size_V[10:0] <= (WDATA[31:0] & wmask) | (int_v_size_V[10:0] & ~wmask);
    end
end

// int_h_size_V[10:0]
always @(posedge ACLK) begin
    if (ARESET)
        int_h_size_V[10:0] <= 0;
    else if (ACLK_EN) begin
        if (w_hs && waddr == ADDR_H_SIZE_V_DATA_0)
            int_h_size_V[10:0] <= (WDATA[31:0] & wmask) | (int_h_size_V[10:0] & ~wmask);
    end
end

// int_init_done_V[0:0]
always @(posedge ACLK) begin
    if (ARESET)
        int_init_done_V[0:0] <= 0;
    else if (ACLK_EN) begin
        if (w_hs && waddr == ADDR_INIT_DONE_V_DATA_0)
            int_init_done_V[0:0] <= (WDATA[31:0] & wmask) | (int_init_done_V[0:0] & ~wmask);
    end
end


//------------------------Memory logic-------------------

endmodule


どうですか?読みやすいですよね?
という具合に、AXI4 Lite のレジスタ記述が欲しい場合は、レジスタにしたい名称をトップの関数の引数にして、
#pragma HLS INTERFACE s_axilite port=<レジスタにしたい名称>
ディレクティブを与えて、適当な演算を書いて合成すれば、XXXX_AXILiteS_s_axi.v や XXXX_AXILiteS_s_axi.vhd ができるので、それを抜き出して使ってAXI4 Lite のレジスタ記述のあるHDL 回路を作成しましょう。

ただし、Vivado HLS で作れる回路だったら、そのまま C で記述して作ったほうが簡単で確実なのは言うまでもない。これを使用するのはカメラのインターフェース回路にレジスタを付けるというような場合のみとなるだろう。
そして、使用する場合はライセンセンスを守って使いましょう
  1. 2019年09月26日 06:27 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.1 でLOOG_MERGE 指示子を使う

Adom Taylor さんの”MicroZed Chronicles: HLS Working with Loops”を見て、LOOP_MERGE 指示子を発見した。Loop を 1 つにしてくれるということで、もしかしたら、AXI4-Stream で 2 つの入力ストリームがある時の 2 つの USER 待ちループを 1 個にしてくれるんじゃないだろうか?ということでやってみた。

今回、対象にするのは、
2つのAXI4 Stream 入力データを演算してAXI4 Stream 出力1
2つのAXI4 Stream 入力データを演算してAXI4 Stream 出力2

2つのAXI4 Stream 入力データを演算してAXI4 Stream 出力1”の s_squares_axis.cpp ソースコードを見てもらうと分かると思うが、 x と y の 2 つの入力を一緒にしたコードを書いている。それを x だけ、y だけの USER 信号待ちループにして LOOP_MERGE 指示子を与えて、x と y の 2 つの入力を一緒にしたコードと同じ結果になるのではないか?ということでやってみよう。使用するのは、Vivado HLS 2019.1 とする。

s_squares_axis.cpp を示す。

#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

int s_squares_axis(hls::stream<ap_axis<8,1,1,1> >& x,
    hls::stream<ap_axis<8,1,1,1> >& y, hls::stream<ap_axis<32,1,1,1> >& result){
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=result
#pragma HLS INTERFACE axis register both port=y
#pragma HLS INTERFACE axis register both port=x
    ap_axis<8,1,1,1> xt;
    ap_axis<8,1,1,1> yt;
    ap_axis<32,1,1,1> rlt;

    xt.user = 0; yt.user = 0;
    Loop_xw : while(xt.user == 0){
#pragma HLS PIPELINE II=1
    // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        x >> xt;
    }

    Loop_yw : while(yt.user == 0){
#pragma HLS PIPELINE II=1
    // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        y >> yt;
    }

    Loop_main : for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        if(i != 0){
            x >> xt; y >> yt;
        }
        rlt.data = xt.data*xt.data + yt.data*yt.data;
        if(i == 0)
            rlt.user = 1;
        else
            rlt.user = 0;
        if(i==9)
            rlt.last = 1;
        else
            rlt.last = 0;
        result << rlt;
    }
    return(0);
}


x と y のUSER 待ちループを分離して、まずは、LOOP_MERGE 指示子を入れないで性能を見てみよう。

C シミュレーション結果を示す。
s_squares_axis_8_190920.png

問題ない。

C コードの合成結果を示す。
s_squares_axis_9_190920.png

Latency が 18 クロックで、”2つのAXI4 Stream 入力データを演算してAXI4 Stream 出力1”の 16 クロックよりも 2 クロック長い。

C/RTL 協調シミュレーションを行った。
s_squares_axis_10_190920.png

C/RTL 協調シミュレーションの波形を示す。
s_squares_axis_11_190920.png

やはり、x と y の入力は排他的になっているようだ。

次に、Loop_xw と Loop_yw に LOOP_MERGE 指示子を入れた。s_squares_axis.cpp を示す。

#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

int s_squares_axis(hls::stream<ap_axis<8,1,1,1> >& x,
    hls::stream<ap_axis<8,1,1,1> >& y, hls::stream<ap_axis<32,1,1,1> >& result){
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=result
#pragma HLS INTERFACE axis register both port=y
#pragma HLS INTERFACE axis register both port=x
    ap_axis<8,1,1,1> xt;
    ap_axis<8,1,1,1> yt;
    ap_axis<32,1,1,1> rlt;

    xt.user = 0; yt.user = 0;
    Loop_xw : while(xt.user == 0){
#pragma HLS LOOP_MERGE
#pragma HLS PIPELINE II=1
    // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        x >> xt;
    }

    Loop_yw : while(yt.user == 0){
#pragma HLS LOOP_MERGE
#pragma HLS PIPELINE II=1
    // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        y >> yt;
    }

    Loop_main : for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        if(i != 0){
            x >> xt; y >> yt;
        }
        rlt.data = xt.data*xt.data + yt.data*yt.data;
        if(i == 0)
            rlt.user = 1;
        else
            rlt.user = 0;
        if(i==9)
            rlt.last = 1;
        else
            rlt.last = 0;
        result << rlt;
    }
    return(0);
}


これで、solution2 を作成してやってみた。

C コードの合成結果を示す。
s_squares_axis_12_190920.png

LOOP_MERGE 指示子を入れないときと同じ結果になった。

C/RTL 協調シミュレーションの結果を示す。
s_squares_axis_13_190920.png

C/RTL 協調シミュレーションの波形を示す。
s_squares_axis_14_190920.png

これも LOOP_MERGE 指示子を入れないときと同じ結果だった。

次に、試しに Loop_main にも LOOP_MERGE 指示子を入れてみた。s_squares_axis.cpp を示す。

#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

int s_squares_axis(hls::stream<ap_axis<8,1,1,1> >& x,
    hls::stream<ap_axis<8,1,1,1> >& y, hls::stream<ap_axis<32,1,1,1> >& result){
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=result
#pragma HLS INTERFACE axis register both port=y
#pragma HLS INTERFACE axis register both port=x
    ap_axis<8,1,1,1> xt;
    ap_axis<8,1,1,1> yt;
    ap_axis<32,1,1,1> rlt;

    xt.user = 0; yt.user = 0;
    Loop_xw : while(xt.user == 0){
#pragma HLS LOOP_MERGE
#pragma HLS PIPELINE II=1
    // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        x >> xt;
    }

    Loop_yw : while(yt.user == 0){
#pragma HLS LOOP_MERGE
#pragma HLS PIPELINE II=1
    // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        y >> yt;
    }

    Loop_main : for(int i=0; i<10; i++){
#pragma HLS LOOP_MERGE
#pragma HLS PIPELINE II=1
        if(i != 0){
            x >> xt; y >> yt;
        }
        rlt.data = xt.data*xt.data + yt.data*yt.data;
        if(i == 0)
            rlt.user = 1;
        else
            rlt.user = 0;
        if(i==9)
            rlt.last = 1;
        else
            rlt.last = 0;
        result << rlt;
    }
    return(0);
}


このコードでのC コードの合成結果、C/RTL 協調シミュレーション結果も同じだった。

LOOP_MERGE 指示子は入出力を伴うループのマージは行わないのかも知れない?
計算のループだけマージされるのかも知れない?
  1. 2019年09月20日 05:00 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

2つのAXI4 Stream 入力データを演算してAXI4 Stream 出力2

2つのAXI4 Stream 入力データを演算してAXI4 Stream 出力1”の続き。

普通の C ソースコードで、2つのAXI4 Stream 入力からデータを受け取る方法を見つけたので、ソースコードを貼って、C シミュレーションを行った。今回は、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行う。なお、Vivado HLS 2019.1 を使用している。

C コードの合成を行った。
s_squares_axis_3_190912.png

Loop2 のLatency が 11 クロックと長めだが、1 クロックで 1 出力できるようだ。
リソース使用量はBRAM_18K は 0 個、DSP48E は 1 個、FF が 282 個、LUT が 676 個だった。

C/RTL 協調シミュレーションを行った。
s_squares_axis_4_190912.png

Latency は 44 クロックだった。

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

データ転送している辺りを拡大してみよう。
s_squares_axis_6_190912.png

入力 x と y は途中で、TREADY が 0 になってうけらない部分はあるが、その後は順調に 1 入力を 1 クロックでこなしている。
出力は、順調に 1 出力を 1 クロックでこなせている。

Export RTL を行った。結果を示す。
s_squares_axis_7_190912.png

LUT が 141 個、FF が 189 個、DSP が 2 個、使用されている。
CP achieved post-implementation は、 6.859 ns で問題無さそうだ。
  1. 2019年09月12日 04:39 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

2つのAXI4 Stream 入力データを演算してAXI4 Stream 出力1

以前、”2つのHLSストリームを同時に入力して演算してHLSストリーム出力2”でDATAFLOW 指示子を使って、2 つのHLS Stream 入力を同時に受け取ることができた。
しかし、その方法は大げさというかソースコードが分かりにくくなるため、普通の C ソースコードで、2つのAXI4 Stream 入力からデータを受け取る方法を見つけたので、書いておく。

s_squares_axis.cpp のコンセプトは、user == 1 だったら、AXI4 Stream 入力を止めて、2 つのAXI4 Steam 入力が共に user == 1 になることを待つということだ。
それでは、s_squares_axis.cpp を貼っておく。

#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

int s_squares_axis(hls::stream<ap_axis<8,1,1,1> >& x,
    hls::stream<ap_axis<8,1,1,1> >& y, hls::stream<ap_axis<32,1,1,1> >& result){
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=result
#pragma HLS INTERFACE axis register both port=y
#pragma HLS INTERFACE axis register both port=x
    ap_axis<8,1,1,1> xt;
    ap_axis<8,1,1,1> yt;
    ap_axis<32,1,1,1> rlt;

    xt.user = 0; yt.user = 0;
    Loop1 : while(!(xt.user==1 && yt.user==1)){
#pragma HLS PIPELINE II=1
    // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
        if(xt.user == 0)
            x >> xt;
        if(yt.user == 0)
            y >> yt;
    }

    Loop2 : for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        if(i != 0){
            x >> xt; y >> yt;
        }
        rlt.data = xt.data*xt.data + yt.data*yt.data;
        if(i == 0)
            rlt.user = 1;
        else
            rlt.user = 0;
        if(i==9)
            rlt.last = 1;
        else
            rlt.last = 0;
        result << rlt;
    }
    return(0);
}


なお、いつもの do{ } while( ); 文を使用すると、PIPELINE 指示子の II を 1 にしても、ループを 1 回回るのに 2 クロックかかってしまう。 do{ } while( ); 文を使用すると、ストリーム用のパイプから入力した user 信号をそのクロックで処理する必要があるから、1 クロックで処理することができなくなってしまうのだろう? while() 文を使用すると、現在のクロックでストリーム用のパイプから入力した user 信号は次のクロックで評価することができるので、ループを 1 回回るのに 1 クロックで済むのだと思う。
このように、ソフトウェアとしてではなく、ハードウェアとして、ソースコードを書く必要がある。

次に、テストベンチの s_squares_axis_tb.cpp を示す。

#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

int s_squares_axis(hls::stream<ap_axis<8,1,1,1> >& x,
    hls::stream<ap_axis<8,1,1,1> >& y, hls::stream<ap_axis<32,1,1,1> >& result);

int main(){
    using namespace std;
    hls::stream<ap_axis<8,1,1,1> > x;
    hls::stream<ap_axis<8,1,1,1> > y;
    hls::stream<ap_axis<32,1,1,1> > result;
    ap_axis<8,1,1,1> xt;
    ap_axis<8,1,1,1> yt;
    ap_axis<32,1,1,1> rlt;

    for(int i=0; i<5; i++){ // dummy data
        xt.user = 0; yt.user = 0;
        xt.data = i; yt.data = i;
        x << xt;
        if(i>2)
            y << yt;
    }
    for(int i=0; i<10; i++){
        xt.data = i; yt.data = i+1;
        if(i == 0){
            xt.user = 1; yt.user = 1;
        }else{
            xt.user = 0; yt.user = 0;
        }
        if(i == 9){
            xt.last = 1; yt.last = 1;
        }else{
            xt.last = 0; yt.last = 0;
        }
        x << xt; y << yt;
    }
    s_squares_axis(x, y, result);

    cout << endl;
    cout << "result" << endl;
    for(int i=0; i<10; i++){
        result >> rlt;
        cout << "i = " << i << " result = " << rlt.data << " user = "
                << rlt.user << " last = " << rlt.last << endl;
    }
    cout << endl;
    return(0);
}


AXI4 Stream の x パイプでは、5 つのダミーデータを入れてあるが、y パイプでは、2 つになっている。これで、本体のデータを正常に受けることができれば成功だ。

s_squares_axis2 プロジェクトを示す。
s_squares_axis_1_190910.png

C シミュレーションを行った。問題ないようだ。
s_squares_axis_2_190910.png
  1. 2019年09月10日 05:08 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLSのバージョンによる性能差(ガボールフィルタを使用)

Vivado HLS 2017.4、Vivado HLS 2018.3、Vivado HLS 2019.1 の性能差をガボールフィルタを使って比較してみよう。
ガボールフィルタの実装では結構差が出ている。Vviado HLS は各バージョンで同じプロジェクトを使えていたが、最近はプロバティが変わっていてエラーが出ることが多くなった。この3バージョンは同じプロジェクトで使いまわすことができなかった。各バージョンで独立のプロジェクトを必要とした。
なお、使用しているソースコードは、”Ultra96用ガボールフィルタIP の作成1”のものを使用している。

まずは、Vivado HLS 2017.4 から合成結果を示す。
Gabor_filter_lh_2_21_190905.png

Timing の Estimated は 5.84 ns と制約をオーバーしている。
Latency の min は 6152 クロック、max は 4147208 クロックだった。
リソース使用量はBRAM_18K が 24 個、DSP48E が 98 個、FF が6736 個、LUT が 8032 個だった。

Export RTL の結果をみてみよう。
Gabor_filter_lh_2_22_190905.png

CP achieved post-synthesis が 9.790 ns で、CP achieved post-implementation が 9.958 ns とターゲットの倍近くになってしまっている。これでは使い物にならないと言える。

次に、Vivado HLS 2018.3 の結果をみてみよう。
合成結果を示す。
Gabor_filter_lh_2_23_190905.png

Timing Estimated が 4.360 ns でターゲットをクリアできた。その代わりLatency は伸びている。
リソース使用量は 2017.4 よりも少なくなっている。

Export RTL の結果をみてみよう。
Gabor_filter_lh_2_24_190905.png

CP achieved post-synthesis が 3.388 ns で、CP achieved post-implementation が 4.347 ns とターゲットをクリアできた。

最後に、現在の最新バージョンの Vivado HLS 2019.1 の結果をみてみよう。
合成結果を示す。
Gabor_filter_lh_2_19_190905.png

2018.3 と余り変化はないようだ。

Export RTL の結果をみてみよう。
Gabor_filter_lh_2_20_190905.png

CP achieved post-synthesis が 3.388 ns で、CP achieved post-implementation が 4.643 ns とターゲットをクリアできたが 2018.3 よりも、CP achieved post-implementation が長くなっている。

最後に、全バージョンのVivado HLS での性能比較表を示す。
Gabor_filter_lh_2_25_190905.png

2018.3 と 2019.1 はほとんど一緒だが、2017.4 の性能はあまり良くないようだ。特に、Vivado での CP achieved post-implementation が 9.958 ns はターゲットの 2 倍近くになっている。なるべく新しいバージョンのVivado HLS と Vivado を使ったほうが良さそうだ。
  1. 2019年09月05日 06:33 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS で 2 つの引数から DMA Read する AXI4 Master モジュールを作る5

Vivado HLS で 2 つの引数から DMA Read する AXI4 Master モジュールを作る4”の続き。

前回は、x と y を構造体で表現したコードをチューニングしたが、1 クロックで 1 出力まではチューニングすることができなかった。今回は、32 ビット幅のDMA トランザクションの中で、フィールドを自分で決めて x と y を配置する形態でチューニングをしてみよう。

今回の sum_of_squares.cpp を示す。今回は、 ap_int<32> を使用している。
ap_int<32> の中から x と y の ap_int<8> の 8 ビット幅を切り出して、 ap_int<8> でキャストしている。

#include <ap_int.h>

int sum_of_squares(volatile ap_int<32> *xy, volatile ap_int<32> *result){
#pragma HLS INTERFACE m_axi depth=10 port=xy offset=slave
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE m_axi depth=10 port=result offset=slave
    ap_int<8> x, y;

    for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        ap_int<16> xyt = xy[i];

        x = (ap_int<8>)(xyt & 0xff);
        y = (ap_int<8>)((xyt & 0xff00)>>8);

        result[i] = x * x + y * y;
    }

    return(0);
}


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

// sum_of_squares_tb.cpp

#include <iostream>
#include <ap_int.h>

int sum_of_squares(volatile ap_int<32> *xy, volatile ap_int<32> *result);

int main(){
    ap_int<32> xy[10];
    ap_int<32> result[10];

    for(int i=0; i<10; i++){
        xy[i] = i;
        xy[i] += (ap_int<32>)((i+1)<<8);
    }

    sum_of_squares(xy, result);

    for(int i=0; i<10; i++){
        std::cout << "x[" << i << "]= " << i <<
                ", y[" << i << "] = " << i+1 <<
                ", result[" << i << "] = " <<
                result[i] << std::endl;
    }
}


sum_of_squares_28_190821.png

C シミュレーションを行った。
sum_of_squares_29_190821.png

C コードの合成を行った。
sum_of_squares_30_190821.png

Initiation Interval の achieved が 1 クロックで良さそう。

C/RTL 協調シミュレーションを行った。
sum_of_squares_31_190821.png

C/RTL 協調シミュレーションの波形を示す。
sum_of_squares_32_190821.png

DMA Read 、 DMA Write 共にバーストできていて良い感じだし、Latency も小さい。
これが良さそうだと思う。

最後におまけで、C++ でな無く、 C で書いたサンプル・プログラムを示す。

#include <ap_cint.h>

typedef struct xy_struct{
    int8 x;
    int8 y;
} xy_st;

int sum_of_squares(volatile xy_st *xy, volatile int *result){
#pragma HLS DATA_PACK variable=xy
#pragma HLS INTERFACE m_axi depth=10 port=xy offset=slave
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE m_axi depth=10 port=result offset=slave

    for(int i=0; i<10; i++){
        result[i] = xy[i].x * xy[i].x + xy[i].y * xy[i].y;
    }

    return(0);
}

  1. 2019年08月24日 05:00 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0
»