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

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

FPGAの部屋

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

Vivado HLS の符号付 C++の任意精度固定小数点型について

Vivado HLS の符号付 C++の任意精度固定小数点型について疑問があったので調べてみた。
Vivado HLS の C++の任意精度固定小数点型は、C++のテンプレートを使用して、ap_[u]fixed で宣言される。(Xilinx社のUser Guide 高位合成 UG902 (v2016.4) 2016年11月30日 192 ページ「C++ の任意精度固定小数点型」参照)

W :ワード長(ビット数)
I :整数部のビット長
Q :量子化モード
O :オーバーフローモード
N :折り返しモードでの飽和ビット数


符号なしC++の任意精度固定小数点型 ap_ufixed<> は問題ないが、符号付C++の任意精度固定小数点型 ap_fixed<>で整数部を1ビットにした場合は符号ビットを含むのか含まないのか?が疑問だった。
そこで、Vivado HLS 2017.1 で ap_fixed_test プロジェクトを作成して、テストしてみた。
ap_fixed_test プロジェクトを示す。
ap_fixed_test_1_170529.png

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

// ap_fixed_test_tb.cpp
// 2017/04/29
//

#include <stdio.h>
#include <ap_fixed.h>

int main(){
    ap_fixed<91, AP_TRN_ZERO, AP_SAT> a;
    float f;

    a = 0.5;
    f = a;
    printf("0.5: %f\n", f);
    a = 1.0;
    f = a;
    printf("1.0: %f\n", f);
    a = -1.0;
    f = a;
    printf("-1.0: %f\n", f);
    a = -0.5;
    f = a;
    printf("-0.5: %f\n", f);
    a = 2.0;
    f = a;
    printf("2.0: %f\n", f);
    a = -1.5;
    f = a;
    printf("-1.5: %f\n", f);
    a = -0.9;
    f = a;
    printf("-0.9: %f\n", f);
}


このテストベンチのC シミュレーション結果はすでにVivado HLS のプロジェクトの図に表示されているが、下に示す。

0.5: 0.500000
1.0: 0.996094
-1.0: -1.000000
-0.5: -0.500000
2.0: 0.996094
-1.5: -1.000000
-0.9: -0.898438


この結果から、整数部のビット長を 1 とすると、1.0 - 量子化ステップ値 ~ -1.0 までの値を取れるようだ。つまり整数部は符号を表すビットのみの 1 ビットだけということになる。
  1. 2017年05月29日 15:05 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLSで stdint.h を使用する

FPGAマガジンNo16 のAXI4 Master のソースコードと、FPGAマガジン No.17 のAXI4-Stream のソースコードを公開しているが、taksei さんから「ソースコードを64bit Linux版VivadoでC-Sim実行してみたのですが、long型が8バイトとして確保されるため、BMPファイルのリード・ライトに失敗するようです。」との報告があった。long型で4バイトのところ、8バイトになっていたようだ。
そこで、taksei さんに教えて頂いたように、stdint.h を使用して、uintX_t や intX_t を使用することにした。taksei さん、ありがとうございました。

私もVirtualBox 上に64ビット版のUbuntu 16.04 をインストールしてあって、その上にVivado 2016.4 をインストールしてある。よって、それ上のVivado HLS 2016.4 で確かめてみよう。

まずは、64ビット版Ubuntu 16.04 の Vivado HLS 2016.4 を起動した。
まずは、以前のままの bitmap_header.h でやってみよう。
stdint_1_170505.png

これで C シミュレーションを行った。
stdint_2_170505.png

”Can't allocate rd_bmp memory”エラーになってしまった。
taksei さんによると、”stdint.h” を使えば良いらしい。
”stdint.h”は、少なくとも 8, 16, 32, 64 ビット長の intN_t, uintN_t という型を宣言してるようだ。つまり、ビット長が明確に定義されている。
これを使用して、BMPのフォーマットに関しての資料を参考に、 intN_t, uintN_t で bitmap_header.h を書き直してみよう。実際には、taksei さんのコードを検証しながら、コピー&ペーストを行った。
更に、lap_filter_tb.c にもBMPのフォーマットがあるので、それも同様に、 intN_t, uintN_t に修正した。
stdint_3_170505.png

stdint_4_170505.png

修正後に C シミュレーションを行ったところ、今度は成功した。
stdint_5_170505.png

C コードの合成結果を示す。Windows 10 での結果と同じレイテンシだった。
stdint_6_170505.png

C/RTL協調シミュレーション結果を示す。これも、Windows 10 での結果と同じだった。
stdint_7_170505.png

lap_filter0_axim/solution1/sim/wrap_pc ディレクトリを見ると、test_lap.bmp が出来て、正常にラプラシアンフィルタ処理が行われているのが分かる。
stdint_8_170505.png

Export RTL も正常に行うことができた。
stdint_9_170505.png

更新した bmp_header.h, lap_filter_tb.c を Windows 10 のVivado HLS 2017.1 に持って行っても正常にシミュレーション、合成を行うことができた。
stdint_10_170505.png

OS によって、int, long の型のビット長は変化するので、ビット長が決まっている変数を宣言する場合は、stdint.h の intN_t, uintN_t を使用して書いたほうが良い。
  1. 2017年05月05日 16:25 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

