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

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

FPGAの部屋

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

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

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

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

前回は、AXI4 インターフェースが 3 個あるのはもったいないので、1 個のAXI4 インターフェースでループで性能を向上させたい。ということで、x と y を構造体で表現したのだが、性能的には、x と y を個別に実装したときと同様の性能だった。今回は、もっと成功向上を図ろうと思う。

前回の sum_of_squares.cpp で引数の構造体を 1 回で読んできたいということで、構造体を = でコピーしようと思い、以下のコードを作成した。

#include <stdint.h>

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

int sum_of_squares(volatile xy_st *xy, volatile int32_t *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++){
#pragma HLS PIPELINE II=1
        xy_st xyt;
        xyt = xy[i];

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

    return(0);
}


C コードを合成したのだが、エラーになってしまった。
sum_of_squares_24_190821.png

エラー内容を示す。

ERROR: [HLS 200-70] Compilation errors found: In file included from sum_of_squares/sum_of_squares.cpp:1:
sum_of_squares/sum_of_squares.cpp:17:7: error: no viable overloaded '='
  xyt = xy[i];
  ~~~ ^ ~~~~~
sum_of_squares/sum_of_squares.cpp:3:16: note: candidate function (the implicit copy assignment operator) not viable: 1st argument ('volatile xy_st' (aka 'volatile xy_struct')) would lose volatile qualifier
typedef struct xy_struct{
               ^


構造体自体をコピーできないようだ。それではということで、volatile xy_st *xy を union にしてみたが、union は関数の引数にできないという仕様だそうだ。残念。。。

とりあえず、構造体のメンバことにコピーしてみよう。
sum_of_squares.cpp を示す。

#include <stdint.h>

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

int sum_of_squares(volatile xy_st *xy, volatile int32_t *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++){
#pragma HLS PIPELINE II=1
        xy_st xyt;
        xyt.x = xy[i].x; xyt.y = xy[i].y;

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

    return(0);
}


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

やはり、 for 文を実行するのに 2 クロックかかっている。

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

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

やはり、Read トランザクションが 20 個あるよね。。。
構造体を使った場合はチューニングがうまく行かなかった。
  1. 2019年08月23日 04:00 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

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

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

前回は、AXI4 インターフェースを独立な 3 個にすることで、DMA Read がバーストするようになって、ループの中を 1 クロックで実行できるようになった。今回は、AXI4 インターフェースが 3 個あるのはもったいないので、1 個のAXI4 インターフェースでループで性能を向上させたい。

入力ポートの x と y は 8 ビットなので、32 ビット幅のインターフェースでは、1 トランザクションで 4 個転送できる。x と y をまとめてしまえば 1 回のRead で x と y を持ってこられるんじゃないか?ということで、とりあえずは structure にしてみよう。
sum_of_squares.cpp はこうなった。

#include <stdint.h>

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

int sum_of_squares(volatile xy_st *xy, volatile int32_t *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++){
#pragma HLS PIPELINE II=1
        result[i] = xy[i].x * xy[i].x + xy[i].y * xy[i].y;
    }

    return(0);
}


今回は、ap_int<8> は * の演算子がオーバーロードされていないということで、 int_8t を使うことにした。
テストベンチのsum_of_squares_tb.cpp はこうなった。

// sum_of_squares_tb.cpp

#include <iostream>
#include <stdint.h>

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

int sum_of_squares(volatile xy_st *xy, volatile int *result);

