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

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

FPGAの部屋

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

Vivado HLS の GUI コマンド実行履歴を TCL 実行スクリプトにする

今のところ、私はほとんどVivado HLS をGUI で操作しているが、TCL スクリプトで動作させることもできる。
GUI でやった履歴を TCL スクリプトにする機能が Vivado HLS にはある。
それは、 solution? -> script.tcl だ。
RTL_kernel_6_191228.png

script.tcl は solution? ディレクトリの下にある。
RTL_kernel_7_191228.png

lap_filter_axis_dma.cpp を合成して、Export RTL した時の TCL スクリプトを貼っておく。

############################################################
## This file is generated automatically by Vivado HLS.
## Please DO NOT edit it.
## Copyright (C) 1986-2019 Xilinx, Inc. All Rights Reserved.
############################################################
open_project lap_filter_axis_dma
set_top lap_filter_axis_dma
add_files lap_filter_axis_dma/lap_filter_axis_dma.cpp
open_solution "solution1"
set_part {xczu3cg-sbva484-1-e}
create_clock -period 10 -name default
config_export -format ip_catalog -rtl verilog -vivado_optimization_level 0 -vivado_phys_opt none -vivado_report_level 0 -xo /home/masaaki/Vivado_HLS/Ultra96/test/lap_filter_axis_dma.xo
config_interface   -m_axi_addr64 -m_axi_offset off -register_io off -trim_dangling_port=0
config_sdx -target xocc
config_compile -name_max_length 80 -pipeline_loops 64
config_schedule -enable_dsp_full_reg
#source "./lap_filter_axis_dma/solution1/directives.tcl"
#csim_design
csynth_design
#cosim_design
export_design -rtl verilog -format ip_catalog -xo /home/masaaki/Vivado_HLS/Ultra96/test/lap_filter_axis_dma.xo


この TCL スクリプトを実行する時は、
vivado_hls -f script.tcl
とする。
  1. 2019年12月28日 17:09 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

AXI4 MasterとAXI4 Streamインターフェースがあって、AXI4 Masterにmemcpy()を使用しているVivado HLSの回路

AXI4 Master インターフェースと AXI4 Stream インターフェースを持っているVivado HLS で作成した回路でAXI4 Master に memcpy() を使用していると合成時のレポートがでないということをツィッターで聞いた。ツィッターでつぶやいていたのは、 @lp6m2 さんで、聞いていみると、Xilinx のCommunity Forums にも、”Is it possible to set internal BRAM values using AXI4 protocol in HLS IP using AXI-Stream protocol?”で質問しているそうだ。

私も試してみることにした。ソースコードはmemcpy() の代わりに for() 文でコピーする行も追加させていただいた。今はコメントアウトしてある。bram_setparam_test.cpp を示す。

#include "hls_video.h"
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include <iostream>
#include <string.h>

void bram_setparam_test(bool mode, int *inputparam,
        hls::stream<ap_axiu<32,1,1,1> >& instream, hls::stream<ap_axiu<32,1,1,1> >& outstream){
#pragma HLS INTERFACE axis port = instream
#pragma HLS INTERFACE axis port = outstream
#pragma HLS INTERFACE s_axilite port = mode
#pragma HLS INTERFACE m_axi port = inputparam offset=slave depth=100
#pragma HLS INTERFACE s_axilite port = mode
#pragma HLS INTERFACE s_axilite port = return
    static int internal_param[100];
#pragma HLS RESOURCE variable=internal_param core=RAM_1P_BRAM
    if(mode == true){
        //AXI4 set parameter mode
        memcpy(internal_param, inputparam, sizeof(int) * 100);
        /*for(int i=0; i<100; i++){
#pragma HLS PIPELINE II=1
            internal_param[i] = inputparam[i];
        }*/
    }else{
        //AXI4-stream MODE
        for(int i = 0; i < 100; i++){
#pragma HLS PIPELINE II=1
            int inval = instream.read().data;
            ap_axiu<32,1,1,1> outval;
            outval.data = inval * internal_param[i];
            outval.last = (i == 99 ? 1 : 0);
            outstream.write(outval);
        }
    }
}


自分なりにテストベンチのbram_setparam_test_tb.cpp を作ったので、それを示す。

// bram_setparam_test_tb.cpp
// 2019/12/04 by marsee
//

#include "hls_video.h"
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include <iostream>
#include <string.h>

void bram_setparam_test(bool mode, int* inputparam,
        hls::stream<ap_axiu<32,1,1,1> >& instream, hls::stream<ap_axiu<32,1,1,1> >& outstream);

int inputparam[100];