AXI4マスタ版ラプラシアン・フィルタ最速の条件

この記事はFPGAマガジンNo.17 に載るはずだったけど、ページが足りなくて載せることができなかったコラムです。

今回、「高位合成ツールVivado HLS 特設記事」第2章で説明した第5段階のAXI4マスタ版のラプラシアン・フィルタですが、その最速の条件を探すためにCソースコードを少しずつ変更して最速の条件を確かめてみました。

AXI4マスタ・ライトのみにif文を使用した場合
まずは、出力の画像フォーマットを出力画像の回り1ビットが0のフォーマットに戻してみましょう。つまり、「高位合成ツールVivado HLS 特設記事」第1章、図23の左の出力画像フォーマットにしてみようと思います。特設記事第2章のリスト1のlap変数をlap_fb[]にライトする部分を少し変更します。特設記事第2章のリスト1では、lap_fp[]にライトするときにifなどの条件文は使用していませんでしたが、今回はif文を使用します。具体的には、変数yが0のとき(つまり最初の行)は、lapの値は0に設定しているので、lap_fb[]の最後の行に書き込むことにします。変数yが1以降のとき(2番目以降の行)では、lap_fb[]の最初の行から順に書き込みます。この部分を抜き出してリスト1 に示します。

             if (y == 0)
                lap_fb[(VERTICAL_PIXEL_WIDTH-1)*HORIZONTAL_PIXEL_WIDTH+x] = lap;
            else
                lap_fb[(y-1)*HORIZONTAL_PIXEL_WIDTH+x] = lap;

リスト 1:  lap_fb[]のライト部分の変更点


Cコードの合成のLatencyは480019クロックでした。なお、今回の画像は800ピクセルx 600行の画像を使用しています。つまり、1ピクセル1クロックとして全てのピクセルを処理すると480000クロック必要です。それと比較するとCコード合成時のLatencyは効率が良いと言えると思います。次にC/RTL協調シミュレーションを行うと、Latencyは1120051クロックでした。Cコードの合成のときのLatencyからすると2倍以上にレイテンシが増えています。C/RTL協調シミュレーション波形を見ると、リードは16バーストなのですが、ライトはシングル転送になっていました。このためC/RTL協調シミュレーション時のレイテンシが増えてしまったようです。詳しくは、「AXI4-Stream向きのコードで書いたラプラシアンフィルタをVivado HLSでテスト2」をご覧ください。