int main(){
    xy_st xy[10];
    int result[10];

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

    sum_of_squares(xy, result);

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


sum_of_squares_17_190821.png

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

問題ないようだ。

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

ループの中は 4 クロックで処理している。

AXI4 Lite Slave のアドレスマップを見ると、引数で x と y を実装したときと同じようだ。

//------------------------Address Info-------------------
// 0x00 : Control signals
//        bit 0  - ap_start (Read/Write/COH)
//        bit 1  - ap_done (Read/COR)
//        bit 2  - ap_idle (Read)
//        bit 3  - ap_ready (Read)
//        bit 7  - auto_restart (Read/Write)
//        others - reserved
// 0x04 : Global Interrupt Enable Register
//        bit 0  - Global Interrupt Enable (Read/Write)
//        others - reserved
// 0x08 : IP Interrupt Enable Register (Read/Write)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x0c : IP Interrupt Status Register (Read/TOW)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x10 : Data signal of ap_return
//        bit 31~0 - ap_return[31:0] (Read)
// 0x18 : Data signal of xy_x
//        bit 31~0 - xy_x[31:0] (Read/Write)
// 0x1c : reserved
// 0x20 : Data signal of xy_y
//        bit 31~0 - xy_y[31:0] (Read/Write)
// 0x24 : reserved
// 0x28 : Data signal of result
//        bit 31~0 - result[31:0] (Read/Write)
// 0x2c : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)



次に引数 xy に DATA_PACK 指示子を追加してみよう。
sum_of_squares_20_190821.png

sum_of_squares.cpp はこうなった。

#include <stdint.h>

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

int sum_of_squares(volatile xy_st *xy, volatile int32_t *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++){
#pragma HLS PIPELINE II=1
        result[i] = xy[i].x * xy[i].x + xy[i].y * xy[i].y;
    }

    return(0);
}


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

やはり、ループの中は 4 クロックで処理している。
AXI4 Lite Slave のアドレスマップを示す。

//------------------------Address Info-------------------
// 0x00 : Control signals
//        bit 0  - ap_start (Read/Write/COH)
//        bit 1  - ap_done (Read/COR)
//        bit 2  - ap_idle (Read)
//        bit 3  - ap_ready (Read)
//        bit 7  - auto_restart (Read/Write)
//        others - reserved
// 0x04 : Global Interrupt Enable Register
//        bit 0  - Global Interrupt Enable (Read/Write)
//        others - reserved
// 0x08 : IP Interrupt Enable Register (Read/Write)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x0c : IP Interrupt Status Register (Read/TOW)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x10 : Data signal of ap_return
//        bit 31~0 - ap_return[31:0] (Read)
// 0x18 : Data signal of xy
//        bit 31~0 - xy[31:0] (Read/Write)
// 0x1c : reserved
// 0x20 : Data signal of result
//        bit 31~0 - result[31:0] (Read/Write)
// 0x24 : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


データがパックされて、xy のオフセットアドレスのみになったのだが、ループの中は 4 クロックなのか???

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

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

結局、40 回Read している。RDATA を見るとデータがパックされているのが分かるが、アクセスとしては x と y が独立の引数のときと同じということが分かった。
  1. 2019年08月22日 05:12 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

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

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

前回は、x と y の平方数の和を求める C ソースコードをいろいろとチューニングしていった。しかし、入力の x と y を同じAXI4 インターフェースから取ってくるので、どうしても x と y をRead する DMA がバーストにならないという欠点があった。今回は、AXI4 インターフェースを独立な 3 個にすることで、DMA Read がバーストするようにしてみよう。

さて、AXI4 インターフェースを 3 個にするにはどうするかというと、x , y , result の 3 個の引数に独立な bundle を与えればよい。
今回の sum_of_squares.cpp を示す。

int sum_of_squares(volatile char *x, volatile char *y, volatile int *result){
#pragma HLS INTERFACE m_axi depth=10 port=y offset=slave bundle=y
#pragma HLS INTERFACE m_axi depth=10 port=x offset=slave bundle=x
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE m_axi depth=10 port=result offset=slave bundle=result

    LOOP1: for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        char xt = x[i];
        char yt = y[i];

        result[i] = xt*xt + yt*yt;
    }

    return(0);
}


今回の sum_of_squares.cpp を合成した。結果を示す。
sum_of_squares_14_190821.png