int main(){
    using namespace std;

    hls::stream<ap_axiu<32,1,1,1> > ins;
    hls::stream<ap_axiu<32,1,1,1> > outs;
    ap_axiu<32,1,1,1> streamd;
    ap_axiu<32,1,1,1> vals;

    for(int i=0; i<100; i++){
        inputparam[i] = i;
    }

    bram_setparam_test(true, inputparam, ins, outs);

    for(int i=0; i<100; i++){
        streamd.data = i;
        if(i == 0)
            streamd.user = 1;
        else
            streamd.user = 0;
        if(i == 100-1)
            streamd.last = 1;
        else
            streamd.last = 0;
        ins << streamd;
    }

    bram_setparam_test(false, inputparam, ins, outs);

    for(int i=0; i<100; i++){
        outs >> vals;
        if((int)vals.data != i*i){
            fprintf(stderr, "Error: i = %d, i^2 = %d; outs = %d\n", i, i*i, (int)vals.data);
            exit(1);
        }
        printf("i = %d, i^2 = %d; outs = %d\n", i, i*i, (int)vals.data);
    }
    return(0);
}


Vivado HLS 2019.2 のプロジェクトを示す。
Vivado_HLS_MS_memcpy_2_191205.png

C シミュレーションを行って成功した。結果を示す。
Vivado_HLS_MS_memcpy_1_191205.png

C コードの合成を行った。この結果はスカスカだった。レポートが表示されない。
Vivado_HLS_MS_memcpy_3_191205.png

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

波形を見ると、正常に動作しているようだ。
Vivado_HLS_MS_memcpy_5_191205.png

Vivado_HLS_MS_memcpy_6_191205.png

Export RTL もそれらしく動いている。
Vivado_HLS_MS_memcpy_7_191205.png

レポートの結果にも問題は無いようだ。

次に、memcpy() をコメントアウトして、for() を活かしてみよう。

 if(mode == true){
  //AXI4 set parameter mode
  //memcpy(internal_param, inputparam, sizeof(int) * 100);
  for(int i=0; i<100; i++){
#pragma HLS PIPELINE II=1
   internal_param[i] = inputparam[i];
  }
 }else{


これでC コードの合成を行った。結果を示す。今度はちゃんとレポートが出た。
Vivado_HLS_MS_memcpy_8_191205.png

C/RTL 協調シミュレーションのレイテンシも memcpy() の場合と同一だ。
Vivado_HLS_MS_memcpy_9_191205.png

Export RTL を示す。memcpy() とあまり変わりがない。
Vivado_HLS_MS_memcpy_10_191205.png

今までも memcpy() は使っていたので、それだけの原因では無いと思う。
AXI4 Master を memcpy() でやっている時に、AXI4 Stream インターフェースを使ったことは無いので、その時にC コードの合成のレポートが消えてしまうのかも知れない?
なお、Vivado HLS 2018.3 でも、Vivado HLS 2017.4 でも、同様にC コードの合成のレポートが消えてしまう。
  1. 2019年12月05日 05:44 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

HDLab で”FPGAの部屋 プレゼンツ 「 HLSハンズオンセミナー基礎編」”をやります

HDLab で”FPGAの部屋 プレゼンツ 「 HLSハンズオンセミナー基礎編」”をやります。

大学内部では院生対象にVivado HLS のセミナをしていたのですが、それを元に全面リニューアルして、一般の方を対象にHDLabさんでやることになりました。

最初に、Zynq の説明とAXI4 インターフェースの概要、Vivado HLS の概要を説明します。

次からは私と一緒にハンズオンをします。簡単な例でVivado HLS のツールのやり方を覚えます。基礎編です。

Vivado HLS の基礎的なサンプル
AXI4 Lite Slave 編
AXI4 Master 編
AXI4 Stream 編


一緒にハンズオンをしながら、Vivado HLSの実装方法をその都度説明していきます。
「Vivado HLS の基礎的なサンプル」と「AXI4 Lite Slave 編」でも実習がありますが、これは私と一緒に実装します。
AXI4 Master 編では、基礎編ながら、コードの変更やプラグマを追加することで3段階に性能を向上させます。

AXI4 Steram 編では演習があります。今度は自分でVivado HLS + Vivado で実装してみます。丁寧にテキストを書いてあるので、簡単に実装できます。

Vivado HLS で回路を作ると、スケジューリングはツールにおまかせなので、バグが少なくなります(DSF2019 での森岡さんのお言葉を一部パクりました)。ぜひ、便利な高位合成ツールの使い方を勉強してみませんか?
ご参加をお待ちしております。
(ステマ、宣伝でした。)
  1. 2019年11月27日 05:23 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

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
»