AXI4マスタのリードとライト両方にif文を使用した場合
次に、出力の画像フォーマットを出力画像の回り1ビットが0のフォーマットに戻すのは同じですが、1行余計にループを回すことにします。最初の行はlap_fb[]に書き込まないようにし、最後の行(つまり、1行多いので、リードするピクセルはもう残っていないため)cam_fb[]から読み込まないようにします。つまり、リードもライトもif文が入ることになります。リード部分の変更点をリスト2 にライト部分の変更点をリスト3 に示します。

    for (int y=0; y<=VERTICAL_PIXEL_WIDTH; y++){
        for (int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE
            if (y < VERTICAL_PIXEL_WIDTH)
                pix = cam_fb[y*HORIZONTAL_PIXEL_WIDTH+x];

リスト 2:  cam_fb[]のリード部分の変更点


            if (y != 0)
                lap_fb[(y-1)*HORIZONTAL_PIXEL_WIDTH+x] = lap;

リスト 3:  lap_fb[]のライト部分の変更点


この実装では、Cコードの合成時のLatencyは480821クロックでした。この実装でも、画像は800ピクセルx 600行の画像を使用しています。次にC/RTL協調シミュレーションを行ってみたところ、1121430クロックでした。この実装でも、Cコードの合成のときのLatencyからすると2倍以上にレイテンシが増えています。しかも、「AXI4マスタ・ライトのみにif文を使用した場合」よりも遅くなってしまいました。C/RTL協調シミュレーション波形を見ると、リードもライトもシングル転送でした。詳しくは、「AXI4-Stream向きのコードで書いたラプラシアンフィルタをVivado HLSでテスト3」をご覧ください。

2つの実装の結果から言えることは、メモリにリード、ライトする記述にif文があるとシングル転送になるのではないか?ということです。つまり、AXI4マスタ版で最速実装ができる幅は狭いということが言えると思います。
  1. 2017年04月26日 20:30 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

FPGAマガジンNo.17にVivado HLS の記事を書きました(ソースコードをGitHubで公開)

FPGAマガジンNo.17 の 80 ページから「高位合成ツールVivado HLS 特設記事」ということで、記事を書きました。
なお、その記事中に使用したC や C++ のソースコードをGitHubで公開しました。

第1章
【データが順番に流れるならストリームが最適】
無料のVivado HL WebPACK Edition 2016.4で高位合成にチャレンジしよう(AXI4ストリーム編)
AXI4ストリームによるラプラシアン・フィルタ処理回路の実装

でVivado HLS のAXI4-Stream編として書きました。これが本命だと思っています。このようにデータのストリーミング中に処理を行うという方法がFPGA に合っていると言いますか、これができないようだとCPU やGPU に処理速度で負けちゃうんじゃないでしょうか?というのが私の意見です。
このソースコードは、marsee101/FPGAmagagine17_Vivado_HLS_AXI4S にアップロードしてあります。

第2章
【これ以上の高速化は難しい?!】
無料のVivado HL WebPACK Edition 2016.4で高位合成にチャレンジしよう(AXI4ストリーム編)
ついに80倍高速化!AXI4マスタ版ラプラシアン・フィルタの最適化

第2章ではVivado HLS のAXI4 Master 編(FPGAマガジンNo.16)でアルゴリズムの通りに実装したラプラシアンフィルタ回路と最適化を行ったラプラシアンフィルタ回路の間の性能差は約 25 倍でしたが、約 80 倍の性能のAXI4 Master のラプラシアンフィルタ回路ができたので、ご紹介しました。もう原理的に限界の性能を出すことができました。記事のタイトルが誤解を招くかもしれませんが、これはAXI4-Stream インターフェースではなく、AXI4 インターフェースのMaster です。AXI4-Stream 版とい同じように書いたら最速になった、AXI4 インターフェースのMaster 版のラプラシアンフィルタ回路の話です。よろしくお願いします。
まあ、本当はどのように書いても同じように最速だったら良いのですが、ソースコードの意図を回路に反映するのか?しないのか?という話もあるので、一概には言えないと思います。ソースコードの意図を無視して、いつも最速にしちゃってよいのか?という話はありますよね。。。
この最速のAXI4 Master のソースコードは marsee101/FPGAmagagine16_Vivado_HLS_AXI4M/lap_filter5_axim/ にあります。
  1. 2017年04月26日 04:57 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS で DMA Read IP を作る3(オフセット・アドレス指定版)

Vivado HLS で DMA Read IP を作る2(絶対アドレス指定版)”の続き。

ついでに作ったので書いておこう。
Vivado HLS で DMA Read IP を作る2(絶対アドレス指定版)”は絶対アドレスを3つ指定して、DMA Read を行うが、DMA Read IP のオフセット・アドレス指定版はベース・アドレスを指定し、ベースアドレスに対してのオフセット・アドレスを3つ指定してDMA Readするアドレスを決定する。ただし、C のソースコードはm_axi 指示子の depth と offset オプションのみが違っている。
depth は本来の値に設定する。そして、offset=slave とする必要がある。offset=slave とすると、ベース・アドレスがAXI4-Lite のレジスタとして実装される。
なおターゲットとしてはZYBO ボードとする。

Vivado HLS 2016.4 の DMA_Read_offset プロジェクトを示す。
DMA_Read_offset_1_170418.png

C シミュレーションを行った。成功した。
DMA_Read_offset_2_170418.png

C コードの合成を行った。結果を示す。
DMA_Read_offset_3_170418.png
ベース・アドレスのレジスタが増えているだけに、オフセット・アドレス版のほうがリソース使用量が多い。
アドレス・マップを示す。

//------------------------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 in_r
//        bit 31~0 - in_r[31:0] (Read/Write)
// 0x1c : reserved
// 0x20 : Data signal of fb0_offset_addr
//        bit 31~0 - fb0_offset_addr[31:0] (Read/Write)
// 0x24 : reserved
// 0x28 : Data signal of fb1_offset_addr
//        bit 31~0 - fb1_offset_addr[31:0] (Read/Write)
// 0x2c : reserved
// 0x30 : Data signal of fb2_offset_addr
//        bit 31~0 - fb2_offset_addr[31:0] (Read/Write)
// 0x34 : reserved
// 0x38 : Data signal of mode_V
//        bit 0  - mode_V[0] (Read/Write)
//        others - reserved
// 0x3c : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


DMAのベース・アドレスのレジスタ”0x18 : Data signal of in_r”があるのが分かる。

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

C/RTL協調シミュレーション波形を示す。3つのDMA Readが見えている。
DMA_Read_offset_5_170418.png
絶対アドレス版と違って、オフセット・アドレス版は dma_result0.bmp、dma_result1.bmp、dma_result2.bmp が正常に”A”になっている。

DMA_Read_offset_6_170418.png

Export RTL を行った。
DMA_Read_offset_7_170418.png

DMA_Read.h を示す。

// DMA_Read.h
// 2016/07/14 by marsee
// 2016/09/18 : BURST_LENGTH を追加
//

#ifndef __DMA_READ_H__
#define __DMA_READ_H__

//#define HORIZONTAL_PIXEL_WIDTH    800
//#define VERTICAL_PIXEL_WIDTH    600

#define HORIZONTAL_PIXEL_WIDTH    64
#define VERTICAL_PIXEL_WIDTH    48

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define MAX_FRAME_NUMBER    3

#define DMA_WRITE_MODE    0
#define FREE_RUN_MODE    1

#define MEMCPY_LENGTH    (HORIZONTAL_PIXEL_WIDTH*4)

#endif


DMA_Read_offset.cpp を示す。

// DMA_Read_offset.cpp
// 2017/04/17 by marsee
//
// fb0_offset_addr, fb1_offset_addr, fb2_offset_addr には3つのフレームバッファのオフセット・アドレスを入れる
// mode = 0 : DMA Write IP の active_frame を見て、その1つ前のフレームをDMA Readするモード(DMA_WRITE_MODE)
// mode = 1 : フリーラン モード(FREE_RUN_MODE)
//
// カメラのフレームレートの方が小さい場合に、フリーランモードで3つフレームバッファが別々だと画像がぶれてしまう。
// その場合は、3つのフレームバッファ・アドレスを1つのアドレスを入れて、シングル・フレームバッファとする必要がある。

// 1フレーム分のDMAを行う
//

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

#include "DMA_Read.h"

int DMA_Read_offset(volatile int *in, hls::stream<ap_axis<32,1,1,1> >& outs,
        unsigned int fb0_offset_addr, unsigned int fb1_offset_addr,
        unsigned int fb2_offset_addr, ap_uint<2> & active_frame,
        ap_uint<1> mode){
#pragma HLS INTERFACE s_axilite port=mode
#pragma HLS INTERFACE ap_none register port=active_frame
#pragma HLS INTERFACE s_axilite port=fb0_offset_addr
#pragma HLS INTERFACE s_axilite port=fb1_offset_addr
#pragma HLS INTERFACE s_axilite port=fb2_offset_addr
#pragma HLS INTERFACE m_axi depth=9216 port=in offset=slave
#pragma HLS INTERFACE axis port=outs
#pragma HLS INTERFACE s_axilite port=return

    ap_axis<32,1,1,1> pix;
    int dma_index;
    static int n = 0;

    if (mode == DMA_WRITE_MODE){
        n = (int)active_frame;
    }else{
        n++;
        if (n > 2)
            n = 0;
    }

    switch (n){ // 1つ前のフレームバッファを読みだす
        case 0 :
            dma_index = fb2_offset_addr/sizeof(int);
            break;
        case 1 :
            dma_index = fb0_offset_addr/sizeof(int);
            break;
        case 2 :
            dma_index = fb1_offset_addr/sizeof(int);
            break;
        default :
            dma_index = fb0_offset_addr/sizeof(int);
            break;
    }

    for (int y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        for (int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE II=1
            pix.data = in[dma_index+(y*HORIZONTAL_PIXEL_WIDTH)+x];

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

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

            outs << pix;
        }
    }

    return 0;
}


DMA_Read_offset_tb.cpp を示す。

// DMA_Read_offset_tb.cpp
// 2016/07/15 by marsee
// 2017/03/16 : 3フレーム処理から1フレーム処理に変更
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include <ap_axi_sdata.h>

#include "DMA_Read.h"
#include "bmp_header.h"

int DMA_Read_offset(volatile int *in, hls::stream<ap_axis<32,1,1,1> >& outs,
        unsigned int fb0_offset_addr, unsigned int fb1_offset_addr,
        unsigned int fb2_offset_addr, ap_uint<2> & active_frame,
        ap_uint<1> mode);

int main()
{
    using namespace std;

    hls::stream<ap_axis<32,1,1,1> > outs;
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> vals;

    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *hw_lapd;
    int blue, green, red;
    ap_uint<2> active_frame = 0;
    int *frame_buffer;

    if ((fbmpr = fopen("test.bmp""rb")) == NULL){ // test.bmp をオープン
    //if ((fbmpr = fopen("road_1.bmp", "rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.bmp by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(char), 2, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(long), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }

    int *buf;
    if ((buf =(int *)malloc(MAX_FRAME_NUMBER * sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate buf memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    // frame buffer をアロケートする、3倍の領域を取ってそれを3つに分ける
    if ((frame_buffer =(int *)malloc(MAX_FRAME_NUMBER * sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate frame_buffer0 ~ 2\n");
        exit(1);
    }

    // 3 つのフレームバッファにそれぞれ'A' を入力する(1つに変更)
    memcpy(frame_buffer, rd_bmp, bmpihr.biHeight * bmpihr.biWidth * sizeof(int));

    memcpy((int *)((unsigned int)frame_buffer + bmpihr.biHeight * bmpihr.biWidth * sizeof(int)),
        rd_bmp, bmpihr.biHeight * bmpihr.biWidth * sizeof(int));

    memcpy((int *)((unsigned int)frame_buffer + 2 * bmpihr.biHeight * bmpihr.biWidth * sizeof(int)),
        rd_bmp, bmpihr.biHeight * bmpihr.biWidth * sizeof(int));

    active_frame = 1// fb0_offset_addr
    DMA_Read_offset((volatile int *)frame_buffer, outs, (unsigned int)0,
        (unsigned int)(bmpihr.biWidth * bmpihr.biHeight * sizeof(int)),
        (unsigned int)(2 * (bmpihr.biWidth * bmpihr.biHeight) * sizeof(int)),
        active_frame, DMA_WRITE_MODE);

    active_frame = 2// fb1_offset_addr
    DMA_Read_offset((volatile int *)frame_buffer, outs, (unsigned int)0,
        (unsigned int)(bmpihr.biWidth * bmpihr.biHeight * sizeof(int)),
        (unsigned int)(2 * (bmpihr.biWidth * bmpihr.biHeight) * sizeof(int)),
        active_frame, DMA_WRITE_MODE);

    active_frame = 0// fb2_offset_addr
    DMA_Read_offset((volatile int *)frame_buffer, outs, (unsigned int)0,
        (unsigned int)(bmpihr.biWidth * bmpihr.biHeight * sizeof(int)),
        (unsigned int)(2 * (bmpihr.biWidth * bmpihr.biHeight) * sizeof(int)),
        active_frame, DMA_WRITE_MODE);

    // outs ストリームのデータを buf に入力する
    for (int k=0; k<3; k++){
        for(int j=0; j < bmpihr.biHeight; j++){
            for(int i=0; i < bmpihr.biWidth; i++){
                outs >> vals;
                ap_int<32> val = vals.data;
                buf[(k*bmpihr.biWidth*bmpihr.biHeight)+(j*bmpihr.biWidth)+i] = (int)val;

                if (val != frame_buffer[(k*bmpihr.biWidth*bmpihr.biHeight)+(j*bmpihr.biWidth)+i]){
                    printf("ERROR HW and SW results mismatch k = %d, i = %d, j = %d, HW = %08x, SW = %08x\n", k, i, j, (int)val, (int)frame_buffer[(k*bmpihr.biWidth*bmpihr.biHeight)+(j*bmpihr.biWidth)+i]);
                    return(1);
                }
            }
        }
    }

    // DMAされたデータをBMPフィルに書き込む
    char output_file[] = "dma_result0.bmp";
    for (int i=0; i<MAX_FRAME_NUMBER; i++){
        switch (i){
            case 0:
                strcpy(output_file,"dma_result0.bmp");
                break;
            case 1:
                strcpy(output_file,"dma_result1.bmp");
                break;
            case 2:
                strcpy(output_file,"dma_result2.bmp");
                break;
        }
        if ((fbmpw=fopen(output_file, "wb")) == NULL){
            fprintf(stderr, "Can't open %s by binary write mode\n", output_file);
            exit(1);
        }
        // BMPファイルヘッダの書き込み
        fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
        fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
        fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
        fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
        fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
        fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

        // RGB データの書き込み、逆順にする
        int offset = i * bmpihr.biWidth * bmpihr.biHeight;
        for (int y=0; y<bmpihr.biHeight; y++){
            for (int x=0; x<bmpihr.biWidth; x++){
                blue = buf[offset+((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
                green = (buf[offset+((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
                red = (buf[offset+((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

                fputc(blue, fbmpw);
                fputc(green, fbmpw);
                fputc(red, fbmpw);
            }
        }
        fclose(fbmpw);
    }
    free(rd_bmp);
    free(frame_buffer);
    return 0;
}

  1. 2017年04月18日 05:39 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLSのAXI4マスタ機能のオプションの違いによるテストベンチからの呼び出し方法

ちょっと訳があって、Vivado HLSのAXI4マスタ機能のオプションの違いによるテストベンチからの呼び出し方法を書いておく。

AXI4マスタ・インターフェースを生成するプラグマは

#pragma HLS INTERFACE m_axi port=<ポート名>

で、オプションにオフセット・アドレスを指定する offset がある。

この offset オプションには3つの設定がある。

off : ベース・アドレス無し。アドレスは 0x00000000
direct : ベース・アドレスは32ビット幅のポートの値となる。
slave : ベース・アドレスを示すためにAXI4-Liteインターフェースに32ビット幅のレジスタが追加される。この場合は、ブロックレベルのインターフェースをAXI4-Liteにする必要がある。
(混同を避けるため、こちらの用語はベース・アドレスにした)


なお、「Vivado Design Suite ユーザー ガイド 高位合成 UG902 (v2016.4) 2016 年 11 月 30 日」の114ページの「AXI4 インターフ ェイスのアドレス オフセットの制御」を参照している。

direct オプションの場合はポートでアドレスを与えるので、決まってしまうが、off と slave の場合のテストベンチでのアドレスの与え方の考え方を書いておこう。(私独自なのかもしれないが。。。)

例にとるのは、「Vivado HLS で DMA Write IP を作る2(絶対アドレス指定編)」と「Vivado HLS で DMA Write IP を作る(オフセット・アドレス指定編)」を例にとって説明しよう。

何を問題にしているか?を最初に書いておくと、ソフトウェアでのテストベンチのアドレスの与え方とハードウェアのIPとしたときのアプリケーションソフトでのアドレスの与え方の整合性なのだ。
DMA Write IP では、画像のフレームバッファを3つ用意して、3つアドレスに順番に順番にWrite していく。それは、画像を表示したときにジャギーを出さない標準的な方法だ。これを実現するために、1つの呼び出しで3回異なるアドレスにAXI4マスタ・インターフェースを使用して3回のDMA Write を行う。
その3つのアドレスをどう指定するか?なのだが、絶対アドレス3つを指定するのと、1つの絶対アドレスを指定して、そのオフセット・アドレスを3つ指定する方法が考えられる。たとえ、最初のオフセット・アドレス0だとしても、3つ指定することになる。
この2つの指定方法の差を説明すると、最初は、3つの独立したアドレスを指定するのだが、2番目は1つの独立したアドレスとその3つのオフセット・アドレスを指定するという違いがある。
最初の「絶対アドレスを3つ指定する方法」では、指定するアドレスは3つなのだが、「1つの絶対アドレスを指定して、そのオフセット・アドレスを3つ指定する方法」では、4つのアドレスを指定する必要がある。

Vivado HLS で DMA Write IP を作る(絶対アドレス指定編)」では、最初の「絶対アドレスを3つ指定する方法」で書いたDMA Write IP となっている。
関数の定義は、

int DMA_Write(hls::stream >& ins, volatile int *out,
  unsigned int frame_buffer0, unsigned int frame_buffer1,
  unsigned int frame_buffer2, volatile ap_uint<2> & active_frame)

なのだが、指示子は以下のようになっている。

#pragma HLS INTERFACE ap_vld port=active_frame
#pragma HLS INTERFACE s_axilite port=frame_buffer0
#pragma HLS INTERFACE s_axilite port=frame_buffer1
#pragma HLS INTERFACE s_axilite port=frame_buffer2
#pragma HLS INTERFACE m_axi depth=5000000 port=out offset=off
#pragma HLS INTERFACE axis port=ins
#pragma HLS INTERFACE s_axilite port=return

out ポートのAXI4マスタ・インターフェースのoffset オプションは off としている。つまり、このベース・アドレスは、0 で、frame_buffer0、frame_buffer1、frame_buffer2 を絶対アドレスとして設定して、それらのアドレスを指定してDMAすることになる。
offset オプションを off とする実装での、C コードの合成結果のレジスタマップを見てみよう。

//------------------------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 frame_buffer0
//        bit 31~0 - frame_buffer0[31:0] (Read/Write)
// 0x1c : reserved
// 0x20 : Data signal of frame_buffer1
//        bit 31~0 - frame_buffer1[31:0] (Read/Write)
// 0x24 : reserved
// 0x28 : Data signal of frame_buffer2
//        bit 31~0 - frame_buffer2[31:0] (Read/Write)
// 0x2c : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


上のレジスタのアドレスマップでは、ベース・アドレスを設定するレジスタ設定が存在しない。つまり、テストベンチでDMA_Write() を呼ぶときに適当な呼び方は、ベース・アドレスを 0 とする呼び方になる。つまり、offset オプションを off としたときのベース・アドレスが 0x00000000 になるというVivado HLS の規則に沿った関数コールの方法となる。そのDMA_Write() 関数のテストベンチでの呼び出し部分を下に示す。

DMA_Write(ins, (volatile int *)0, (unsigned int)frame_buffer,
 (unsigned int)frame_buffer+(bmpihr.biWidth * bmpihr.biHeight * sizeof(unsigned int)),
 (unsigned int)frame_buffer+(2 * (bmpihr.biWidth * bmpihr.biHeight) * sizeof(unsigned int)),
 active_frame);


ここでハードウェアの場合を考えてみると、ベース・アドレスは上のレジスタ・マップに無いように設定する必要が無く、frame_buffer0、frame_buffer1、frame_buffer2 の3つの絶対アドレスだけを指定すればよいことになり、関数コールのやり方とあっている。

下の関数コールのように読んでしまうと、ベース・アドレスと、3つの絶対アドレスを指定することになり、上のアドレスマップでは設定できないことになってしまう。

DMA_Write(ins, (volatile int *)frame_buffer, (unsigned int)0,
 (unsigned int)(bmpihr.biWidth * bmpihr.biHeight * sizeof(int)),
 (unsigned int)(2 * (bmpihr.biWidth * bmpihr.biHeight) * sizeof(int)),
 active_frame);


この関数コールを使う場合は、ベース・アドレスにframe_buffer の値を使っている。そしてのベース・アドレスからのオフセット・アドレスを3つ設定している。これは、ハードウェアにすると4つのレジスタを設定することになる。AXI4マスタ・インターフェースのoffset オプションは off にしていたのでは、4つのレジスタを設定することはないので、offset オプションは slave にする必要がある。こうすれば、ベース・アドレス+オフセット・アドレスを3つ設定することができる。
Vivado HLS で DMA Write IP を作る(オフセット・アドレス指定編)」の場合のアドレスマップを示す。この実装では、AXI4マスタ・インターフェースのoffset オプションは 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 out_r
//        bit 31~0 - out_r[31:0] (Read/Write)
// 0x1c : reserved
// 0x20 : Data signal of fb0_offset_addr
//        bit 31~0 - fb0_offset_addr[31:0] (Read/Write)
// 0x24 : reserved
// 0x28 : Data signal of fb1_offset_addr
//        bit 31~0 - fb1_offset_addr[31:0] (Read/Write)
// 0x2c : reserved
// 0x30 : Data signal of fb2_offset_addr
//        bit 31~0 - fb2_offset_addr[31:0] (Read/Write)
// 0x34 : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


0x18に outr のレジスタが設置されている。
  1. 2017年04月15日 20:28 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS で DMA Write IP を作る2(絶対アドレス指定編)

Vivado HLS で DMA Write IP を作る(絶対アドレス指定編)”のテストベンチが間違っていたので、その修正です。その他のC ソースコードも修正してあります。

まずは、Source の DMA_Write.c を貼っておきます。

// DMA_Write.cpp
// 2016/07/10 by marsee
//
// frame_buffer0, frame_buffer1, frame_buffer2 には3つのフレームバッファのアドレスを入れる
// 2017/04/15 : m_axiプラグマのdepthを変更した
//

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

#include "DMA_Write.h"

int DMA_Write(hls::stream<ap_axis<32,1,1,1> >& ins, volatile int *out,
        unsigned int frame_buffer0, unsigned int frame_buffer1,
        unsigned int frame_buffer2, volatile ap_uint<2> & active_frame){
#pragma HLS INTERFACE ap_vld port=active_frame
#pragma HLS INTERFACE s_axilite port=frame_buffer0
#pragma HLS INTERFACE s_axilite port=frame_buffer1
#pragma HLS INTERFACE s_axilite port=frame_buffer2
#pragma HLS INTERFACE m_axi depth=9216 port=out offset=off
//#pragma HLS INTERFACE m_axi depth=30000000 port=out offset=off
#pragma HLS INTERFACE axis port=ins
#pragma HLS INTERFACE s_axilite port=return

    ap_axis<32,1,1,1> pix;
    int dma_index;

    for (int i=0; i<MAX_FRAME_NUMBER; i++){
        switch (i){
            case 0 :
                dma_index = frame_buffer0/sizeof(int);
                break;
            case 1 :
                dma_index = frame_buffer1/sizeof(int);
                break;
            case 2 :
                dma_index = frame_buffer2/sizeof(int);
                break;
        } 
        active_frame = i;

        do { // user が 1になった時にフレームがスタートする
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
            ins >> pix;
        } while(pix.user == 0);

        for (int y=0; y<VERTICAL_PIXEL_WIDTH; y++){
            for (int x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE II=1
                if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                    ins >> pix;    // AXI4-Stream からの入力
                out[dma_index+(y*HORIZONTAL_PIXEL_WIDTH)+x] = pix.data;
            }
        }
    }
    return 0;
}


次に、DMA_Write.h を貼っておきます。

// DMA_Write.h
// 2016/07/10 by marsee
//

#ifndef __DMA_WRITE_H__
#define __DMA_WRITE_H__

//#define HORIZONTAL_PIXEL_WIDTH    800
//#define VERTICAL_PIXEL_WIDTH    600

#define HORIZONTAL_PIXEL_WIDTH    64
#define VERTICAL_PIXEL_WIDTH    48

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define MAX_FRAME_NUMBER    3

#endif


DMA_Write_tb.cpp を貼っておきます。

// DMA_Write_tb.cpp
// 2016/07/10 by marsee
//
// 2017/04/15 : DMA_Write()を呼び出すときの引数を変更した
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include <ap_axi_sdata.h>

#include "DMA_Write.h"
#include "bmp_header.h"

int DMA_Write(hls::stream<ap_axis<32,1,1,1> >& ins, volatile int *out,
        unsigned int frame_buffer0, unsigned int frame_buffer1,
        unsigned int frame_buffer2, volatile ap_uint<2> & active_frame);

int main()
{
    using namespace std;

    hls::stream<ap_axis<32,1,1,1> > ins;
    ap_axis<32,1,1,1> pix;

    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *hw_lapd;
    int blue, green, red;
    ap_uint<2> active_frame;
    int *frame_buffer;

    if ((fbmpr = fopen("test.bmp""rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.bmp by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(char), 2, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(long), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    // ins に入力データを用意する
    for(int i=0; i<5; i++){    // dummy data
           pix.user = 0;
         pix.data = i;
        ins << pix;
    }

    for(int k=0; k<MAX_FRAME_NUMBER; k++){
        for(int j=0; j < bmpihr.biHeight; j++){
            for(int i=0; i < bmpihr.biWidth; i++){
                pix.data = (ap_int<32>)rd_bmp[(j*bmpihr.biWidth)+i];

                if (j==0 && i==0)    // 最初のデータの時に TUSER を 1 にする
                    pix.user = 1;
                else
                    pix.user = 0;

                if (i == bmpihr.biWidth-1// 行の最後でTLASTをアサートする
                    pix.last = 1;
                else
                    pix.last = 0;

                ins << pix;
            }
        }
    }

    // frame buffer をアロケートする、3倍の領域を取ってそれを3つに分ける
    if ((frame_buffer =(int *)malloc(MAX_FRAME_NUMBER * sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate frame_buffer0 ~ 2\n");
        exit(1);
    }

    DMA_Write(ins, (volatile int *)0, (unsigned int)frame_buffer,
        (unsigned int)frame_buffer+(bmpihr.biWidth * bmpihr.biHeight * sizeof(unsigned int)),
        (unsigned int)frame_buffer+(2 * (bmpihr.biWidth * bmpihr.biHeight) * sizeof(unsigned int)),
        active_frame);
    
    // DMAされたデータをBMPフィルに書き込む
    char output_file[] = "dma_result0.bmp";
    for (int i=0; i<MAX_FRAME_NUMBER; i++){
        switch (i){
            case 0:
                strcpy(output_file,"dma_result0.bmp");
                break;
            case 1:
                strcpy(output_file,"dma_result1.bmp");
                break;
            case 2:
                strcpy(output_file,"dma_result2.bmp");
                break;
        }
        if ((fbmpw=fopen(output_file, "wb")) == NULL){
            fprintf(stderr, "Can't open %s by binary write mode\n", output_file);
            exit(1);
        }
        // BMPファイルヘッダの書き込み
        fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
        fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
        fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
        fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
        fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
        fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

        // RGB データの書き込み、逆順にする
        int offset = i * bmpihr.biWidth * bmpihr.biHeight;
        for (int y=0; y<bmpihr.biHeight; y++){
            for (int x=0; x<bmpihr.biWidth; x++){
                blue = frame_buffer[offset+((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
                green = (frame_buffer[offset+((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
                red = (frame_buffer[offset+((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

                fputc(blue, fbmpw);
                fputc(green, fbmpw);
                fputc(red, fbmpw);
            }
        }
        fclose(fbmpw);
    }       
    free(rd_bmp);
    free(frame_buffer);
    return 0;
}


これで、C コードの合成を行いました。Vivado HLS のバージョンは 2016.4 です。
結果を示します。
DMA_Write_IP2_1_170415.png

DMA_Write_IP2_2_170415.png
2016.2 の結果と比べると多少、リソース使用量が減っていますね。

合成されたAXI4 Lite SlaveのVerilog HDL ファイルのDMA_Write_AXILiteS_s_axi.v のアドレスマップを下に示します。
ここでのポイントは、 DMA_Write() 関数自体のオフセット・アドレスを示すレジスタは無いことです。

//------------------------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 frame_buffer0
//        bit 31~0 - frame_buffer0[31:0] (Read/Write)
// 0x1c : reserved
// 0x20 : Data signal of frame_buffer1
//        bit 31~0 - frame_buffer1[31:0] (Read/Write)
// 0x24 : reserved
// 0x28 : Data signal of frame_buffer2
//        bit 31~0 - frame_buffer2[31:0] (Read/Write)
// 0x2c : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


C/RTL協調シミュレーションを行いました。
DMA_Write_IP2_3_170415.png
エラーが発生してしまいました。

次に、DMA_Write.cpp の

#pragma HLS INTERFACE m_axi depth=9216 port=out offset=off

のdepth だけを 30000000 に変更します。
DMA_Write_IP2_4_170415.png

Cコードの合成を行います。depth の値は合成結果に影響を及ぼさないので、結果の変化はありません。
C/RTL協調シミュレーションを行いました。今度は、正常終了です。
DMA_Write_IP2_5_170415.png

C/RTL協調シミュレーションの波形を見ていきます。
まずは波形全体です。
DMA_Write_IP2_6_170415.png

DMA_Write_IP2_7_170415.png
出力のAXI4 Master Write も正常にデータが出いているのが分かります。

レジスタ設定用のAXI4-Lite Slave アクセスを見てみましょう。
DMA_Write_IP2_8_170415.png
3つのDMAのWrite の先頭アドレスはそれぞれ 0x0133ad98, 0x1133dd98, 0x01340d98 です。

3つの DMA Write の先頭アドレスを見てみましたが、見事に、0x0133ad98, 0x1133dd98, 0x01340d98 でした。
1つ目のDMA Write の先頭アドレス。
DMA_Write_IP2_9_170415.png

2つ目のDMA Write の先頭アドレス。
DMA_Write_IP2_10_170415.png

3つ目のDMA Write の先頭アドレス。
DMA_Write_IP2_11_170415.png

malloc() で確保した画像領域の最後+1 は 0x01340d98 + 64(10進数)x 48(10進数) x 4(バイト) = 0x01343d98 です。
因みに depth の 30000000 は、0x01c9c380 です。
テストベンチで DMA_Write() の out のオフセットに0 を入れています。(「Vivado Design Suite ユーザー ガイド 高位合成 UG902 (v2016.4) 2016 年 11 月 30 日」の114ページの「AXI4 インターフ ェイスのアドレス オフセットの制御」参照)
そして、何度かやってみたのですが、malloc() で取られた画像用のバッファのアドレス以上のdepth の値を与えないとエラーになってしまうようなのです。つまり、Vivado HLS のC/RTL協調シミュレーションの仕組みはアクセスする可能性のあるバッファ領域を確保しなくてはいけないようです。考えてみれば当たり前とも言えます。通常はオフセット・アドレス+オフセットですので、使う領域だけを depth に指定すれば良いのですが、malloc() された絶対アドレスを引数経由で渡してDMA するとこのような結果になります。

また、C/RTL協調シミュレーションの結果も奇妙なことになっています。
DMA_Write_IP2_12_170415.png

Cシミュレーションの結果である sim -> wrapc フォルダのdma_result0.bmp は正常に「A」ですが、
DMA_Write_IP2_13_170415.png

C/RTL協調シミュレーションの結果の sim -> wrapc_pc フォルダのdma_result0.bmp は真っ暗です。
DMA_Write_IP2_14_170415.png

このようなアドレスの与え方には、Vivado HLS が対応していないのかもしれません?
  1. 2017年04月15日 17:03 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0
»