PIPELINE の Initiation Interval が 1 クロックになった。これで完璧だ。
Verilog HDL のトップのファイル sum_of_squares.v の module 定義を引用すると、 x , y , result の 3 個のAXI4 インターフェースが実装されているのが分かる。

module sum_of_squares (
        ap_clk,
        ap_rst_n,
        m_axi_x_AWVALID,
        m_axi_x_AWREADY,
        m_axi_x_AWADDR,
        m_axi_x_AWID,
        m_axi_x_AWLEN,
        m_axi_x_AWSIZE,
        m_axi_x_AWBURST,
        m_axi_x_AWLOCK,
        m_axi_x_AWCACHE,
        m_axi_x_AWPROT,
        m_axi_x_AWQOS,
        m_axi_x_AWREGION,
        m_axi_x_AWUSER,
        m_axi_x_WVALID,
        m_axi_x_WREADY,
        m_axi_x_WDATA,
        m_axi_x_WSTRB,
        m_axi_x_WLAST,
        m_axi_x_WID,
        m_axi_x_WUSER,
        m_axi_x_ARVALID,
        m_axi_x_ARREADY,
        m_axi_x_ARADDR,
        m_axi_x_ARID,
        m_axi_x_ARLEN,
        m_axi_x_ARSIZE,
        m_axi_x_ARBURST,
        m_axi_x_ARLOCK,
        m_axi_x_ARCACHE,
        m_axi_x_ARPROT,
        m_axi_x_ARQOS,
        m_axi_x_ARREGION,
        m_axi_x_ARUSER,
        m_axi_x_RVALID,
        m_axi_x_RREADY,
        m_axi_x_RDATA,
        m_axi_x_RLAST,
        m_axi_x_RID,
        m_axi_x_RUSER,
        m_axi_x_RRESP,
        m_axi_x_BVALID,
        m_axi_x_BREADY,
        m_axi_x_BRESP,
        m_axi_x_BID,
        m_axi_x_BUSER,
        m_axi_y_AWVALID,
        m_axi_y_AWREADY,
        m_axi_y_AWADDR,
        m_axi_y_AWID,
        m_axi_y_AWLEN,
        m_axi_y_AWSIZE,
        m_axi_y_AWBURST,
        m_axi_y_AWLOCK,
        m_axi_y_AWCACHE,
        m_axi_y_AWPROT,
        m_axi_y_AWQOS,
        m_axi_y_AWREGION,
        m_axi_y_AWUSER,
        m_axi_y_WVALID,
        m_axi_y_WREADY,
        m_axi_y_WDATA,
        m_axi_y_WSTRB,
        m_axi_y_WLAST,
        m_axi_y_WID,
        m_axi_y_WUSER,
        m_axi_y_ARVALID,
        m_axi_y_ARREADY,
        m_axi_y_ARADDR,
        m_axi_y_ARID,
        m_axi_y_ARLEN,
        m_axi_y_ARSIZE,
        m_axi_y_ARBURST,
        m_axi_y_ARLOCK,
        m_axi_y_ARCACHE,
        m_axi_y_ARPROT,
        m_axi_y_ARQOS,
        m_axi_y_ARREGION,
        m_axi_y_ARUSER,
        m_axi_y_RVALID,
        m_axi_y_RREADY,
        m_axi_y_RDATA,
        m_axi_y_RLAST,
        m_axi_y_RID,
        m_axi_y_RUSER,
        m_axi_y_RRESP,
        m_axi_y_BVALID,
        m_axi_y_BREADY,
        m_axi_y_BRESP,
        m_axi_y_BID,
        m_axi_y_BUSER,
        m_axi_result_AWVALID,
        m_axi_result_AWREADY,
        m_axi_result_AWADDR,
        m_axi_result_AWID,
        m_axi_result_AWLEN,
        m_axi_result_AWSIZE,
        m_axi_result_AWBURST,
        m_axi_result_AWLOCK,
        m_axi_result_AWCACHE,
        m_axi_result_AWPROT,
        m_axi_result_AWQOS,
        m_axi_result_AWREGION,
        m_axi_result_AWUSER,
        m_axi_result_WVALID,
        m_axi_result_WREADY,
        m_axi_result_WDATA,
        m_axi_result_WSTRB,
        m_axi_result_WLAST,
        m_axi_result_WID,
        m_axi_result_WUSER,
        m_axi_result_ARVALID,
        m_axi_result_ARREADY,
        m_axi_result_ARADDR,
        m_axi_result_ARID,
        m_axi_result_ARLEN,
        m_axi_result_ARSIZE,
        m_axi_result_ARBURST,
        m_axi_result_ARLOCK,
        m_axi_result_ARCACHE,
        m_axi_result_ARPROT,
        m_axi_result_ARQOS,
        m_axi_result_ARREGION,
        m_axi_result_ARUSER,
        m_axi_result_RVALID,
        m_axi_result_RREADY,
        m_axi_result_RDATA,
        m_axi_result_RLAST,
        m_axi_result_RID,
        m_axi_result_RUSER,
        m_axi_result_RRESP,
        m_axi_result_BVALID,
        m_axi_result_BREADY,
        m_axi_result_BRESP,
        m_axi_result_BID,
        m_axi_result_BUSER,
        s_axi_AXILiteS_AWVALID,
        s_axi_AXILiteS_AWREADY,
        s_axi_AXILiteS_AWADDR,
        s_axi_AXILiteS_WVALID,
        s_axi_AXILiteS_WREADY,
        s_axi_AXILiteS_WDATA,
        s_axi_AXILiteS_WSTRB,
        s_axi_AXILiteS_ARVALID,
        s_axi_AXILiteS_ARREADY,
        s_axi_AXILiteS_ARADDR,
        s_axi_AXILiteS_RVALID,
        s_axi_AXILiteS_RREADY,
        s_axi_AXILiteS_RDATA,
        s_axi_AXILiteS_RRESP,
        s_axi_AXILiteS_BVALID,
        s_axi_AXILiteS_BREADY,
        s_axi_AXILiteS_BRESP,
        interrupt
);


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

Latency は 84 クロックに減っている。

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

result の DMA Write と y の DMA Read の波形だ。 y の DMA Read は ARLEN が 3 になっていてバーストで Read しているのが分かる。 x も波形がここにはないが同様にバースト Read してる。

これで、このインターフェースでのチューニングは終了だが、3 個のAXI4 インターフェースが実装されているのがもったいないと感じる。オーバースペックじゃないかな?
x と y は 8 ビットなので、バス幅 32 ビットの内の 8 ビットを 2 個使えば良いのではないか?というコンセプトで次回の検討を行おう。
  1. 2019年08月21日 05:04 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

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

Vivado HLS で 2 つの入力のAXI4 Master モジュールを作ってみよう。題材は平方数の和を求めるコードだ。

まずは、ap_int 型を使って作ってみよう。
Source は sum_of_squares.cpp とした。

#include <ap_int.h>

int sum_of_squares(volatile ap_int<8> *x, volatile ap_int<8> *y, volatile ap_int<17> *result){
#pragma HLS INTERFACE m_axi depth=10 port=y offset=slave
#pragma HLS INTERFACE m_axi depth=10 port=x 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] = x[i]*x[i] + y[i]*y[i];
    }

    return(0);
}


Testbench は sum_of_squares_tb.cpp とした。

// sum_of_squares_tb.cpp

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

int sum_of_squares(volatile ap_int<8> *x, volatile ap_int<8> *y, volatile ap_int<17> *result);

int main(){
    ap_int<8> x[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    ap_int<8> y[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    ap_int<17> result[10];

    sum_of_squares(x, y, result);

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


Vivado HLS 2018.3 で sum_of_squares プロジェクトを作成した。使用するFPGA は xc7z010clg400-1 のZYBO Z7-10 を想定している。
sum_of_squares_1_190820.png

C シミュレーションをするとエラーになった。
sum_of_squares_2_190820.png

最初のエラーを貼る。

../../../sum_of_squares.cpp: 関数 ‘int sum_of_squares(volatile ap_int<8>*, volatile ap_int<8>*, volatile ap_int<17>*)’ 内:
../../../sum_of_squares.cpp:10:19: エラー: no match for ‘operator*’ (operand types are ‘volatile ap_int<8>’ and ‘volatile ap_int<8>’)
   result[i] = x[i]*x[i] + y[i]*y[i];


なんと乗算の定義が無い様だ。
なお、”高位合成ユーザーズ ガイド UG902 (v2018.3) 2018 年 12 月 20 日”の 90 ページに

値が複数回更新されるデータ信号を指定するには、volatile 修飾子を使用してください。

と書いてあるので、volatile は必要だ。

エラーを回避するにはどうするかというと、char と int に引数の方を変えてみよう。
sum_of_squares.cpp を示す。

int sum_of_squares(volatile char *x, volatile char *y, volatile int *result){
#pragma HLS INTERFACE m_axi depth=10 port=y offset=slave
#pragma HLS INTERFACE m_axi depth=10 port=x 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] = x[i]*x[i] + y[i]*y[i];
    }

    return(0);
}


sum_of_squares_tb.cpp を示す。

// sum_of_squares_tb.cpp

#include <iostream>

int sum_of_squares(volatile char *x, volatile char *y, volatile int *result);

int main(){
    char x[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    char y[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int result[10];

    sum_of_squares(x, y, result);

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


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

今度は成功した。”高位合成ユーザーズ ガイド UG902 (v2018.3) 2018 年 12 月 20 日”のAXI4 Master のサンプルの引数の型が int なのはこのような理由なのだろうか?

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

AXI4 Lite Slave のアドレスマップを示す。

//------------------------Address Info-------------------
// 0x00 : Control signals
//        bit 0  - ap_start (Read/Write/COH)
//        bit 1  - ap_done (Read/COR)
//        bit 2  - ap_idle (Read)
//        bit 3  - ap_ready (Read)
//        bit 7  - auto_restart (Read/Write)
//        others - reserved
// 0x04 : Global Interrupt Enable Register
//        bit 0  - Global Interrupt Enable (Read/Write)
//        others - reserved
// 0x08 : IP Interrupt Enable Register (Read/Write)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x0c : IP Interrupt Status Register (Read/TOW)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x10 : Data signal of ap_return
//        bit 31~0 - ap_return[31:0] (Read)
// 0x18 : Data signal of x
//        bit 31~0 - x[31:0] (Read/Write)
// 0x1c : reserved
// 0x20 : Data signal of y
//        bit 31~0 - y[31:0] (Read/Write)
// 0x24 : reserved
// 0x28 : Data signal of result
//        bit 31~0 - result[31:0] (Read/Write)
// 0x2c : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


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

0x18 番地(x のアドレス)には 0 、0x20 番地(y のアドレス)には 0xC 、 0x28 番地(result のアドレス)には 0x18 を書いている。
C/RTL 協調シミュレーションの波形を拡大した図を示す。
sum_of_squares_6_190820.png

AXI4 Master Read は、ARLEN が 00 なので単発のアクセス、AXI4 Master Write は AWLEN が 09 なので、バースト・アクセスとなる。
x の値は 2 回 Read されている。 y の値も 2 回 Read されているようだ。

次に、sum_of_squares.cpp の for 文に PIPELINE 指示子を追加してみよう。

int sum_of_squares(volatile char *x, volatile char *y, volatile int *result){
#pragma HLS INTERFACE m_axi depth=10 port=y offset=slave
#pragma HLS INTERFACE m_axi depth=10 port=x offset=slave
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE m_axi depth=10 port=result offset=slave

    LOOP1: for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        result[i] = x[i]*x[i] + y[i]*y[i];
    }

    return(0);
}


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

約 1/3 程度のLatency になった。

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

C/RTL 協調シミュレーションの波形を拡大した図を示す。
sum_of_squares_9_190820.png

1 回の演算を行うのに、合計 40 回 Read している。 write は 10 回だ。

それじゃ、このRead のアクセス回数を 1/2 にしてみよう。
Source の sum_of_squares.cpp のコードを以下のように変更した。

int sum_of_squares(volatile char *x, volatile char *y, volatile int *result){
#pragma HLS INTERFACE m_axi depth=10 port=y offset=slave
#pragma HLS INTERFACE m_axi depth=10 port=x offset=slave
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE m_axi depth=10 port=result offset=slave

    LOOP1: for(int i=0; i<10; i++){
#pragma HLS PIPELINE II=1
        char xt = x[i];
        char yt = y[i];

        result[i] = xt*xt + yt*yt;
    }

    return(0);
}


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

Initiation Interval が 4 クロックから 2 クロックになった。

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

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

Read が 20 回になった。
  1. 2019年08月20日 05:27 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

VGA画像をXGA画像やHD画像に変換するdisp_dmar_axis_vga IP 5(やっと完成2)

VGA画像をXGA画像やHD画像に変換するdisp_dmar_axis_vga IP 4(やっと完成1)”の続き。

前回は、風呂掃除をしていた時にふとひらめいた。いままで画像を拡張しない時はうまく行っていたdisp_dmar_axis IP があるのだから、それにHLSストリーム・インターフェースで画像を拡大するIP を付けたらどうか?そして、それらを並列化したら良いのでは?というアイデアだった。ということで、ソースコードを貼った。今回は、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行った。

まずは、現在のdisp_dmar_axis_vga プロジェクトを示す。
disp_dmar_axis_vga_10_190715.png

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

dmar_result.bmp も問題ない。
disp_dmar_axis_vga_12_190715.png

disp_dmar_axis_vga_9_190713.jpg

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

これも良さそうだ。

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

Latency は 480036 クロックだった。これは、出力しているSVGA 画像の 800 x 600 = 480000 クロックとほぼ等しい。これは、良い結果が期待できそうだ。

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

m_axi_gmem_ARLEN を見ると、0f となっていて、16 バーストであることが分かる。
outs_TREADY と outs_TVALID もほとんど 1 固定だ。これは良い。

拡大した。
disp_dmar_axis_vga_17_190716.png

最後にExport RTL を行った。
disp_dmar_axis_vga_15_190715.png

CP achieved post-implementation は 3.305 ns で問題無さそうだ。

やはり、データを加工するDMA をVivado HLS で書く時は、シンプルなDMA してデータをHLSストリーム出力する関数と、HLSストリーム入出力のデータ加工関数を用意して、トップの関数でそれらを接続し、DATAFLOW 指示子で並列動作するように記述するのが賢いようだ。
  1. 2019年07月16日 04:59 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

VGA画像をXGA画像やHD画像に変換するdisp_dmar_axis_vga IP 4(やっと完成1)

VGA画像をXGA画像やHD画像に変換するdisp_dmar_axis_vga IP 3(2 種類の実装を試した)”の続き。

前回は、disp_dmar_axis IP 用のソースコードを 2 種類作って試してみたが、うまく行かなかった。今回は、風呂掃除をしていた時にひらめいたdisp_dmar_axis IP の実装を試してみよう。

風呂掃除をしていた時にふとひらめいた。いままで画像を拡張しない時はうまく行っていたdisp_dmar_axis IP があるのだから、それにHLSストリーム・インターフェースで画像を拡大するIP を付けたらどうか?そして、それらを並列化したら良いのでは?というアイデアだった。やはり、風呂掃除はアイデアが湧くのでお勧めである。皆さん、風呂掃除をしましょう。。。w

さて、今回はファイルも分けて、VGA画像を 3 チャネル分DMA するのが、vga_dmar_axis.cpp で、HLSストリーム・インターフェースで画像を拡大するIP が axis_expand.cpp (ちょっと名前をミスった)、全体をまとめるのが、disp_dmar_axis_vga.cpp となる。

disp_dmar_axis_vga.cpp のソースコードを示す。今回はインターフェースを変更して、row, col つまり画像の縦横のピクセル数を入力するようにした。

// disp_dmar_axis_vga.cpp
// 2019/07/11 by marsee
//

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

#include "disp_dmar_axis_vga.h"

int vga_dmar_axis(volatile ap_int<32> *fb0, volatile ap_int<32> *fb1, volatile ap_int<32> *fb2,
        AXI_STREAM &outs, ap_uint<2> active_frame);

int axis_expand(AXI_STREAM &ins, AXI_STREAM &outs, int row, int col);

int disp_dmar_axis_vga(volatile ap_int<32> *fb0, volatile ap_int<32> *fb1, volatile ap_int<32> *fb2,
        AXI_STREAM &outs, ap_uint<16> row, ap_uint<16> col, ap_uint<2> &active_frame){
#pragma HLS DATAFLOW
#pragma HLS INTERFACE ap_ctrl_hs port=return
#pragma HLS INTERFACE ap_none port=active_frame
#pragma HLS INTERFACE s_axilite port=row
#pragma HLS INTERFACE s_axilite port=col
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE m_axi depth=307200 port=fb2 offset=slave
#pragma HLS INTERFACE m_axi depth=307200 port=fb1 offset=slave
#pragma HLS INTERFACE m_axi depth=307200 port=fb0 offset=slave

    AXI_STREAM dmad;
#pragma HLS STREAM variable=dmad depth=64 dim=1

    vga_dmar_axis(fb0, fb1, fb2, dmad, active_frame);
    axis_expand(dmad, outs, row, col);
    
    return(0);
}


次に、vga_dmar_axis.cpp を示す。

// vga_dmar_axis.cpp
// 2019/07/13 by marsee
//

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

#include "disp_dmar_axis_vga.h"

int disp_dmar_fb0(volatile ap_int<32> *fb0, AXI_STREAM &outs, int max_width, int max_height);
int disp_dmar_fb1(volatile ap_int<32> *fb1, AXI_STREAM &outs, int max_width, int max_height);
int disp_dmar_fb2(volatile ap_int<32> *fb2, AXI_STREAM &outs, int max_width, int max_height);

int vga_dmar_axis(volatile ap_int<32> *fb0, volatile ap_int<32> *fb1, volatile ap_int<32> *fb2,
        AXI_STREAM &outs, ap_uint<2> active_frame){

    AP_AXIU32 pix;

    if (active_frame == (ap_uint<2>)0)
        disp_dmar_fb2(fb2, outs, VGA_WIDTH, VGA_HEIGHT);
    else if (active_frame == (ap_uint<2>)1)
        disp_dmar_fb0(fb0, outs, VGA_WIDTH, VGA_HEIGHT);
    else
        disp_dmar_fb1(fb1, outs, VGA_WIDTH, VGA_HEIGHT);
    return(0);
}

int disp_dmar_fb0(volatile ap_int<32> *fb0, AXI_STREAM &outs, int max_width, int max_height){
    AP_AXIU32 pix;

    LOOP_Y0: for (int y=0; y<max_height; y++){
#pragma HLS LOOP_TRIPCOUNT min=480 max=480 avg=480
        LOOP_X0: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=640 max=640 avg=640
#pragma HLS PIPELINE II=1
            pix.data = fb0[(y*max_width)+x];

            if (x==0 && y==0)
                pix.user = 1;
            else
                pix.user = 0;

            if (x == max_width-1)
                pix.last = 1;
            else
                pix.last = 0;

            outs << pix;
        }
    }
    return(0);
}

int disp_dmar_fb1(volatile ap_int<32> *fb1, AXI_STREAM &outs, int max_width, int max_height){
    AP_AXIU32 pix;

    LOOP_Y1: for (int y=0; y<max_height; y++){
#pragma HLS LOOP_TRIPCOUNT min=480 max=480 avg=480
        LOOP_X1: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=640 max=640 avg=640
#pragma HLS PIPELINE II=1
            pix.data = fb1[(y*max_width)+x];

            if (x==0 && y==0)
                pix.user = 1;
            else
                pix.user = 0;

            if (x == max_width-1)
                pix.last = 1;
            else
                pix.last = 0;

            outs << pix;
        }
    }
    return(0);
}

int disp_dmar_fb2(volatile ap_int<32> *fb2, AXI_STREAM &outs, int max_width, int max_height){
    AP_AXIU32 pix;

    LOOP_Y2: for (int y=0; y<max_height; y++){
#pragma HLS LOOP_TRIPCOUNT min=480 max=480 avg=480
        LOOP_X2: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=640 max=640 avg=640
#pragma HLS PIPELINE II=1
            pix.data = fb2[(y*max_width)+x];

            if (x==0 && y==0)
                pix.user = 1;
            else
                pix.user = 0;

            if (x == max_width-1)
                pix.last = 1;
            else
                pix.last = 0;

            outs << pix;
        }
    }
    return(0);
}


axis_expand.cpp を示す。

// axis_expand.cpp
// 2019/07/13 by marsee
//


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

#include "disp_dmar_axis_vga.h"

int axis_expand(AXI_STREAM &ins, AXI_STREAM &outs, int row, int col){
    AP_AXIU32 pix;
    int x_padding = (col - VGA_WIDTH)/2;
    int y_padding = (row - VGA_HEIGHT)/2;

    for(int y=0; y<row; y++){
#pragma HLS LOOP_TRIPCOUNT min=600 max=1080 avg=768
        for(int x=0; x<col; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            if (y < y_padding || y >= y_padding+VGA_HEIGHT){
                pix.data = 0;
                pix.dest = 0;
                pix.id = 0;
                pix.keep = 0;
                pix.strb = 0;
            }else if (x < x_padding || x >= x_padding+VGA_WIDTH){
                pix.data = 0;
                pix.dest = 0;
                pix.id = 0;
                pix.keep = 0;
                pix.strb = 0;
            }else{
                ins >> pix;
            }

            if (x==0 && y==0)
                pix.user = 1;
            else
                pix.user = 0;

            if (x == col-1)
                pix.last = 1;
            else
                pix.last = 0;

            outs << pix;
        }
    }
    return(0);
}


前と同じだが、disp_dmar_axis_vga.h を示す。


// disp_dmar_axis_vga.h
// 2019/07/11 by marsee
//

#ifndef __DISP_DMAR_AXIS_VGA_H__
#define __DISP_DMAR_AXIS_VGA_H__

#include "ap_axi_sdata.h"
#include "hls_video.h"

#define VGA_WIDTH 640
#define VGA_HEIGHT 480

#define SVGA_WIDTH 800
#define SVGA_HEIGHT 600

#define XGA_WIDTH 1024
#define XGA_HEIGHT 768

#define HD_WIDTH 1920
#define HD_HEIGHT 1080

#define RESO_SVGA 0
#define RESO_XGA 1
#define RESO_HD  2

typedef hls::stream<ap_axiu<32,1,1,1> > AXI_STREAM;
typedef ap_axiu<32,1,1,1> AP_AXIU32;
typedef hls::Scalar<3, unsigned char> RGB_PIXEL;
typedef hls::Mat<HD_HEIGHT, HD_WIDTH, HLS_8UC3> RGB_IMAGE;
typedef hls::Mat<HD_HEIGHT, HD_WIDTH, HLS_8UC1> GRAY_IMAGE;

#endif


最後にbmp_file0.bmp だが、ブログに貼れないのでPNG ファイルを示す。
disp_dmar_axis_vga_8_190712.png
  1. 2019年07月15日 05:15 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0
»