FC2カウンター FPGAの部屋 2021年04月
fc2ブログ

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

FPGAの部屋

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

Zynq のベアメタル・アプリケーションによるキャッシュの操作3(ACPポート2)

”Zynq のベアメタル・アプリケーションによるキャッシュの操作2(ACPポート)”で ACP ポートのキャッシュ・コヒーレンシがうまく行ってないようだったが、今回、HDLabで開催する予定の Zynq + Vivado HLS セミナの例題を作っていて、同じ結果になってしまった。そこで、 ACP ポートを使用して、キャッシュ・コヒーレンシを取れる DMA を使用するために調査した。Vivado HLS と Vivado のバージョンは 2019.2 です。

今回は、平方数の和を出力する Vivado HLS で作成した AXI4-Stream の s_squares_axis IP を使用している。8 ビットの x 入力と 8 ビットの y 入力を 2 乗して加算する。 8 ビットの x 入力と y 入力に 1 個ずつ DMA2axis8 IP が接続されている。また s_squares_axis IP の出力は32 ビットの result を受けて axis2DMA する IP も接続されている。各 DMA は AXI4 Master で ACP ポートに接続されている。
ACP_cache_1_210429.png

ここで、Zynq Processing System7 は ACP ポートを活かしてあるが、Tie off AxUSERにはチェックを入れていない。
ACP_cache_2_210429.png

各 DMA する IP は Enable USER ports にチェックを入れて、width を 1 にしてある。
CACHE value は "1111"にしてある。
USER value は 0x00000001 にしてある。
ACP_cache_3_210429.png

これで、このアプリケーション・ソフトウェアを走らせた。

// sums_ex6.c 2019/09/14 by marsee

#include <stdio.h>
#include <stdint.h>
#include "xaxis2dma.h"
#include "xdma2axis8.h"
#include "xs_squares_axis.h"

volatile uint8_t data0[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
volatile uint8_t data1[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
volatile uint32_t result[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

int main(){
    XDma2axis8 xdma2axis8_ap0, xdma2axis8_ap1;
    XAxis2dma xaxis2dma_ap;
    XS_squares_axis XS_squares_axis_ap;

    XDma2axis8_Initialize(&xdma2axis8_ap0, 0);
    XDma2axis8_Initialize(&xdma2axis8_ap1, 1);
    XAxis2dma_Initialize(&xaxis2dma_ap, 0);
    XS_squares_axis_Initialize(&XS_squares_axis_ap, 0);

    XDma2axis8_Set_in_V(&xdma2axis8_ap0, (u32)data0);
    XDma2axis8_Set_in_V(&xdma2axis8_ap1, (u32)data1);
    XAxis2dma_Set_out_V(&xaxis2dma_ap, (u32)result);

    XAxis2dma_Start(&xaxis2dma_ap);
    XS_squares_axis_Start(&XS_squares_axis_ap);
    XDma2axis8_Start(&xdma2axis8_ap0);
    XDma2axis8_Start(&xdma2axis8_ap1);

    while(!XAxis2dma_IsDone(&xaxis2dma_ap));

    for(int i=0; i<10; i++){
        printf("data0[%d] = %d, data1[%d] = %d, result[%d] = %d\n", i, (int)data0[i], i, (int)data1[i], i, (int)result[i]);
    }

    return(0);
}


結果を示す。
最後の 3 個の結果が正しくない。
ACP_cache_4_210429.png

この結果は”Zynq のベアメタル・アプリケーションによるキャッシュの操作2(ACPポート)”と同じだ。

さて、ここからすべての DMA IP の Enable USER ports のチェックボックスのチェックを外した。
ACP_cache_5_210429.png

これでアプリケーション・ソフトウェアを走らせたところ、同じ結果だった。
ACP_cache_6_210429.png

Zynq Processing System7 は ACP ポートを活かして、Tie off AxUSERにもチェックを入れた。
ACP_cache_7_210429.png

すると、アプリケーション・ソフトウェアを走らせたところ、今度はすべての結果が正しい。
ACP_cache_8_210429.png

もしかして、DMA IP の AxUSER が Zynq Processing System7 まで回っていないのか?
Zynq Processing System7 は ACP ポートを活かしてあるが、Tie off AxUSERにはチェックを入れていない。しかも、各 DMA IP の Enable USER ports にチェックを入れた状態に戻して、各 AXI ポートを展開してみた。
ACP_cache_9_210429.png

DMA IP には AxUSER ポートがある。その対向している AXI Interconnect の AXI ポートには AxUSER がない。。。これか原因は AXI Interconnect まで AxUSER が回っていない?

ちなみに Zynq Processing System7 の AXI ポートには、 AxUSER ポートがあって、その対向の AXI Interconnect の AXI ポートにも AxUSER がある。。。

Vivado HLS で作った IP は AxUSER 無視されるか?設定があるのかな?
自分で HDL で書いて IP Packager でパッケージした IP はどうなんだろうか?

とりあえずは、Zynq Processing System7 の ACP ポートを使う場合は、Tie off AxUSERにチェックを入れて使った方が良さそうだ。

(2021/04/30:追記)
ikwzm さんに Zynq Processing System7 ポートの AxUSER 信号は 5 ビット幅なので、Vivado HLS で作成した IP の AxUSER信号も 5 ビットにしてはどうか?という提案があったので、やってみた。

Vivado HLS で作成した IP の AWUSER と ARUSER 信号を 5 ビットに変更した。
ACP_cache_10_210429.png

ポートを見ると、Vivado HLS IP と接続された AXI Interconnect の AXI インターフェース・ボートにも AxUSER のポートが確認できた。
ACP_cache_11_210429.png

これでアプリケーション・ソフトウェアを走らせたところ、今までと同様に最後の 3 個が誤っている。
ACP_cache_12_210429.png

最後に同じ設定で Vivado HLS IP の AXI4 Master 用の AXI Interconnect を AXI SmartConnect に変更した。
ACP_cache_13_210430.png

SmartConnect の Vivado HLS IP と接続された AXI ポートを見ると AxUSER 信号が存在する。 Zynq Processing System7 との対向 AXI ポートにも AxUSER が存在する。
ACP_cache_14_210430.png

これでアプリケーション・ソフトウェアを走らせたところ、すべての結果が正しくなった。
ACP_cache_15_210430.png

AXI Interconnect は AxUSER を伝搬しないが、AXI SmartConnect だったら問題なく伝搬するのかな?
  1. 2021年04月29日 20:40 |
  2. Zynq
  3. | トラックバック:0
  4. | コメント:0

Vitis Vision Library でカメラ画像にメディアン・フィルタをかけてディスプレイに出力する2(axis2xf8uc3 IP その1)

Vitis Vision Library でカメラ画像にメディアン・フィルタをかけてディスプレイに出力する1(構想編)”の続き。

Vitis Vision Library 2020.2 を使用して、カメラ画像にメディアン・フィルタをかけてディスプレイに出力してみようということで、前回は、構想を練った。今回は、図のうちで axis2xf8uc3 IP を Vitis HLS 2020.2 で作成する。

最初に作成した aixs2xf8uc3.cpp を貼っておく。

// aixs2xf8uc3.cpp
// 2021/04/23 by marsee
//

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

int axis2xf8uc3(hls::stream<ap_axis<32,1,1,1> >& ins, int rows, int cols, volatile ap_uint<32>* _dst){
#pragma HLS INTERFACE m_axi depth=360000 port=_dst offset=slave
#pragma HLS INTERFACE s_axilite port=cols
#pragma HLS INTERFACE s_axilite port=rows
#pragma HLS INTERFACE axis register_mode=both register port=ins
#pragma HLS INTERFACE s_axilite port=return
    ap_axis<32,1,1,1> pix;
    uint32_t pixay[4];

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

    const int xy_limit = rows * cols;
    int xfcnt = 0;
    LOOP_XY: for(int xy=0; xy<xy_limit; xy++){
#pragma HLS LOOP_TRIPCOUNT avg=480000 max=480000 min=480000
#pragma HLS PIPELINE II=1
        if(!(xy == 0)) // 最初の入力はすでに入力されている
            ins >> pix; // AXI4-Stream からの入力

        switch(xy%4){
        case 0 :
            pixay[0] = pix.data;
            break;
        case 1 :
            pixay[1] = pix.data;
            _dst[xfcnt++] = ((pixay[1]&0xff)<<24)+(pixay[0]&0xffffff);
            break;
        case 2 :
            pixay[2] = pix.data;
            _dst[xfcnt++] = ((pixay[2]&0xffff)<<16)+((pixay[1]&0xffff00)>>8);
            break;
        default : // 3
            pixay[3] = pix.data;
            _dst[xfcnt++] = ((pixay[3]&0xffffff)<<8)+((pixay[2]&0xff0000)>>16);
            break;
        }
    }
    return(0);
}


テストベンチの axis2xf8uc8_tb.cpp を貼っておく

// axis2xf8uc8_tb.cpp
// 2021/04/23 by marsee
//

#include "ap_int.h"
#include "hls_stream.h"
#include "ap_axi_sdata.h"
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"

int axis2xf8uc3(hls::stream<ap_axis<32,1,1,1> >& ins, int rows, int cols, volatile ap_uint<32>* _dst);

int main(int argc, char **argv){
    hls::stream<ap_axis<32,1,1,1> > ins;
    hls::stream<ap_axis<32,1,1,1> > ins_soft;
    ap_axis<32,1,1,1> pix;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <INPUT IMAGE>", argv[0]);
        exit(1);
    }

    cv::Mat img = cv::imread(argv[1], 1); // reading in the color image

    if (img.data == NULL) {
        fprintf(stderr, "ERROR: Cannot open image %s\n ", argv[1]);
        exit(1);
    }

    // ピクセルを入れる領域の確保
    std::vector<int32_t> rd_bmp(sizeof(int32_t)*img.cols*img.rows);

    // rd_bmp にBMPのピクセルを代入
    cv::Mat_<cv::Vec3b> dst_vec3b = cv::Mat_<cv::Vec3b>(img);
    for (int y=0; y<img.rows; y++){
        for (int x=0; x<img.cols; x++){
            cv::Vec3b pixel;
            pixel = dst_vec3b(y,x);
            rd_bmp[y*img.cols+x] = (pixel[0] & 0xff) | ((pixel[1] & 0xff)<<8) | ((pixel[2] & 0xff)<<16);
            // blue - pixel[0]; green - pixel[1]; red - pixel[2];
        }
    }

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

    for(int j=0; j < img.rows; j++){
        for(int i=0; i < img.cols; i++){
            pix.data = (ap_int<32>)rd_bmp[(j*img.cols)+i];

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

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

            ins << pix;
        }
    }

    cv::Mat out_img;
    out_img.create(img.rows, img.cols, CV_8UC3);

    axis2xf8uc3(ins, img.rows, img.cols, (volatile ap_uint<32> *)out_img.data);

    cv::imwrite("output.jpg", out_img);

    return(0);
}


Vitis HLS 2020.2 の axis2xf8uc3 プロジェクトを示す。
Vitis_Vision_disp_50_210429.png

Vitis HLS で OpenCV を使用するために設定が必要となっている。
Vitis HLS の Project メニューから Project Settings... を選択して、Project Settings (axis2xf8uc3) ダイアログを表示させた。
左のペインから Simulation を選択する。
TestBench Files の axis2xf8uc3_tb.cpp の CFLAGS に以下の設定を行った。

-I/usr/local/include


絶対パスで書いても、相対パスに直されてしまう。

Linker Flags に

-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc

を設定した。

Input Arguments に

test2.jpg

を設定した。
  1. 2021年04月29日 04:16 |
  2. Vitis_Vision
  3. | トラックバック:0
  4. | コメント:0

MicroBlaze プロセッサの BRAM メモリの増減の方法

MicroBlaze プロセッサを使っていて DLMB と ILMB ポートに microblaze_local_memory を付けているが、その増減方法が分からなかった。その増減方法を IDA さんに教えてもらったので、備忘録として書いておく。

microblaze_0_local_memory を展開して、lmb_bram をダブルクリックして設定を行った。
microblaze_local_memory_1_210428.png

Block RAM Generator 設定ダイアログが開く。
microblaze_local_memory_2_210428.png

Port A Option タブの Write Depth を設定しようとしてもハイドされていて設定できない。
microblaze_local_memory_3_210428.png

何処を設定するのか、悩んでいたら IDA さんが、Address Editor で設定すると教えてくれた。ありがとうございます。

Address Editor を開いて dlmb_bram_if_cntlr/SLMB の Range を 64k バイトに設定したところ、正常に設定できた。(上の図は設定されたところだ)
microblaze_local_memory_4_210428.png
  1. 2021年04月28日 21:23 |
  2. MicroBlaze
  3. | トラックバック:0
  4. | コメント:1

Vitis Vision Library でカメラ画像にメディアン・フィルタをかけてディスプレイに出力する1(構想編)

Vitis Vision Library 2020.2 を使用して、カメラ画像にメディアン・フィルタをかけてディスプレイに出力してみよう。
今回は、そのための構想を練ってみる。

ディスプレイ側は以前作成した。
AXI4-Stream の IP を使用して Vitis Vision Library で加工されたデータをディスプレイに表示する1(構想編)
AXI4-Stream の IP を使用して Vitis Vision Library で加工されたデータをディスプレイに表示する2(Vivado プロジェクト)
AXI4-Stream の IP を使用して Vitis Vision Library で加工されたデータをディスプレイに表示する3(Vitis プロジェクト)

今回はカメラ側を作成する。

全体のブロック図を下に示す。
Vitis_Vision_disp_49_210428.png

cam_inf_d111_axis IP は、”ZYBO Z7-20でのMNISTの実装にOV5642を使用する3”で作成済みだ。今回は、確実性を取って、既存のカメラ・インターフェースを使用する。MIPI をやりたいが次回に取っておく。

axis2xf8uc3 は AXI4-Stream を XF8UC3 に変換する IP で Vitis HLS 2020.2 でこれから作成する。

(追加)ランダム・ノイズ発生器(random_noise_gen)を追加した。これは、メディアン・フィルタの性能を確認するために、わざとカメラ画像にランダム・ノイズを追加する IP となる。
  1. 2021年04月28日 04:52 |
  2. Vitis_Vision
  3. | トラックバック:0
  4. | コメント:0

AXI4-Lite インターフェースの I2C Master Core を使用して 3 軸加速度度センサーの値を収集する2

AXI4-Lite インターフェースの I2C Master Core を使用して 3 軸加速度度センサーの値を収集する1”の続き。

AXI4-Lite インターフェースの I2C Master Core を IP にする”で作成した AXI4-Lite インターフェースの I2C Master Core IP を使用して、ストロベリーリナックスの ADXL355 超低ノイズ3軸加速度センサモジュールのデータを I2C で収集してみようということで、前回は、Vivado 2020.2 でプロジェクトを作成し、i2cm_axi4ls IP を使ってブロックデザインを作成し、論理合成、インプリメンテーション、ビットストリームを生成して成功した。そして、ハードウェアをエクスポートした。今回は、Vitis 2020.2 を立ち上げて、プラットフォームとアプリケーション・ソフトウェアを作成して、ZYBO Z7-20 とストロベリーリナックスの ADXL355 超低ノイズ3軸加速度センサモジュールで試してみよう。

Vivado 2020.2 の Tools メニューから Launch Vitis を選択して、Vitis 2020.2 を立ち上げた。
acc_sensor_202 ディレクトリに下に、vitis_work ディレクトリを作成して、Workspace とした。
acc_bd_wrapper プラットフォームと acc_sensor アプリケーション・プロジェクトを作成した。
acc_sensor.c アプリケーション・ソフトウェアを作成して、ビルドしたところ、成功し、acc_sensor.elf が生成された。
i2cm_axi4ls_6_210426.png

ZYBO Z7-20 とストロベリーリナックスの ADXL355 超低ノイズ3軸加速度センサモジュールを接続した。
SCL と SDA のプルアップ抵抗はそれぞれ 1 kΩにしてある。
i2cm_axi4ls_7_210426.jpg

アプリケーション・ソフトウェアを Run すると、3軸加速度センサーのセンサー・データを取得することができた。成功だ。うまく行ってよかった。。。
i2cm_axi4ls_11_210426.png

I2C の波形を示す。
i2cm_axi4ls_8_210426.jpg

拡大する。
i2cm_axi4ls_9_210426.jpg

クロックの周期を測定した。 2.32 us だった。周波数にすると 431 kHz だった。
i2cm_axi4ls_10_210426.jpg

acc_sensor.c を貼っておく。

// acc_sensor.c
// 2021/04/19 by marsee
// コード中のコメント部分は”OpenCores.org の I2C controller core ”のマニュアルから引用した
// https://opencores.org/projects/i2c

#include <stdio.h>
#include <stdint.h>
#include "xil_io.h"
#include "xparameters.h"
#include <unistd.h>
#include "xtime_l.h"

#define I2CM_PRER_LO    XPAR_I2CM_AXI4LS_0_BASEADDR
#define I2CM_PRER_HI    (XPAR_I2CM_AXI4LS_0_BASEADDR+0x4)
#define I2CM_CTR        (XPAR_I2CM_AXI4LS_0_BASEADDR+0x8)
#define I2CM_RXR        (XPAR_I2CM_AXI4LS_0_BASEADDR+0xc)
#define I2CM_TXR        (XPAR_I2CM_AXI4LS_0_BASEADDR+0xc)
#define I2CM_CR         (XPAR_I2CM_AXI4LS_0_BASEADDR+0x10)
#define I2CM_SR         (XPAR_I2CM_AXI4LS_0_BASEADDR+0x10)

#define CR_STA      0x80    // generate (repeated) start condition
#define CR_STO      0x40    // generate stop condition
#define CR_RD       0x20    // read from slave
#define CR_WR       0x10    // write from slave
#define CR_NACK     0x08    //  when a receiver, sent ACK (ACK = ‘0’) or NACK (ACK = ‘1’)
#define CR_IACK     0x01    // Interrupt acknowledge. When set, clears a pending interrupt.

#define SR_RxACK    0x80    // Received acknowledge from slave.This flag represents acknowledge from  the addressed slave.
                            // ‘1’ = No acknowledge received , ‘0’ = Acknowledge received
#define SR_Busy     0x40    // I2C bus busy
                            // ‘1’ f after START signal detected , ‘0’ after STOP signal detected
#define SR_AL       0x20    // Arbitration lost. This bit is set when the core lost arbitration. Arbitration is lost when:
                            // * a STOP signal is detected, but non requested , * The master drives SDA high, but SDA is low.
#define SR_TIP      0x02    // Transfer in progress.
                            // ‘1’ f when transferring data , ‘0’f when transfer complete
#define SR_IF       0x01    //  Interrupt Flag. This bit is set when an interrupt is pending, which
                            //  will cause a processor interrupt request if the IEN bit is set.
                            //  The Interrupt Flag is set when:
                            // *  one byte transfer has been completed , *  arbitration is lost

void idle_check(){
    while(Xil_In32(I2CM_SR) & SR_TIP); // TIP bit is clear
}
void acc_sensor_write(uint8_t dev_addr, uint8_t waddr, uint8_t wdata){
    dev_addr &= 0xfe;
    idle_check();

    Xil_Out32(I2CM_TXR, (u32)dev_addr);
    Xil_Out32(I2CM_CR, (u32)(CR_STA|CR_WR));
    idle_check();

    Xil_Out32(I2CM_TXR, (u32)waddr);
    Xil_Out32(I2CM_CR, (u32)CR_WR);
    idle_check();

    Xil_Out32(I2CM_TXR, (u32)wdata);
    Xil_Out32(I2CM_CR, (u32)(CR_STO|CR_WR));
    idle_check();
}
uint8_t acc_sensor_read1(uint8_t dev_addr, uint8_t raddr){
    const uint8_t devw_addr = dev_addr & 0xfe;

    dev_addr |= 0x01;
    idle_check();

    Xil_Out32(I2CM_TXR, (u32)devw_addr);
    Xil_Out32(I2CM_CR, (u32)(CR_STA|CR_WR));
    idle_check();

    Xil_Out32(I2CM_TXR, (u32)raddr);
    Xil_Out32(I2CM_CR, (u32)CR_WR);
    idle_check();

    Xil_Out32(I2CM_TXR, (u32)dev_addr);
    Xil_Out32(I2CM_CR, (u32)(CR_STA|CR_WR));
    idle_check();

    Xil_Out32(I2CM_CR, (u32)(CR_STO|CR_RD|CR_NACK));
    idle_check();

    uint8_t rdata8 = (uint8_t)(Xil_In32(I2CM_RXR) & 0xff);
    return(rdata8);
}
void acc_sensor_read3(uint8_t dev_addr, uint8_t raddr, int32_t *rdata){
    uint8_t rdata8a[3];
    const uint8_t devw_addr = dev_addr & 0xfe;

    dev_addr |= 0x01;
    idle_check();

    Xil_Out32(I2CM_TXR, (u32)devw_addr);
    Xil_Out32(I2CM_CR, (u32)(CR_STA|CR_WR));
    idle_check();

    Xil_Out32(I2CM_TXR, (u32)raddr);
    Xil_Out32(I2CM_CR, (u32)CR_WR);
    idle_check();

    Xil_Out32(I2CM_TXR, (u32)dev_addr);
    Xil_Out32(I2CM_CR, (u32)(CR_STA|CR_WR));
    idle_check();

    Xil_Out32(I2CM_CR, (u32)CR_RD);
    idle_check();
    rdata8a[0] = (uint8_t)(Xil_In32(I2CM_RXR) & 0xff);

    Xil_Out32(I2CM_CR, (u32)CR_RD);
    idle_check();
    rdata8a[1] = (uint8_t)(Xil_In32(I2CM_RXR) & 0xff);

    Xil_Out32(I2CM_CR, (u32)(CR_STO|CR_RD|CR_NACK));
    idle_check();
    rdata8a[2] = (uint8_t)(Xil_In32(I2CM_RXR) & 0xff);

    *rdata = (((int32_t)rdata8a[0])<<12) + (((int32_t)rdata8a[1])<<4) + (((int32_t)(rdata8a[2] & 0xf0))>>4);
    if(*rdata & 0x80000) // Is the 19th bit 1?
        *rdata |= 0xfff00000; // sign extension
}

int main(){
    uint8_t read_data, read_rdy;
    int32_t dataX, dataY, dataZ;
    XTime cur_time;

    // I2C I2C operating frequency setting 433KHz/100MHz, 415kHz/96MHz
    Xil_Out32(I2CM_PRER_LO, (u32)0x29);
    Xil_Out32(I2CM_PRER_HI, (u32)0x0);

    Xil_Out32(I2CM_CTR, 0x80); // enable core

    acc_sensor_write(0x3a, 0x2c, 0x83); // I2C speed is Hi speed, +-8g
    acc_sensor_write(0x3a, 0x1e, 0x00); // OFFSET_X_H
    acc_sensor_write(0x3a, 0x1f, 0x00); // OFFSET_X_L
    acc_sensor_write(0x3a, 0x20, 0x00); // OFFSET_Y_H
    acc_sensor_write(0x3a, 0x21, 0x00); // OFFSET_Y_L
    acc_sensor_write(0x3a, 0x22, 0x00); // OFFSET_Z_H
    acc_sensor_write(0x3a, 0x23, 0x00); // OFFSET_Z_L

    acc_sensor_write(0x3a, 0x2d, 0x00); // stanby clear

    while(1){
        do{
            read_data = acc_sensor_read1(0x3b, 0x04);
            read_rdy = read_data & 0x01;
        }while(read_rdy != 0x01);

        acc_sensor_read3(0x3b, 0x08, &dataX);
        acc_sensor_read3(0x3b, 0x0b, &dataY);
        acc_sensor_read3(0x3b, 0x0e, &dataZ);

        XTime_GetTime(&cur_time);
        printf("%lf,%x,%x,%x\n", (double)((long long int)cur_time)/333333.3435, (int)dataX, (int)dataY, (int)dataZ);

        usleep(2434); // for 400 kHz
    }
}

  1. 2021年04月27日 04:20 |
  2. FPGAを使用したシステム
  3. | トラックバック:0
  4. | コメント:0

AXI4-Lite インターフェースの I2C Master Core を使用して 3 軸加速度度センサーの値を収集する1

AXI4-Lite インターフェースの I2C Master Core を IP にする”で作成した AXI4-Lite インターフェースの I2C Master Core IP を使用して、ストロベリーリナックスの ADXL355 超低ノイズ3軸加速度センサモジュールのデータを I2C で収集してみよう。
しかし、やっとこれで目的を達成することができる。結構長かった。

Vivado 2020.2 で acc_sensor_202 プロジェクトを作成した。
i2cm_axi4ls_1_210425.png

AXI4-Lite インターフェースの I2C Master Core を IP にする”の IP を acc_sensor_202 ディレクトリの下に i2cm_axi4ls ディレクトリを作成して、 marsee101_user_i2cm_axi4ls_1.0.zip の中身をコピーした。

acc_bd ブロックデザインを新規作成して、Zynq Processing System 7 プロセッサ IP を Add IP し、 i2cm_axi4ls IP も Add IP して回路を完成させた。
i2cm_axi4ls_2_210425.png

Address Map を示す。
i2cm_axi4ls_3_210425.png

制約ファイルの acc_sensor.xdc を貼っておく。

set_property IOSTANDARD LVCMOS33 [get_ports iic_0_scl_io]
set_property IOSTANDARD LVCMOS33 [get_ports iic_0_sda_io]
set_property PACKAGE_PIN V8 [get_ports iic_0_scl_io]
set_property PACKAGE_PIN W8 [get_ports iic_0_sda_io]


これで、論理合成、インプリメンテーション、ビットストリームの生成を行って、成功した。
Project Summary を示す。
i2cm_axi4ls_4_210425.png

ハードウェアをエクスポートして、acc_bd_wrapper.xsa を生成した。
i2cm_axi4ls_5_210426.png
  1. 2021年04月26日 04:58 |
  2. FPGAを使用したシステム
  3. | トラックバック:0
  4. | コメント:0

AXI4-Lite インターフェースの I2C Master Core を IP にする

AXI4-Lite インターフェースの I2C Master Core をシミュレーションする”の続き。

OpenCores.org の I2C controller core をシミュレーションする”で、OpenCores.org の WishBone バスの I2C controller core をシミュレーションしたが、これを Xilinx の Zynq で使用するために AXI4-Lite インターフェースで使用したいと思ったということで、前回は AXI4-Lite インターフェース版の I2C Master Core をシミュレーションを行ったが、問題無さそうだった。今回は、IP にしてみよう。

Tools メニューから Create and Package New IP... を選択して IP に変換した。

Identification
Vitis_Vision_disp_39_210424.png

Compatibility
Vitis_Vision_disp_40_210424.png

File Groups
Vitis_Vision_disp_41_210424.png

Customization Parameters
Vitis_Vision_disp_42_210424.png

Ports and Interfaces
iic のインターフェースになっている。
Vitis_Vision_disp_43_210424.png

Addressing and Memory
Vitis_Vision_disp_44_210424.png

Customization GUI
Vitis_Vision_disp_45_210424.png

Review ans Package
Vitis_Vision_disp_46_210424.png

Package IP ボタンをクリックし IP 化した。

プロジェクト・ディレクトリに marsee101_user_i2cm_axi4ls_1.0.zip が生成された。
Vitis_Vision_disp_47_210424.png

marsee101_user_i2cm_axi4ls_1.0.zip の中身を示す。
Vitis_Vision_disp_48_210424.png
  1. 2021年04月25日 04:23 |
  2. FPGAを使用したシステム
  3. | トラックバック:0
  4. | コメント:0

AXI4-Lite インターフェースの I2C Master Core をシミュレーションする

OpenCores.org の I2C controller core をシミュレーションする”で、OpenCores.org の WishBone バスの I2C controller core をシミュレーションしたが、これを Xilinx の Zynq で使用するために AXI4-Lite インターフェースで使用したいと思いった。
そこで、”AXI4 Master Bus Functional Model を修正した”と”AXI4-Lite Master Bus Functional Model を修正した”で AXI4-Lite の Master の BFM を用意した。
WishBone バスと AXI4-Lite インターフェースを変換する wb2axi4ls_conv.v とトップの i2cm_axi4ls.v”で、WishBone バスと AXI4-Lite インターフェースを変換する wb2axi4ls_conv.v とトップの i2cm_axi4ls.v を用意した。
AXI4-Lite インターフェースの I2C Master Core をシミュレーションしてみよう。

Vivado 2020.2 の i2cm_axi4ls プロジェクトを示す。
Vitis_Vision_disp_32_210422.png

階層図を示す。
Vitis_Vision_disp_33_210422.png

論理シミュレーションを行った。
Vitis_Vision_disp_34_210422.png

全体波形を示す。
Vitis_Vision_disp_35_210422.png

最初に 0 番地に 0x29 を 4 番地に 0x00 を Write して Read するトランザクションを示す。
Vitis_Vision_disp_36_210422.png

うまく行っているようだ。

次に、I2C のトランザクションがスタートしてから 0x010 番地を Read してビット 2 を関しているところを示す。
現在の Read の値は 0x02 になっているが、これは TIP ビットが立っていて I2C が busy ということを示す。
Vitis_Vision_disp_37_210422.png

I2C のトランザクションが終了して、 0x010 番地を Read した時に 0x41 になっているのが、黄色いカーソルの部分だ。
次のAXI4-Lite インターフェースの Write トランザクションが見える。
Vitis_Vision_disp_38_210422.png

うまく行っているようだ。

テストベンチファイルの i2cm_axi4ls_tb.v を示す。
なお、コード中のコメント部分は”OpenCores.org の I2C controller core ”のマニュアルから引用した。

// i2cm_axi4ls_tb.v
// 2021/04/13 by marsee
// コード中のコメント部分は”OpenCores.org の I2C controller core ”のマニュアルから引用した
// https://opencores.org/projects/i2c

`default_nettype none

`timescale 100ps / 1ps

module i2cm_axi4ls_tb;
    parameter DELAY    = 10;

    parameter integer C_S_AXI_LITE_ADDR_WIDTH = 12; // Address width of the AXI Lite Interface
    parameter integer C_S_AXI_LITE_DATA_WIDTH = 32; // Data width of the AXI Lite Interface

    // Quoted from tst_bench_top.v of I2C controller core on OpenCores.org.
    // https://opencores.org/projects/i2c
    parameter PRER_LO = 12'h000;
    parameter PRER_HI = 12'h004;
    parameter CTR     = 12'h008;
    parameter RXR     = 12'h00C;
    parameter TXR     = 12'h00C;
    parameter CR      = 12'h010;
    parameter SR      = 12'h010;

    parameter TXR_R   = 12'h014; // undocumented / reserved output
    parameter CR_R    = 12'h018; // undocumented / reserved output

    parameter RD      = 1'b1;
    parameter WR      = 1'b0;
    parameter SADR    = 7'b0010_000;

    // Inputs
    reg ACLK;
    reg ARESETN;
    wire [C_S_AXI_LITE_ADDR_WIDTH-1:0] S_AXI_AWADDR;
    wire [2:0] S_AXI_AWPROT;
    wire S_AXI_AWVALID;
    wire [C_S_AXI_LITE_DATA_WIDTH-1:0] S_AXI_WDATA;
    wire [C_S_AXI_LITE_DATA_WIDTH/8-1:0] S_AXI_WSTRB;
    wire S_AXI_WVALID;
    wire S_AXI_BREADY;
    wire [C_S_AXI_LITE_ADDR_WIDTH-1:0] S_AXI_ARADDR;
    wire [2:0] S_AXI_ARPROT;
    wire S_AXI_ARVALID;
    wire S_AXI_RREADY;

    // Outputs
    wire S_AXI_AWREADY;
    wire S_AXI_WREADY;
    wire [1:0] S_AXI_BRESP;
    wire S_AXI_BVALID;
    wire S_AXI_ARREADY;
    wire [C_S_AXI_LITE_DATA_WIDTH-1:0] S_AXI_RDATA;
    wire [1:0] S_AXI_RRESP;
    wire S_AXI_RVALID;

    wire scl_pad_i;
    wire scl_pad_o;
    wire scl_padoen_o;
    wire sda_pad_i;
    wire sda_pad_o;
    wire sda_padoen_o;

    // InOut
    wire scl;
    wire sda;

    reg [31:0] rdata;

    always #50 ACLK = ~ACLK;

    // Instantiate the Unit Under Test (UUT)
    i2cm_axi4ls #(
        .C_S_AXI_LITE_ADDR_WIDTH(12),
        .C_S_AXI_LITE_DATA_WIDTH(32)
    ) i2cm_axi4ls_i (
        .s_axi_lite_aclk(ACLK),
        .s_axi_lite_resetn(ARESETN),
        .s_axi_lite_awaddr(S_AXI_AWADDR),
        .s_axi_lite_awvalid(S_AXI_AWVALID),
        .s_axi_lite_awready(S_AXI_AWREADY),
        .s_axi_lite_wdata(S_AXI_WDATA),
        .s_axi_lite_wstrb(S_AXI_WSTRB),
        .s_axi_lite_wvalid(S_AXI_WVALID),
        .s_axi_lite_wready(S_AXI_WREADY),
        .s_axi_lite_bresp(S_AXI_BRESP),
        .s_axi_lite_bvalid(S_AXI_BVALID),
        .s_axi_lite_bready(S_AXI_BREADY),
        .s_axi_lite_araddr(S_AXI_ARADDR),
        .s_axi_lite_arvalid(S_AXI_ARVALID),
        .s_axi_lite_arready(S_AXI_ARREADY),
        .s_axi_lite_rdata(S_AXI_RDATA),
        .s_axi_lite_rresp(S_AXI_RRESP),
        .s_axi_lite_rvalid(S_AXI_RVALID),
        .s_axi_lite_rready(S_AXI_RREADY),
        .scl_i(scl_pad_i),
        .scl_o(scl_pad_o),
        .scl_t(scl_padoen_o),
        .sda_i(sda_pad_i),
        .sda_o(sda_pad_o),
        .sda_t(sda_padoen_o)
    );

    // i2c slave model
    i2c_slave_model #(SADR) i2c_slave (
        .scl(scl),
        .sda(sda)
    );

    pullup p1(scl); // pullup scl line
    pullup p2(sda); // pullup sda line

    // AXI4_Lite_Master_BFM
    AXI4_Lite_Master_BFM #(
        .DELAY(DELAY),
        .C_S_AXI_LITE_ADDR_WIDTH(12),
        .C_S_AXI_LITE_DATA_WIDTH(32)
    ) LMBFMi(
        .ACLK(ACLK),
        .S_AXI_AWADDR(S_AXI_AWADDR),
        .S_AXI_AWPROT(S_AXI_AWPROT),
        .S_AXI_AWVALID(S_AXI_AWVALID),
        .S_AXI_AWREADY(S_AXI_AWREADY),
        .S_AXI_WDATA(S_AXI_WDATA),
        .S_AXI_WSTRB(S_AXI_WSTRB),
        .S_AXI_WVALID(S_AXI_WVALID),
        .S_AXI_WREADY(S_AXI_WREADY),
        .S_AXI_BRESP(S_AXI_BRESP),
        .S_AXI_BVALID(S_AXI_BVALID),
        .S_AXI_BREADY(S_AXI_BREADY),
        .S_AXI_ARADDR(S_AXI_ARADDR),
        .S_AXI_ARPROT(S_AXI_ARPROT),
        .S_AXI_ARVALID(S_AXI_ARVALID),
        .S_AXI_ARREADY(S_AXI_ARREADY),
        .S_AXI_RDATA(S_AXI_RDATA),
        .S_AXI_RRESP(S_AXI_RRESP),
        .S_AXI_RVALID(S_AXI_RVALID),
        .S_AXI_RREADY(S_AXI_RREADY)
    );

    scl_sda_buf cd_buf_i(
        .scl_pad_i(scl_pad_i),
        .scl_pad_o(scl_pad_o),
        .scl_padoen_o(scl_padoen_o),
        .sda_pad_i(sda_pad_i),
        .sda_pad_o(sda_pad_o),
        .sda_padoen_o(sda_padoen_o),
        .scl(scl),
        .sda(sda)
    );

    // test
    initial begin
        // Initialize Inputs
        ACLK = 0;
        ARESETN = 0;

        // Wait 100 ns for global reset to finish
        #1000;
        ARESETN = 1;
        #1000;

        // Add stimulus here
        @(posedge ACLK);    // 次のクロックへ
        #DELAY;

        LMBFMi.AXI_LiteM_1Seq_Write(PRER_LO, 32'h29, 0, 2); // address is PRER_LO, data Write = 0x22
        LMBFMi.AXI_LiteM_1Seq_Write(PRER_HI, 32'h0, 0, 2); // address is PRER_HI, data Write = 0x0

        #1000;    // Wait 100 ns
        @(posedge ACLK);    // 次のクロックへ
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Read(PRER_LO, 2, rdata);    // PRER_LO, rmax_wait=0
        LMBFMi.AXI_LiteM_1Seq_Read(PRER_HI, 2, rdata);    // PRER_HI, rmax_wait=0

        #1000;    // Wait 100 ns
        @(posedge ACLK);    // 次のクロックへ
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Write(CTR, 8'h80, 0, 2);    // enable core
        LMBFMi.AXI_LiteM_1Seq_Write(TXR, {SADR,WR}, 0, 2 ); // present slave address, set write-bit
        LMBFMi.AXI_LiteM_1Seq_Write( CR, 8'h90, 0, 2 ); // set command (start, write)

        // check tip bit
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata);
        while(S_AXI_RDATA[1])
            LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata); // SR bit1 TIP

        // send register address
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Write(TXR, 8'h01, 0, 2); // present slave's memory address
        LMBFMi.AXI_LiteM_1Seq_Write(CR, 8'h10, 0, 2); // set command (write)

        // check tip bit
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata);
        while(S_AXI_RDATA[1])
            LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata); // SR bit1 TIP

        // send data byte
        LMBFMi.AXI_LiteM_1Seq_Write(TXR, 8'ha5, 0, 2); // present slave's memory address
        LMBFMi.AXI_LiteM_1Seq_Write(CR, 8'h10, 0, 2); // set command (write)

        // check tip bit
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata);
        while(S_AXI_RDATA[1])
            LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata); // SR bit1 TIP

        // send data byte for next register address (auto_inc)
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Write(TXR, 8'h5a, 0, 2); // present data
        LMBFMi.AXI_LiteM_1Seq_Write(CR, 8'h50, 0, 2); // set command (stop, write)

        // check tip bit
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata);
        while(S_AXI_RDATA[1])
            LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata); // SR bit1 TIP

        // Read Transaction
        #1000;
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Write(TXR, {SADR,WR}, 0, 2 ); // present slave address, set write-bit
        LMBFMi.AXI_LiteM_1Seq_Write( CR, 8'h90, 0, 2 ); // set command (start, write)

        // check tip bit
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata);
        while(S_AXI_RDATA[1])
            LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata); // SR bit1 TIP

        // send register address
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Write(TXR, 8'h01, 0, 2); // present slave's memory address
        LMBFMi.AXI_LiteM_1Seq_Write(CR, 8'h10, 0, 2); // set command (write)

        // check tip bit
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata);
        while(S_AXI_RDATA[1])
            LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata); // SR bit1 TIP

        // repeat start
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Write(TXR, {SADR,WR}, 0, 2 ); // present slave address, set write-bit
        LMBFMi.AXI_LiteM_1Seq_Write( CR, 8'h90, 0, 2 ); // set command (start, write)

        // check tip bit
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata);
        while(S_AXI_RDATA[1])
            LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata); // SR bit1 TIP

        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Write(CR, 8'h68, 0, 2 ); // set command (read, nack, stop)

        // check tip bit
        #DELAY;
        LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata);
        while(S_AXI_RDATA[1])
            LMBFMi.AXI_LiteM_1Seq_Read(SR, 2, rdata); // SR bit1 TIP

        LMBFMi.AXI_LiteM_1Seq_Read(RXR, 2, rdata);
        $display("\nRead data = %x at time %t", rdata, $time);

        $finish;
    end
endmodule

`default_nettype wire

  1. 2021年04月24日 04:30 |
  2. FPGAを使用したシステム
  3. | トラックバック:0
  4. | コメント:0

WishBone バスと AXI4-Lite インターフェースを変換する wb2axi4ls_conv.v とトップの i2cm_axi4ls.v

今回は、”OpenCores.org の I2C controller core をシミュレーションする”で使用した OpenCores.org の I2C controller core を AXI4-Lite インターフェースに変更するための wb2axi4ls_conv.v とトップの i2cm_axi4ls.v を紹介する。

まずは、 wb2axi4ls_conv.v から貼っておく。

// wb2axi4ls_conv.v
// Convert Wishbone Bus Version I2C Master IP to AXI4-Lite Interface
// 2021/04/12 by marsee
//

`default_nettype none

module wb2axi4ls_conv # (
    parameter integer C_S_AXI_LITE_ADDR_WIDTH = 5, // Address width of the AXI Lite Interface
    parameter integer C_S_AXI_LITE_DATA_WIDTH = 32, // Data width of the AXI Lite Interface
    parameter integer WISHBONE_BUS_ADDR_WIDTH = 3,
    parameter integer WISHBONE_BUS_DATA_WIDTH = 8
)(
    input   wire                                    s_axi_lite_aclk,
    input   wire                                    axi_resetn,

    // AXI Lite Write Address Channel
    input   wire                                    s_axi_lite_awvalid,
    output  wire                                    s_axi_lite_awready,
    input   wire    [C_S_AXI_LITE_ADDR_WIDTH-1: 0]  s_axi_lite_awaddr,

    // AXI Lite Write Data Channel
    input   wire                                    s_axi_lite_wvalid,
    output  wire                                    s_axi_lite_wready,
    input   wire    [C_S_AXI_LITE_DATA_WIDTH-1: 0]  s_axi_lite_wdata,
    input   wire    [C_S_AXI_LITE_DATA_WIDTH/8-1:0] s_axi_lite_wstrb,

    // AXI Lite Write Response Channel
    output  wire    [1:0]                           s_axi_lite_bresp,
    output  wire                                    s_axi_lite_bvalid,
    input   wire                                    s_axi_lite_bready,

    // AXI Lite Read Address Channel
    input   wire                                    s_axi_lite_arvalid,
    output  wire                                    s_axi_lite_arready,
    input   wire    [C_S_AXI_LITE_ADDR_WIDTH-1: 0]  s_axi_lite_araddr,

    // AXI Lite Read Data Channel
    output  wire                                    s_axi_lite_rvalid,
    input   wire                                    s_axi_lite_rready,
    output  wire    [C_S_AXI_LITE_DATA_WIDTH-1: 0]  s_axi_lite_rdata,
    output  wire    [1:0]                           s_axi_lite_rresp,

    // WishBone Interface
    output  reg     [WISHBONE_BUS_ADDR_WIDTH-1:0]   wb_adr_i,
    output  wire    [WISHBONE_BUS_DATA_WIDTH-1:0]   wb_dat_i,
    input   wire    [WISHBONE_BUS_DATA_WIDTH-1:0]   wb_dat_o,
    output  wire                                    wb_we_i,
    output  wire                                    wb_stb_i,
    output  wire                                    wb_cyc_i,
    input   wire                                    wb_ack_o,
    input   wire                                    wb_inta_o
);

    // RESP の値の定義
    localparam  RESP_OKAY =     2'b00;
    localparam  RESP_EXOKAY =   2'b01;
    localparam  RESP_SLVERR =   2'b10;
    localparam  RESP_DECERR =   2'b11;

    localparam  IDLE_WR =           5'b00001,   // for wrt_cs
                DATA_WRITE_HOLD =   5'b00010,
                WB_WAIT_ACK_W =     5'b00100,
                BREADY_ASSERT =     5'b01000,
                BREADY_ASSERTED =   5'b10000;

    localparam  IDLE_RD =       3'b001,         //  for rdt_cs
                WB_WAIT_ACK_R = 3'b010,
                AR_DATA_WAIT =  3'b100;

    reg     [4:0]   wrt_cs = IDLE_WR;

    reg     [2:0]   rdt_cs = IDLE_RD;

    reg     reset_1d = 1'b0;
    reg     reset = 1'b0;
    reg     awready = 1'b1;
    reg     bvalid = 1'b0;
    reg     arready = 1'b1;
    reg     wready = 1'b0;
    reg     rvalid = 1'b0;
    wire    aclk;
    reg    [C_S_AXI_LITE_DATA_WIDTH-1:0]   rdata;

    reg     wb_cyc = 1'b0;
    reg     wb_we = 1'b0;

    assign aclk = s_axi_lite_aclk;
    // Synchronization of axi_resetn
    always @(posedge aclk) begin
        reset_1d <= ~axi_resetn;
        reset <= reset_1d;
    end

    // wb_adr_i
    always @(posedge aclk) begin
        if (reset) begin
            wb_adr_i = 3'b000;
        end else if (s_axi_lite_awvalid & s_axi_lite_awready) begin
            wb_adr_i = s_axi_lite_awaddr[4:2];
        end else if (s_axi_lite_arvalid & s_axi_lite_arready) begin
            wb_adr_i = s_axi_lite_araddr[4:2];
        end
    end
    
    // AXI4 Lite Slave Write Transaction State Machine
    always @(posedge aclk) begin
        if (reset) begin
            wrt_cs <= IDLE_WR;
            awready <= 1'b1;
            bvalid <= 1'b0;
            wready <= 1'b0;
            wb_we <= 1'b0;
        end else begin
            case (wrt_cs)
                IDLE_WR :
                    if (s_axi_lite_awvalid & ~s_axi_lite_wvalid) begin  // Write Transaction Start
                        wrt_cs <= DATA_WRITE_HOLD;
                        awready <= 1'b0;
                    end else if (s_axi_lite_awvalid & s_axi_lite_wvalid) begin  // Write Transaction Start with data
                        wrt_cs <= WB_WAIT_ACK_W;
                        awready <= 1'b0;
                        if (s_axi_lite_wstrb[0]) begin
                            wb_we <= 1'b1;
                        end
                    end
                DATA_WRITE_HOLD :
                    if (s_axi_lite_wvalid) begin    // Write data just valid
                        wrt_cs <= WB_WAIT_ACK_W;
                        if (s_axi_lite_wstrb[0]) begin
                            wb_we <= 1'b1;
                        end
                    end
                WB_WAIT_ACK_W :
                    if (wb_ack_o) begin
                        wrt_cs <= BREADY_ASSERT;
                        wready <= 1'b1;
                        wb_we <= 1'b0;
                    end
                BREADY_ASSERT: begin
                    wrt_cs <= BREADY_ASSERTED;
                    wready <= 1'b0;
                    bvalid <= 1'b1;
                end
                BREADY_ASSERTED :
                    if (s_axi_lite_bready) begin    // The write transaction was terminated.
                        wrt_cs <= IDLE_WR;
                        bvalid <= 1'b0;
                        awready <= 1'b1;
                    end
            endcase
        end
    end
    assign s_axi_lite_awready = awready;
    assign s_axi_lite_bvalid = bvalid;
    assign s_axi_lite_bresp = 2'b00;
    assign s_axi_lite_wready = wready;
    assign wb_we_i = wb_we;
    assign wb_dat_i = s_axi_lite_wdata[WISHBONE_BUS_DATA_WIDTH-1:0];

    // AXI4 Lite Slave Read Transaction State Machine
    always @(posedge aclk) begin
        if (reset) begin
            rdt_cs <= IDLE_RD;
            arready <= 1'b1;
            rvalid <= 1'b0;
            rdata <= 0;
        end else begin
            case (rdt_cs)
                IDLE_RD :
                    if (s_axi_lite_arvalid) begin
                        rdt_cs <= WB_WAIT_ACK_R;
                        arready <= 1'b0;
                    end
                WB_WAIT_ACK_R :
                    if(wb_ack_o) begin
                        rdata[WISHBONE_BUS_DATA_WIDTH-1:0] <= wb_dat_o;
                        rdata[C_S_AXI_LITE_DATA_WIDTH-1:WISHBONE_BUS_DATA_WIDTH] = 0;
                        rvalid <= 1'b1;
                        rdt_cs <= AR_DATA_WAIT;
                    end
                AR_DATA_WAIT :
                    if (s_axi_lite_rready) begin
                        rdt_cs <= IDLE_RD;
                        rvalid <= 1'b0;
                        arready <= 1'b1;
                    end
            endcase
        end
    end
    assign s_axi_lite_arready = arready;
    assign s_axi_lite_rvalid = rvalid;
    assign s_axi_lite_rresp = 2'b00;
    assign s_axi_lite_rdata = rdata;

    // wb_cyc_i
    always @(posedge aclk) begin
        if (reset) begin
            wb_cyc <= 1'b0;
        end else begin
            if (wrt_cs==IDLE_WR & s_axi_lite_awvalid & s_axi_lite_wvalid) begin // Write
                wb_cyc <= 1'b1;
            end else if (wrt_cs==DATA_WRITE_HOLD & s_axi_lite_wvalid) begin
                wb_cyc <= 1'b1;
            end else if (wrt_cs==WB_WAIT_ACK_W & wb_ack_o) begin
                wb_cyc <= 1'b0;
            end else if (rdt_cs==IDLE_RD & s_axi_lite_arvalid) begin // Read
                wb_cyc <= 1'b1;
            end else if (rdt_cs==WB_WAIT_ACK_R & wb_ack_o) begin
                wb_cyc <= 1'b0;
            end
        end
    end
    assign wb_cyc_i = wb_cyc;
    assign wb_stb_i = wb_cyc;
endmodule

`default_nettype wire


次に、トップの i2cm_axi4ls.v を貼っておく。

`default_nettype none

// i2cm_axi4ls.v
// 2021/04/16 by marsee
//

module i2cm_axi4ls #(
    parameter integer C_S_AXI_LITE_ADDR_WIDTH = 12, // Address width of the AXI Lite Interface
    parameter integer C_S_AXI_LITE_DATA_WIDTH = 32 // Data width of the AXI Lite Interface
)(
    input   wire                                    s_axi_lite_aclk,
    input   wire                                    s_axi_lite_resetn,

    // AXI Lite Write Address Channel
    input   wire                                    s_axi_lite_awvalid,
    output  wire                                    s_axi_lite_awready,
    input   wire    [C_S_AXI_LITE_ADDR_WIDTH-1: 0]  s_axi_lite_awaddr,

    // AXI Lite Write Data Channel
    input   wire                                    s_axi_lite_wvalid,
    output  wire                                    s_axi_lite_wready,
    input   wire    [C_S_AXI_LITE_DATA_WIDTH-1: 0]  s_axi_lite_wdata,
    input   wire    [C_S_AXI_LITE_DATA_WIDTH/8-1:0] s_axi_lite_wstrb,

    // AXI Lite Write Response Channel
    output  wire    [1:0]                           s_axi_lite_bresp,
    output  wire                                    s_axi_lite_bvalid,
    input   wire                                    s_axi_lite_bready,

    // AXI Lite Read Address Channel
    input   wire                                    s_axi_lite_arvalid,
    output  wire                                    s_axi_lite_arready,
    input   wire    [C_S_AXI_LITE_ADDR_WIDTH-1: 0]  s_axi_lite_araddr,

    // AXI Lite Read Data Channel
    output  wire                                    s_axi_lite_rvalid,
    input   wire                                    s_axi_lite_rready,
    output  wire    [C_S_AXI_LITE_DATA_WIDTH-1: 0]  s_axi_lite_rdata,
    output  wire    [1:0]                           s_axi_lite_rresp,

    // I2C Bus
    output  wire                                    scl_o,
    output  wire                                    scl_t,
    input   wire                                    scl_i,
    output  wire                                    sda_o,
    output  wire                                    sda_t,
    input   wire                                    sda_i
    
    //inout   wire                                    scl,
    //inout   wire                                    sda
);

    // WishBone Interface
    wire    [2:0]   wb_adr_i;
    wire    [7:0]   wb_dat_i;
    wire    [7:0]   wb_dat_o;
    wire            wb_we_i;
    wire            wb_stb_i;
    wire            wb_cyc_i;
    wire            wb_ack_o;
    wire            wb_inta_o;
    wire            wb_rst_i;

    wb2axi4ls_conv #(
        .C_S_AXI_LITE_ADDR_WIDTH(C_S_AXI_LITE_ADDR_WIDTH),
        .C_S_AXI_LITE_DATA_WIDTH(C_S_AXI_LITE_DATA_WIDTH)
    ) wb2axi4c_i (
        .s_axi_lite_aclk(s_axi_lite_aclk),
        .axi_resetn(s_axi_lite_resetn),
        .s_axi_lite_awvalid(s_axi_lite_awvalid),
        .s_axi_lite_awready(s_axi_lite_awready),
        .s_axi_lite_awaddr(s_axi_lite_awaddr),
        .s_axi_lite_wvalid(s_axi_lite_wvalid),
        .s_axi_lite_wready(s_axi_lite_wready),
        .s_axi_lite_wdata(s_axi_lite_wdata),
        .s_axi_lite_wstrb(s_axi_lite_wstrb),
        .s_axi_lite_bresp(s_axi_lite_bresp),
        .s_axi_lite_bvalid(s_axi_lite_bvalid),
        .s_axi_lite_bready(s_axi_lite_bready),
        .s_axi_lite_arvalid(s_axi_lite_arvalid),
        .s_axi_lite_arready(s_axi_lite_arready),
        .s_axi_lite_araddr(s_axi_lite_araddr),
        .s_axi_lite_rvalid(s_axi_lite_rvalid),
        .s_axi_lite_rready(s_axi_lite_rready),
        .s_axi_lite_rdata(s_axi_lite_rdata),
        .s_axi_lite_rresp(s_axi_lite_rresp),
        .wb_adr_i(wb_adr_i),
        .wb_dat_i(wb_dat_i),
        .wb_dat_o(wb_dat_o),
        .wb_we_i(wb_we_i),
        .wb_stb_i(wb_stb_i),
        .wb_cyc_i(wb_cyc_i),
        .wb_ack_o(wb_ack_o),
        .wb_inta_o(wb_inta_o)
    );

    assign wb_rst_i = ~s_axi_lite_resetn;
    i2c_master_top i2cm_wb_i(
        .wb_clk_i(s_axi_lite_aclk),
        .wb_rst_i(wb_rst_i),
        .arst_i(1'b1),
        .wb_adr_i(wb_adr_i),
        .wb_dat_i(wb_dat_i),
        .wb_dat_o(wb_dat_o),
        .wb_we_i(wb_we_i),
        .wb_stb_i(wb_stb_i),
        .wb_cyc_i(wb_cyc_i),
        .wb_ack_o(wb_ack_o),
        .wb_inta_o(wb_inta_o),
        .scl_pad_i(scl_i),
        .scl_pad_o(scl_o),
        .scl_padoen_o(scl_t),
        .sda_pad_i(sda_i),
        .sda_pad_o(sda_o),
        .sda_padoen_o(sda_t)
    );
 endmodule

`default_nettype wire


なお、I2C の出力は、ブロックデザインで scl, sda を直接出力するとエラーになってしまう。そこで、 _t, _i, _o の IOBUF の入出力のポートを出した。
しかも、 scl_t, scl_i, scl_o, sda_t, sda_i, sda_o の名前にしないとラッパー・ファイルを作る時にIIC として推論してくれなかったので、この信号名にしたほうが良いと思う。(違うポート名にして、IP パッケージャーで関連付けしたのだが IIC として推論してくれなかった orz)
  1. 2021年04月23日 03:50 |
  2. FPGAを使用したシステム
  3. | トラックバック:0
  4. | コメント:0

AXI4-Lite Master Bus Functional Model を修正した

OpenCores.org の I2C controller core をシミュレーションする”で使用した OpenCores.org の I2C controller core を AXI4-Lite インターフェースに変更したのだが、その際に、AXI4-Lite Master BFM を使用した。ということで、”AXI4 Master Bus Functional Model を修正した”を書いたが、その AXI4 Master BFM にかぶせる AXI4-Lite Master BFM で Read したデータを出力できるように変更した。

AXI4-Lite Master BFM の元ネタは、”AXI4 Lite Master BFM の Verilog HDL コード”だ。

、”AXI4 Lite Master BFM の Verilog HDL コード”の説明を引用する。

AXI4 Lite Master BFM の Verilog HDL コードを貼っておく。AXI4 Lite Master BFM は、AXI4 Master BFM のラッパーとして作られている。
Verilog HDL の task で書いてあって、下に示す。7つの task がある。
・AXI_LiteM_1Seq_Write(Write Address, Write Data, Write Response をシーケンシャルにオーバーラップせずに行う)
 ・AXI_LiteM_WAC(Write Address Channel の Transaction を実行する)
 ・AXI_LiteM_WDC(Write Data Channel の Transaction を実行する)
 ・AXI_LiteM_WRC(Write Response Channel の Transaction を実行する)
・AXI_LiteM_1Seq_Read(Read Address, Read Data をシーケンシャルに行う)
 ・AXI_LiteM_RAC(Read Address Channel の Transaction を実行する)
 ・AXI_LiteM_RDC(Read Data Channel の Transaction を実行する)


この AXI4-Lite Master BFM の AXI_LiteM_1Seq_Read と AXI_LiteM_RDC タスクに rdata 出力を追加した。

AXI4_Lite_Master_BFM.v を貼っておく。

// AXI4 bus Lite Master Bus Fucntion Mode
// AXI4_Lite_Master_BFM.v
// https://marsee101.blog.fc2.com/blog-entry-2673.html
// 2013/12/14
// AXI4_Master_BFM のラッパー
// 2021/04/15 AXI_LiteM_1Seq_Read と AXI_LiteM_RDC に rdata 出力を追加
//

`default_nettype none

`timescale 100ps / 1ps

module AXI4_Lite_Master_BFM #(
    parameter DELAY = 10,
    parameter integer C_S_AXI_LITE_ADDR_WIDTH = 12, // Address width of the AXI Lite Interface
    parameter integer C_S_AXI_LITE_DATA_WIDTH = 32 // Data width of the AXI Lite Interface
)(
    input   wire    ACLK,

    output wire [C_S_AXI_LITE_ADDR_WIDTH-1:0] S_AXI_AWADDR,
    output wire [2:0] S_AXI_AWPROT,
    output wire S_AXI_AWVALID,
    output wire [C_S_AXI_LITE_DATA_WIDTH-1:0] S_AXI_WDATA,
    output wire [C_S_AXI_LITE_DATA_WIDTH/8-1:0] S_AXI_WSTRB,
    output wire S_AXI_WVALID,
    output wire S_AXI_BREADY,
    output wire [C_S_AXI_LITE_ADDR_WIDTH-1:0] S_AXI_ARADDR,
    output wire [2:0] S_AXI_ARPROT,
    output wire S_AXI_ARVALID,
    output wire S_AXI_RREADY,

    input wire S_AXI_AWREADY,
    input wire S_AXI_WREADY,
    input wire [1:0] S_AXI_BRESP,
    input wire S_AXI_BVALID,
    input wire S_AXI_ARREADY,
    input wire [C_S_AXI_LITE_DATA_WIDTH-1:0] S_AXI_RDATA,
    input wire [1:0] S_AXI_RRESP,
    input wire S_AXI_RVALID
);

    parameter   ASIZE_BT_4  = 3'd2; // 32 bit width
    parameter   ASIZE_BT_2 = 3'd1;  // 16 bit width
    parameter   ASIZE_BT_1 = 3'd0;  // 8 bit width

    parameter   ABURST_FIXED    = 2'd0;
    parameter   ABURST_INCR = 2'd1;
    parameter   ABURST_WRAP = 2'd2;

    // RESP の値の定義
    parameter   RESP_OKAY =     2'b00;
    parameter   RESP_EXOKAY =   2'b01;
    parameter   RESP_SLVERR =   2'b10;
    parameter   RESP_DECERR =   2'b11;

    reg  [7:0]  awlen_hold = 0;
    reg  [0:0]  wid_hold = 0;
    reg  axi_w_transaction_active = 0;
    reg  axi_r_transaction_active = 0;
    reg  [7:0]  arlen_hold = 0;

    // AXI4_BFM のインスタンス
    AXI4_Master_BFM #(
        .DELAY(DELAY),
        .C_S_AXI_ADDR_WIDTH(C_S_AXI_LITE_ADDR_WIDTH),
        .C_S_AXI_DATA_WIDTH(C_S_AXI_LITE_DATA_WIDTH)
    ) MBFMi(
        .ACLK(ACLK),
        .S_AXI_AWID(),
        .S_AXI_AWADDR(S_AXI_AWADDR),
        .S_AXI_AWLEN(),
        .S_AXI_AWSIZE(),
        .S_AXI_AWBURST(),
        .S_AXI_AWLOCK(),
        .S_AXI_AWCACHE(),
        .S_AXI_AWPROT(S_AXI_AWPROT),
        .S_AXI_AWREGION(),
        .S_AXI_AWQOS(),
        .S_AXI_AWUSER(),
        .S_AXI_AWVALID(S_AXI_AWVALID),
        .S_AXI_AWREADY(S_AXI_AWREADY),
        .S_AXI_WID(),
        .S_AXI_WDATA(S_AXI_WDATA),
        .S_AXI_WSTRB(S_AXI_WSTRB),
        .S_AXI_WLAST(),
        .S_AXI_WUSER(),
        .S_AXI_WVALID(S_AXI_WVALID),
        .S_AXI_WREADY(S_AXI_WREADY),
        .S_AXI_BID(1'b0),
        .S_AXI_BRESP(S_AXI_BRESP),
        .S_AXI_BUSER(1'b0),
        .S_AXI_BVALID(S_AXI_BVALID),
        .S_AXI_BREADY(S_AXI_BREADY),
        .S_AXI_ARID(),
        .S_AXI_ARADDR(S_AXI_ARADDR),
        .S_AXI_ARLEN(),
        .S_AXI_ARSIZE(),
        .S_AXI_ARBURST(),
        .S_AXI_ARLOCK(),
        .S_AXI_ARCACHE(),
        .S_AXI_ARPROT(S_AXI_ARPROT),
        .S_AXI_ARREGION(),
        .S_AXI_ARQOS(),
        .S_AXI_ARUSER(),
        .S_AXI_ARVALID(S_AXI_ARVALID),
        .S_AXI_ARREADY(S_AXI_ARREADY),
        .S_AXI_RID(1'b0),
        .S_AXI_RDATA(S_AXI_RDATA),
        .S_AXI_RRESP(S_AXI_RRESP),
        .S_AXI_RLAST(1'b1),
        .S_AXI_RUSER(1'b0),
        .S_AXI_RVALID(S_AXI_RVALID),
        .S_AXI_RREADY(S_AXI_RREADY)
    );

    // Write Channel
    // wait_clk_bready : 0 - bready の Wait は無し、0以外 - bready の Wait は wait_clk_bready の値の Wait が入る
    // wmax_wait : 0 - wvalid の Wait は無し、0以外 - wmax_wait を最大値とするランダムな値の Wait が wvalid に入る
    task AXI_LiteM_1Seq_Write; // Write Address, Write Data, Write Response をシーケンシャルにオーバーラップせずに行う。
        input   [C_S_AXI_LITE_ADDR_WIDTH-1:0]  awaddr;
        input   [C_S_AXI_LITE_DATA_WIDTH-1:0]  wdata;
        input   [7:0]   wait_clk_bready;
        input   [7:0]   wmax_wait;
        begin
            MBFMi.AXI_Master_1Seq_Write(1'b0, awaddr, 8'd0, ASIZE_BT_4, ABURST_INCR, wdata, wait_clk_bready, wmax_wait);
        end
    endtask

    // Write Address Channel
    task AXI_LiteM_WAC;
        input   [C_S_AXI_LITE_ADDR_WIDTH-1:0]  awaddr;
        begin
            MBFMi.AXI_MASTER_WAC(1'b0, awaddr, 8'd0, ASIZE_BT_4, ABURST_INCR);
        end
    endtask

    // Write Data Channel
    // wmax_wait : 0 - wvalid の Wait は無し、0以外 - wmax_wait を最大値とするランダムな値の Wait が wvalid に入る
    task AXI_LiteM_WDC;    // WDATA は+1する
    // とりあえず、WSTRBはオール1にする
        input   [C_S_AXI_LITE_DATA_WIDTH-1:0]  wdata;
        input   [7:0]   wmax_wait;  // Write時の最大wait数
        begin
            MBFMi.AXI_MASTER_WDC(wdata, wmax_wait);
        end
    endtask

    // Write Response Channel
    // wait_clk_bready : 0 - bready の Wait は無し、0以外 - bready の Wait は wait_clk_bready の値の Wait が入る
    task AXI_LiteM_WRC;    // wait_clk_bready
        input   [7:0]   wait_clk_bready;
        begin
            MBFMi.AXI_MASTER_WRC(wait_clk_bready);
        end
    endtask

    // Read Channel
    task AXI_LiteM_1Seq_Read; // Read Address, Read Data をシーケンシャルに行う
        input   [C_S_AXI_LITE_ADDR_WIDTH-1:0]  araddr;
        input   [7:0]   rmax_wait;  // Read時の最大wait数
        output  [C_S_AXI_LITE_DATA_WIDTH-1:0]    rdata; // S_AXI_RDATA のラッチ
        begin
            MBFMi.AXI_Master_1Seq_Read(1'b0, araddr, 8'd0, ASIZE_BT_4, ABURST_INCR, rmax_wait, rdata);
        end
    endtask

    // Read Address Channel
    task AXI_LiteM_RAC;
        input   [C_S_AXI_LITE_ADDR_WIDTH-1:0]  araddr;
        begin
            MBFMi.AXI_MASTER_RAC(1'b0, araddr, 8'd0, ASIZE_BT_4, ABURST_INCR);
        end
    endtask

    // Read Data Channel
    task AXI_LiteM_RDC; // S_AXI_RLAST がアサートされるまでS_AXI_RREADY をアサートする
        input   [7:0]   rmax_wait;  // Read時の最大wait数
        output  [C_S_AXI_LITE_DATA_WIDTH-1:0]    rdata; // S_AXI_RDATA のラッチ
        begin
            MBFMi.AXI_MASTER_RDC(rmax_wait, rdata);
        end
    endtask
endmodule

`default_nettype wire

  1. 2021年04月22日 04:31 |
  2. AXI4バス
  3. | トラックバック:0
  4. | コメント:0

AXI4 Master Bus Functional Model を修正した

OpenCores.org の I2C controller core をシミュレーションする”で使用した OpenCores.org の I2C controller core を AXI4-Lite インターフェースに変更したのだが、その際に、AXI4-Lite Master BFM を使用した。
AXI4-Lite Master BFM は AXI4 Master BFM をオーバーロードしているということで、 AXI4 Master BFM も修正したので、ブログに書いておく。

AXI4 Master BFM は”キャラクタ・ディスプレイ・コントローラをAXI4スレーブにする8.3(AXI Master BFMのVerilogコード)”のブログに書いた。その AXI4 Master BFM に Read した時のデータをタスクに追加した。

キャラクタ・ディスプレイ・コントローラをAXI4スレーブにする8.3(AXI Master BFMのVerilogコード)”の説明文を貼っておく。

AXI Master BFMの Write の task がAXI_Master_1Seq_Write だ。このtaskは、(Write Address, Write Data), Write Response をシーケンシャルにオーバーラップせずに行う。今回の修正で、(Write Address, Write Data)はfork ~ join を使用して、オーバーラップするように変更した。AXI_Master_1Seq_Writeは、8個の引数を持つ。wait_clk_bready、wmax_wait 以外の信号はAXIバスの信号なので、説明は省く。wait_clk_bready はWriteのデータ転送が終了して、BREADYをアサートするまでのWaitクロック数を設定する。Writeデータ転送時にランダムな数のWaitが入るが、wmax_wait は、そのWait の最大値を指示する。

AXI Master BFMの Read の task が、AXI_Master_1Seq_Read で、Read Address, Read Data をシーケンシャルに行う。


今回は、AXI_Master_1Seq_Read, AXI_MASTER_RDC タスクに rdata 出力を追加した。

それでは、 AXI4_Master_BFM.v を貼っておく。

// AXI4 bus Master Bus Fucntion Mode
// AXI4_Master_BFM.v
// https://marsee101.blog.fc2.com/blog-entry-2288.html
// 2012/10/24 : 修正、S_AXI_AWREADYが1になるのを確認してからS_AXI_WVALIDを1にしていたのでは、AXIバスの非標準となる。
// よって、AXI_MASTER_WAC とAXI_MASTER_WDC をfork ~ join で並列に実行する
// 2013/12/14 : input に DELAYを入れるように変更
// 2021/04/15 : AXI_Master_1Seq_Read, AXI_MASTER_RDC に rdata 出力を追加
//

`default_nettype none

`timescale 100ps / 1ps

module AXI4_Master_BFM #(
    parameter DELAY    = 10,
    parameter integer C_S_AXI_ADDR_WIDTH = 32, // Address width of the AXI Interface
    parameter integer C_S_AXI_DATA_WIDTH = 32 // Data width of the AXI Interface
)(
    input    wire    ACLK,

    output reg [0:0] S_AXI_AWID = 0,
    output reg [C_S_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR = 0,
    output reg [7:0] S_AXI_AWLEN = 0,
    output reg [2:0] S_AXI_AWSIZE = 0,
    output reg [1:0] S_AXI_AWBURST = 0,
    output reg [1:0] S_AXI_AWLOCK = 0,
    output reg [3:0] S_AXI_AWCACHE = 3,    // Normal Non-cacheable bufferable
    output reg [2:0] S_AXI_AWPROT = 0,
    output reg [3:0] S_AXI_AWREGION = 0,
    output reg [3:0] S_AXI_AWQOS = 0,
    output reg [0:0] S_AXI_AWUSER = 0,
    output reg S_AXI_AWVALID = 0,
    output reg [0:0] S_AXI_WID = 0,
    output reg [C_S_AXI_DATA_WIDTH-1:0] S_AXI_WDATA = 0,
    output reg [C_S_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB = 0,
    output reg S_AXI_WLAST = 0,
    output reg [0:0] S_AXI_WUSER = 0,
    output reg S_AXI_WVALID = 0,
    output reg S_AXI_BREADY = 0,
    output reg [0:0] S_AXI_ARID = 0,
    output reg [C_S_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR = 0,
    output reg [7:0] S_AXI_ARLEN = 0,
    output reg [2:0] S_AXI_ARSIZE = 0,
    output reg [1:0] S_AXI_ARBURST = 0,
    output reg [1:0] S_AXI_ARLOCK = 0,
    output reg [3:0] S_AXI_ARCACHE = 2, // Normal Non-cacheable bufferable
    output reg [2:0] S_AXI_ARPROT = 0,
    output reg [3:0] S_AXI_ARREGION = 0,
    output reg [3:0] S_AXI_ARQOS = 0,
    output reg [0:0] S_AXI_ARUSER = 0,
    output reg S_AXI_ARVALID = 0,
    output reg S_AXI_RREADY = 0,

    input wire S_AXI_AWREADY,
    input wire S_AXI_WREADY,
    input wire [0:0] S_AXI_BID,
    input wire [1:0] S_AXI_BRESP,
    input wire [0:0] S_AXI_BUSER,
    input wire S_AXI_BVALID,
    input wire S_AXI_ARREADY,
    input wire [0:0] S_AXI_RID,
    input wire [C_S_AXI_DATA_WIDTH-1:0] S_AXI_RDATA,
    input wire [1:0] S_AXI_RRESP,
    input wire S_AXI_RLAST,
    input wire [0:0] S_AXI_RUSER,
    input wire S_AXI_RVALID
);

    reg     [7:0]    awlen_hold = 0;
    reg     [0:0]    wid_hold = 0;
    reg     axi_w_transaction_active = 0;
    reg     axi_r_transaction_active = 0;
    reg     [7:0]    arlen_hold = 0;

    reg     S_AXI_AWREADY_d;
    reg     S_AXI_WREADY_d;
    reg     [0:0] S_AXI_BID_d;
    reg     [1:0] S_AXI_BRESP_d;
    reg     [0:0] S_AXI_BUSER_d;
    reg     S_AXI_BVALID_d;
    reg     S_AXI_ARREADY_d;
    reg     [0:0] S_AXI_RID_d;
    reg     [C_S_AXI_DATA_WIDTH-1:0] S_AXI_RDATA_d;
    reg     [1:0] S_AXI_RRESP_d;
    reg     S_AXI_RLAST_d;
    reg     [0:0] S_AXI_RUSER_d;
    reg     S_AXI_RVALID_d;

    always @* S_AXI_AWREADY_d <= #DELAY S_AXI_AWREADY;
    always @* S_AXI_WREADY_d <= #DELAY S_AXI_WREADY;
    always @* S_AXI_BID_d <= #DELAY S_AXI_BID;
    always @* S_AXI_BRESP_d <= #DELAY S_AXI_BRESP;
    always @* S_AXI_BUSER_d <= #DELAY S_AXI_BUSER;
    always @* S_AXI_BVALID_d <= #DELAY S_AXI_BVALID;
    always @* S_AXI_ARREADY_d <= #DELAY S_AXI_ARREADY;
    always @* S_AXI_RID_d <= #DELAY S_AXI_RID;
    always @* S_AXI_RDATA_d <= #DELAY S_AXI_RDATA;
    always @* S_AXI_RRESP_d <= #DELAY S_AXI_RRESP;
    always @* S_AXI_RLAST_d <= #DELAY S_AXI_RLAST;
    always @* S_AXI_RUSER_d <= #DELAY S_AXI_RUSER;
    always @* S_AXI_RVALID_d <= #DELAY S_AXI_RVALID;

    // Write Channel
    // wait_clk_bready : 0 - bready の Wait は無し、0以外 - bready の Wait は wait_clk_bready の値の Wait が入る
    // wmax_wait : 0 - wvalid の Wait は無し、0以外 - wmax_wait を最大値とするランダムな値の Wait が wvalid に入る
    task AXI_Master_1Seq_Write;    // Write Address; Write Data, Write Response をシーケンシャルにオーバーラップせずに行う。
        input    [0:0]    awid;
        input    [C_S_AXI_ADDR_WIDTH-1:0]    awaddr;
        input    [7:0]    awlen;
        input    [2:0]    awsize;
        input    [1:0]    awburst;
        input    [C_S_AXI_DATA_WIDTH-1:0]    wdata;
        input    [7:0]    wait_clk_bready;
        input    [7:0]    wmax_wait;
        begin
            fork
                AXI_MASTER_WAC(awid, awaddr, awlen, awsize, awburst);
                AXI_MASTER_WDC(wdata, wmax_wait);
            join
            AXI_MASTER_WRC(wait_clk_bready);
        end
    endtask

    // Write Address Channel
    task AXI_MASTER_WAC;
        input    [0:0]    awid;
        input    [C_S_AXI_ADDR_WIDTH-1:0]    awaddr;
        input    [7:0]    awlen;
        input    [2:0]    awsize;
        input    [1:0]    awburst;
        begin
            S_AXI_AWID        = awid;
            S_AXI_AWADDR    = awaddr;
            S_AXI_AWLEN        = awlen;
            S_AXI_AWSIZE    = awsize;
            S_AXI_AWBURST    = awburst;
            S_AXI_AWVALID    = 1'b1;

            if (axi_w_transaction_active == 1'b0) begin // AXI Write トランザクションが開始されている場合は戻る
                axi_w_transaction_active = 1'b1; // AXIトランザクション開始

                awlen_hold        = awlen; // Write Data Channel のためにバースト数を取っておく
                @(posedge ACLK);    // 次のクロックへ

                while (~S_AXI_AWREADY_d) begin    // S_AXI_AWREADY が1になるまで待つ
                    #DELAY;
                    @(posedge ACLK);    // 次のクロックへ
                end

                #DELAY;
                S_AXI_AWID         = 0;
                S_AXI_AWADDR    = 0;
                S_AXI_AWLEN     = 0;
                S_AXI_AWSIZE     = 0;
                S_AXI_AWBURST     = 0;
                S_AXI_AWVALID     = 1'b0;
                @(posedge ACLK);    // 次のクロックへ
                #DELAY;
            end
        end
    endtask

    // Write Data Channel
    // wmax_wait : 0 - wvalid の Wait は無し、0以外 - wmax_wait を最大値とするランダムな値の Wait が wvalid に入る
    task AXI_MASTER_WDC;    // WDATA は+1する
    // とりあえず、WSTRBはオール1にする
        input    [C_S_AXI_DATA_WIDTH-1:0]    wdata;
        input    [7:0]    wmax_wait;    // Write時の最大wait数
        integer    i, j, val;
        begin
            i = 0; j = 0;
            S_AXI_WSTRB = {(C_S_AXI_DATA_WIDTH/8-1){1'b1}};

            while (~S_AXI_AWVALID) begin    // S_AXI_AWVALID が1になるまで待つ
                @(posedge ACLK);    // 次のクロックへ
                #DELAY;
            end

            while (i<=awlen_hold) begin
                if (wmax_wait == 0) // wmax_wait が0の時は$random を実行しない
                    val = 0;
                else
                    val = $unsigned($random) % (wmax_wait+1);

                if (val == 0) begin // waitなし
                    S_AXI_WVALID = 1'b1;
                end else begin // waitあり
                    S_AXI_WVALID = 1'b0;
                    for (j=0; j<val; j=j+1) begin
                        @(posedge ACLK);    // 次のクロックへ
                        #DELAY;
                    end
                    S_AXI_WVALID = 1'b1; // wait終了
                end

                if (i == awlen_hold)
                    S_AXI_WLAST = 1'b1;
                else
                    S_AXI_WLAST = 1'b0;
                S_AXI_WDATA = wdata;
                wdata = wdata + 1;

                @(posedge ACLK);    // 次のクロックへ

                while (~S_AXI_WREADY_d) begin    // S_AXI_WREADY が0の時は1になるまで待つ
                    #DELAY;
                    @(posedge ACLK);    // 次のクロックへ
                end
                #DELAY;

                i = i + 1;
            end
            S_AXI_WVALID = 1'b0;
            S_AXI_WLAST = 1'b0;
            S_AXI_WSTRB = {(C_S_AXI_DATA_WIDTH/8-1){1'b0}};
        end
    endtask

    // Write Response Channel
    // wait_clk_bready : 0 - bready の Wait は無し、0以外 - bready の Wait は wait_clk_bready の値の Wait が入る
    task AXI_MASTER_WRC;    // wait_clk_bready
        input    [7:0]    wait_clk_bready;
        integer    i;
        begin
            for (i=0; i<wait_clk_bready; i=i+1) begin
                @(posedge ACLK);    // 次のクロックへ
                #DELAY;
            end

            S_AXI_BREADY = 1'b1;


            @(posedge ACLK);    // 次のクロックへ

            while (~S_AXI_BVALID_d) begin // S_AXI_BVALID が1になるまでWait
                #DELAY;
                @(posedge ACLK);    // 次のクロックへ
            end
            #DELAY;

            S_AXI_BREADY = 1'b0;

            axi_w_transaction_active = 1'b0; // AXIトランザクション終了
            @(posedge ACLK);
            #DELAY;
        end
    endtask

    // Read Channel
    task AXI_Master_1Seq_Read; // Read Address, Read Data をシーケンシャルに行う。
        input    [0:0]    arid;
        input    [C_S_AXI_ADDR_WIDTH-1:0]    araddr;
        input    [7:0]    arlen;
        input    [2:0]    arsize;
        input    [1:0]    arburst;
        input    [7:0]    rmax_wait;    // Read時の最大wait数
        output  [C_S_AXI_DATA_WIDTH-1:0]    rdata; // S_AXI_RDATA のラッチ
        begin
            AXI_MASTER_RAC(arid, araddr, arlen, arsize, arburst);
            AXI_MASTER_RDC(rmax_wait, rdata);
        end
    endtask

    // Read Address Channel
    task AXI_MASTER_RAC;
        input    [0:0]    arid;
        input    [C_S_AXI_ADDR_WIDTH-1:0]    araddr;
        input    [7:0]    arlen;
        input    [2:0]    arsize;
        input    [1:0]    arburst;
        begin
            S_AXI_ARID         = arid;
            S_AXI_ARADDR    = araddr;
            S_AXI_ARLEN        = arlen;
            S_AXI_ARSIZE    = arsize;
            S_AXI_ARBURST    = arburst;
            S_AXI_ARVALID     = 1'b1;

            if (axi_r_transaction_active == 1'b0) begin // AXI Read トランザクションが開始されている場合は戻る
                arlen_hold    =arlen; // Read Data Channel のためにバースト数を取っておく
                @(posedge ACLK);    // 次のクロックへ

                while (~S_AXI_ARREADY_d) begin    // S_AXI_ARREADY が1になるまで待つ
                    #DELAY;
                    @(posedge ACLK);    // 次のクロックへ
                end

                #DELAY;
                S_AXI_ARID         = 0;
                S_AXI_ARADDR    = 0;
                S_AXI_ARLEN     = 0;
                S_AXI_ARSIZE     = 0;
                S_AXI_ARBURST     = 0;
                S_AXI_ARVALID     = 1'b0;
                @(posedge ACLK);    // 次のクロックへ
                #DELAY;
                axi_r_transaction_active = 1'b1; // AXIトランザクション開始
            end
        end
    endtask

    // Read Data Channel
    task AXI_MASTER_RDC; // S_AXI_RLAST がアサートされるまでS_AXI_RREADY をアサートする
        input    [7:0]    rmax_wait;    // Read時の最大wait数
        output  [C_S_AXI_DATA_WIDTH-1:0]    rdata; // S_AXI_RDATA のラッチ
        integer i, val;
        begin
            while (~(S_AXI_RLAST_d & S_AXI_RVALID_d & S_AXI_RREADY)) begin // S_AXI_RLAST & S_AXI_RVALID & S_AXI_RREADY で終了
                if (rmax_wait == 0) begin // rmax_wait が0の時は$random を実行しない
                    val = 0;
                    S_AXI_RREADY = 1'b1;
                end else begin
                    val = $unsigned($random) % (rmax_wait+1);
                    if (val == 0)
                        S_AXI_RREADY = 1'b1;
                    else
                        S_AXI_RREADY = 1'b0;
                end
                #DELAY;

                for (i=0; i<val; i=i+1) begin // ランダム値でWait、val=0の時はスキップ
                    @(posedge ACLK);    // 次のクロックへ
                    #DELAY;
                end

                S_AXI_RREADY = 1'b1;
                @(posedge ACLK);    // 次のクロックへ
                while (~S_AXI_RVALID_d) begin // S_AXI_RVALID が1になるまでWait
                    #DELAY;
                    @(posedge ACLK);    // 次のクロックへ
                end
                #DELAY;
                rdata = S_AXI_RDATA;
            end
            #DELAY;

            S_AXI_RREADY = 1'b0;
            axi_r_transaction_active = 1'b0; // AXIトランザクション終了
            @(posedge ACLK);
            #DELAY;
        end
    endtask

endmodule

`default_nettype wire

  1. 2021年04月21日 04:14 |
  2. AXI4バス
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream の IP を使用して Vitis Vision Library で加工されたデータをディスプレイに表示する3(Vitis プロジェクト)

AXI4-Stream の IP を使用して Vitis Vision Library で加工されたデータをディスプレイに表示する2(Vivado プロジェクト)”の続き。

AXI4-Stream 版ビットマップ・ディスプレイ・コントローラと AXI4-Stream 出力 xf_8uc3_2axis IP を使用して”Vitis Vision Library の medianblur を ZYBO Z7-20 で使ってみる1”の回路を変更した。前回は Vivado 2020.2 で median_vision_axis プロジェクトを作成して、ブロックデザインを作成し、論理合成、インプリメンテーション、ビットストリームの生成が成功して、ハードウェアをエクスポートした。今回は Vitis 2020.2 を起動して、プラットフォームとアプリケーション・プロジェクトを作成する。

Vitis 2020.2 を起動して、median_v_axi_bd_wrapper プラットフォームと median_vison_axis アプリケーション・プロジェクトを作成した。
Vitis_Vision_disp_27_210419.png

Vitis Vision Library の medianblur を ZYBO Z7-20 で使ってみる3”から画像をヘッダに変換した bmp_data.h を使用した。
更に、median_vision.c を参考にして、 median_vision_axis.c を作成した。
Vitis_Vision_disp_28_210420.png

ビルドも成功したので、これで ZYBO Z7-20 を使って、テストしてみたところ表示することができた。

gtkterm の様子を示す。
0 を入力するとノイズの混じった元画像で、1 を入力するとメディアン・フィルタでノイズを除去した綺麗な画像が表示された。
Vitis_Vision_disp_29_210420.png

よくわからないかも知れないが、ノイズの混じった元画像を示す。
Vitis_Vision_disp_30_210420.jpg

メディアン・フィルタ後の綺麗になった画像を示す。
Vitis_Vision_disp_31_210420.jpg

最後に、 median_vision_axis.c を貼っておく。

// median_vision_axis.c
// 2021/04/19 by marsee
//

#include <stdio.h>
#include <stdint.h>
#include "xil_io.h"
#include "xparameters.h"

#include "xmedian_blur_accel.h"
#include "xxf_8uc3_2axis.h"

#include "bmp_data.h"

#define ORG_PICT_XF_8UC4_ADDR   0x10000000
#define ORG_PICT_XF_8UC3_ADDR   0x10200000
#define FILTER_XF_8UC3_ADDR     0x10400000
#define FILTER_XF_8UC4_ADDR     0x10600000

#define HORIZONTAL_PIXELS   800
#define VERTICAL_LINES      600

int bmp_write_xf_8uc3(uint32_t xf_8uc4_addr, uint32_t xf_8uc3_addr);
void Xil_DCacheFlush(void);

int main(){
    XMedian_blur_accel XMedian_blur_accel_ap;
    XXf_8uc3_2axis XXf_8uc3_2axis_ap;
    int inbyte_in;

    XMedian_blur_accel_Initialize(&XMedian_blur_accel_ap, 0);
    XXf_8uc3_2axis_Initialize(&XXf_8uc3_2axis_ap, 0);

    XMedian_blur_accel_Set_rows(&XMedian_blur_accel_ap, (u32)VERTICAL_LINES);
    XMedian_blur_accel_Set_cols(&XMedian_blur_accel_ap, (u32)HORIZONTAL_PIXELS);
    XXf_8uc3_2axis_Set_rows(&XXf_8uc3_2axis_ap, (u32)VERTICAL_LINES);
    XXf_8uc3_2axis_Set_cols(&XXf_8uc3_2axis_ap, (u32)HORIZONTAL_PIXELS);

    XMedian_blur_accel_Set_img_in(&XMedian_blur_accel_ap, (u32)ORG_PICT_XF_8UC3_ADDR);
    XMedian_blur_accel_Set_img_out(&XMedian_blur_accel_ap, (u32)FILTER_XF_8UC3_ADDR);
    XXf_8uc3_2axis_Set_p_src(&XXf_8uc3_2axis_ap, (u32)ORG_PICT_XF_8UC3_ADDR);

    bmp_write_xf_8uc3(ORG_PICT_XF_8UC4_ADDR, ORG_PICT_XF_8UC3_ADDR);
    Xil_DCacheFlush();

    XMedian_blur_accel_Start(&XMedian_blur_accel_ap);
    while(!XMedian_blur_accel_IsDone(&XMedian_blur_accel_ap));

    Xil_Out32(XPAR_BITMAP_DISP_CONT_AXIS_0_BASEADDR, ORG_PICT_XF_8UC4_ADDR); // dummy address, start

    XXf_8uc3_2axis_Start(&XXf_8uc3_2axis_ap);
    XXf_8uc3_2axis_EnableAutoRestart(&XXf_8uc3_2axis_ap);

    while(1){
        printf("\nPlease input <0> or <1> (<q> : exit) = ");
        fflush(stdout);
        inbyte_in = inbyte();
        printf("%c", inbyte_in);
        fflush(stdout);
        switch(inbyte_in) {
            case '0': //bmp image
                XXf_8uc3_2axis_Set_p_src(&XXf_8uc3_2axis_ap, (u32)ORG_PICT_XF_8UC3_ADDR);
                break;
            case '1': // median filter
                XXf_8uc3_2axis_Set_p_src(&XXf_8uc3_2axis_ap, (u32)FILTER_XF_8UC3_ADDR);
                break;
            case 'q': // exit
                return(0);
        }
    }
}

int bmp_write_xf_8uc3(uint32_t xf_8uc4_addr, uint32_t xf_8uc3_addr){
    uint32_t pix[4];

    for(int y=0; y<VERTICAL_LINES; y++){
        for(int x=0; x<HORIZONTAL_PIXELS; x++){
            int xf_8uc4 = 0xff000000 + ((uint32_t)bmp_file_array[y][x][2]<<16)
                    +((uint32_t)bmp_file_array[y][x][1]<<8)+(uint32_t)bmp_file_array[y][x][0];
            Xil_Out32(xf_8uc4_addr+(y*HORIZONTAL_PIXELS+x)*sizeof(uint32_t), xf_8uc4);

            switch((x+y*HORIZONTAL_PIXELS)%4){
            case 0 :
                pix[0] = xf_8uc4;
                break;
            case 1 :
                pix[1] = xf_8uc4;
                Xil_Out32(xf_8uc3_addr, ((pix[1]&0xff)<<24)+(pix[0]&0xffffff));
                xf_8uc3_addr += sizeof(uint32_t);
                break;
            case 2 :
                pix[2] = xf_8uc4;
                Xil_Out32(xf_8uc3_addr, ((pix[2]&0xffff)<<16)+((pix[1]&0xffff00)>>8));
                xf_8uc3_addr += sizeof(uint32_t);
                break;
            default : // 3
                pix[3] = xf_8uc4;
                Xil_Out32(xf_8uc3_addr, ((pix[3]&0xffffff)<<8)+((pix[2]&0xff0000)>>16));
                xf_8uc3_addr += sizeof(uint32_t);
                break;
            }
        }
    }
    return(0);
}


最初に画像が表示されなかったのだが、それは、

Xil_Out32(XPAR_BITMAP_DISP_CONT_AXIS_0_BASEADDR, ORG_PICT_XF_8UC4_ADDR); // dummy address, start


XXf_8uc3_2axis_Start(&XXf_8uc3_2axis_ap);
XXf_8uc3_2axis_EnableAutoRestart(&XXf_8uc3_2axis_ap);

の後になっていたからだった。後ろのモジュールからスタートさせる必要がある。
  1. 2021年04月20日 04:27 |
  2. Vitis_Vision
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream の IP を使用して Vitis Vision Library で加工されたデータをディスプレイに表示する2(Vivado プロジェクト)

AXI4-Stream の IP を使用して Vitis Vision Library で加工されたデータをディスプレイに表示する1(構想編)”の続き。

Vitis Vision Library の medianblur を ZYBO Z7-20 で使ってみる4”の続きでもある。

Vivado 2020.2 を使用して AXI4-Stream 版ビットマップ・ディスプレイ・コントローラをテストする1
Vivado 2020.2 を使用して AXI4-Stream 版ビットマップ・ディスプレイ・コントローラをテストする2
で AXI4-Stream 版ビットマップ・ディスプレイ・コントローラを作成した。

AXI4-Stream 出力 xf_8uc3_2axis IP を Vitis HLS 2020.2 で作成する1
AXI4-Stream 出力 xf_8uc3_2axis IP を Vitis HLS 2020.2 で作成する2
AXI4-Stream 出力 xf_8uc3_2axis IP を Vitis HLS 2020.2 で作成する3
で、AXI4-Stream 出力 xf_8uc3_2axis IP が生成できた。

それらの IP と”Vitis Vision Library の medianblur を ZYBO Z7-20 で使ってみる1”の medianblur IP を使用して下図の様に画像にメディアン・フィルタを掛ける回路を作成する。
Vitis_Vision_disp_2_210413.png

Vivado 2020.2 で median_vision_axis プロジェクトを作成した。
Vitis_Vision_disp_16_210419.png

bitmap_disp_cont_axis IP と rgb2dvi IP を”Vivado 2020.2 を使用して AXI4-Stream 版ビットマップ・ディスプレイ・コントローラをテストする1”から持ってきた。
Vitis Vision Library の medianblur を ZYBO Z7-20 で使ってみる1”の medianblur IP と”AXI4-Stream 出力 xf_8uc3_2axis IP を Vitis HLS 2020.2 で作成する3”からAXI4-Stream 出力 xf_8uc3_2axis IP をプロジェクト・ディレクトリにコピーした。
Vitis_Vision_disp_17_210419.png

IP を IP Catalog に登録した。
Vitis_Vision_disp_18_210419.png

median_v_axis_bd ブロックデザインを作成した。
Vitis_Vision_disp_19_210419.png

ZYNQ7 Processing System は AXI_HP0 ポートを追加して、FCLK_CLK0 は 100 MHz に 、FCLK_CLK1 は SVGA の 40 MHz に設定した。
Vitis_Vision_disp_23_210419.png

rgb2dvi IP の TMDS clock range は 80 MHz 以下に設定した。
Vitis_Vision_disp_24_210419.png

Address Map を示す。
Vitis_Vision_disp_20_210419.png

median_vision_axis.xdc を作成した。

set_property PACKAGE_PIN H16 [get_ports TMDS_Clk_p_0]
set_property PACKAGE_PIN D19 [get_ports {TMDS_Data_p_0[0]}]
set_property PACKAGE_PIN C20 [get_ports {TMDS_Data_p_0[1]}]
set_property PACKAGE_PIN B19 [get_ports {TMDS_Data_p_0[2]}]

set_property IOSTANDARD TMDS_33 [get_ports TMDS_Clk_p_0]
set_property IOSTANDARD TMDS_33 [get_ports {TMDS_Data_p_0[2]}]
set_property IOSTANDARD TMDS_33 [get_ports {TMDS_Data_p_0[1]}]
set_property IOSTANDARD TMDS_33 [get_ports {TMDS_Data_p_0[0]}]


Vitis_Vision_disp_21_210419.png

HDL Wrapper ファイルを作成した。
Vitis_Vision_disp_22_210419.png

論理合成、インプリメンテーション、ビットストリームの生成を行った。
成功した。Project Summary を示す。
Vitis_Vision_disp_25_210419.png

ハードウェアをエクスポートして、median_v_axis_bd_wrapper.xsa ファイルを生成した。
Vitis_Vision_disp_26_210419.png
  1. 2021年04月19日 04:19 |
  2. Vitis_Vision
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream 出力 xf_8uc3_2axis IP を Vitis HLS 2020.2 で作成する3

AXI4-Stream 出力 xf_8uc3_2axis IP を Vitis HLS 2020.2 で作成する2”の続き。

AXI4-Stream 出力の xf_8uc3_2axis IP を作成するということで、前回は、Vitis HLS 2020.2 の xf_8uc3_2axis プロジェクトで、C シミュレーション、 C コードの合成、C/RTL 協調シミュレーション、Export RTL を行った。今回は、どうしても DSP を 20 個も使っているのが気に入らないので、浮動小数点数演算を任意精度固定小数点演算に変更する。

浮動小数点数演算の部分は、Mat 型の画像の個数が 3/4 になっているので、その値を計算する部分だ。つまり、

const int rows_cols_limit = (int)((float)rows * (float)cols * 3.0/4.0 + 0.76);


の部分なので、これを任意精度固定小数点データ型を使用して書き直した。

    const int rows_cols_limit = (int)((ap_ufixed<32,30,AP_TRN_ZERO,AP_SAT>)rows *
        (ap_ufixed<32,30,AP_TRN_ZERO,AP_SAT>)cols *
        (ap_ufixed<32,30,AP_TRN_ZERO,AP_SAT>)3.0/(ap_ufixed<32,30,AP_TRN_ZERO,AP_SAT>)4.0 +
        (ap_ufixed<32,30,AP_TRN_ZERO,AP_SAT>)0.75);



任意精度固定小数点データ型の演算を使用した xf_8uc3_2axis.cpp を貼っておく。

// xf_8uc3_2axis.cpp
// 2021/04/13 by marsee
//

#include "ap_int.h"
#include "hls_stream.h"
#include "ap_axi_sdata.h"
#include "ap_fixed.h"

int dmar2axis(volatile ap_uint<32>* _src, int rows, int cols, hls::stream<ap_uint<32> >& axis_out);
int xf_8uc3s_2axis(hls::stream<ap_uint<32> >& axis_in, int rows, int cols, hls::stream<ap_axis<32,1,1,1> >& axis_out);

//#define DEBUG

int xf_8uc3_2axis(volatile ap_uint<32>* _src, int rows, int cols, hls::stream<ap_axis<32,1,1,1> >& axis_out){
#pragma HLS INTERFACE axis register_mode=both register port=axis_out
#pragma HLS DATAFLOW
#pragma HLS INTERFACE s_axilite port=cols
#pragma HLS INTERFACE s_axilite port=rows
#pragma HLS INTERFACE m_axi depth=360000 bundle=gmem port=_src offset=slave
#pragma HLS INTERFACE s_axilite port=return

    hls::stream<ap_uint<32> > axis0;

    dmar2axis(_src, rows, cols, axis0);
    xf_8uc3s_2axis(axis0, rows, cols, axis_out);

    return(0);
}

int dmar2axis(volatile ap_uint<32>* _src, int rows, int cols, hls::stream<ap_uint<32> >& axis_out){
    const int rows_cols_limit = (int)((ap_ufixed<32,30,AP_TRN_ZERO,AP_SAT>)rows *
        (ap_ufixed<32,30,AP_TRN_ZERO,AP_SAT>)cols *
        (ap_ufixed<32,30,AP_TRN_ZERO,AP_SAT>)3.0/(ap_ufixed<32,30,AP_TRN_ZERO,AP_SAT>)4.0 +
        (ap_ufixed<32,30,AP_TRN_ZERO,AP_SAT>)0.75);

    ap_uint<32>  pix;
    //printf("rows_cols_limit = %d\n",rows_cols_limit);

    LOOP_dr2a: for(int xy=0; xy<rows_cols_limit; xy++){
#pragma HLS PIPELINE II=1
#pragma HLS LOOP_TRIPCOUNT avg=360000 max=360000 min=360000
        pix = _src[xy];

#ifdef DEBUG
        if(xy < 10)
            printf("%x\n", (unsigned int)pix);
#endif

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

int xf_8uc3s_2axis(hls::stream<ap_uint<32> >& axis_in, int rows, int cols, hls::stream<ap_axis<32,1,1,1> >& axis_out){
    ap_uint<32> rgb[3];
    ap_axis<32,1,1,1> pix;

    LOOP_y:for(int y=0; y<rows; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=600 min=600
        LOOP_x:for(int x=0; x<cols; x++){
#pragma HLS PIPELINE II=1
#pragma HLS LOOP_TRIPCOUNT avg=800 max=800 min=800
            int xy = x + y * cols;
            switch(xy%4){
            case 0 :
                axis_in >> rgb[0];
                pix.data = (rgb[0] & 0xffffff) + 0xff000000;
                break;
            case 1 :
                axis_in >> rgb[1];
                pix.data = ((rgb[1] & 0xffff)<<8) + ((rgb[0] & 0xff000000)>>24) + 0xff000000;
                break;
            case 2 :
                axis_in >> rgb[2];
                pix.data = ((rgb[2] & 0xff)<<16) + ((rgb[1] & 0xffff0000)>>16) + 0xff000000;
                break;
            default : // 3
                pix.data = ((rgb[2] & 0xffffff00)>>8) + 0xff000000;
                break;
            }
            if(x==0 && y==0)
                pix.user = 1;
            else
                pix.user = 0;
            if(x == cols-1)
                pix.last = 1;
            else
                pix.last = 0;
            axis_out << pix;
        }
    }
    return(0);
}


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

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

DSP が 20 個から 11 個になった。
LUT が 2430 個から、1274 個になった。
FF が 3582 個から 2109 個になった。
全体的にリソース使用量が 5割強程度になった。
  1. 2021年04月18日 03:54 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream 出力 xf_8uc3_2axis IP を Vitis HLS 2020.2 で作成する2

AXI4-Stream 出力 xf_8uc3_2axis IP を Vitis HLS 2020.2 で作成する1”の続き。

AXI4-Stream 出力の xf_8uc3_2axis IP を作成するということで、前回は、ソースコードとテストベンチ・コードを貼って、Vitis HLS 2020.2 の xf_8uc3_2axis プロジェクトを作成した。今回は、C シミュレーション、 C コードの合成、C/RTL 協調シミュレーション、Export RTL を行う。

最初に C シミュレーションを行った。
成功した。
Vitis_Vision_disp_6_210416.png

solution1/csim/build ディレクトリを見ると、output.png が出力されている。大丈夫そうだ。
Vitis_Vision_disp_7_210416.png

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

BRAM を 2 個、DSP を 17 個、FF を 4112 個、LUT を 5753 個使用している。やはり浮動小数点数演算をしているのが増えている原因かな?

C/RTL 協調シミュレーションを行った。
なお、実行時に、Co-simulation Dialog の Input Arguments に test2.jpg を入力してから、OK ボタンをクリックした。
Vitis_Vision_disp_10_210416.png

Latency は 480053 クロックだった。総ピクセル数は 480000 ピクセルなので、優秀だと思う。

C/RTL 協調シミュレーションの波形を確認した。
axis_out_TVALID がほとんど 1 になっているので、スループットは出ている。
Vitis_Vision_disp_11_210416.png

Export RTL を行った。
DSP は 20 個に増えている。
Vitis_Vision_disp_12_210416.png

リソース使用量が多いので、浮動小数点数演算を固定小数点演算に変更しよう。
  1. 2021年04月17日 03:50 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream 出力 xf_8uc3_2axis IP を Vitis HLS 2020.2 で作成する1

AXI4-Stream の IP を使用して Vitis Vision Library で加工されたデータをディスプレイに表示する1(構想編)”で構想したように、DMA Write 出力 xf_8uc3_2rgb IP を AXI4-Stream 出力 xf_8uc3_2axis IP に変更したい。
そこで、今回は、AXI4-Stream 出力 xf_8uc3_2axis IP を作成しよう。

XF_8UC3 を XF_8UC4 に変換する xf_8uc3_2rgb プロジェクトを作成する1”のソースコードやテストベンチ・コードを元に、出力を AXI4-Stream インターフェースに変更するのだ。しかし、よく見ると途中は AXI4-Stream になっているので、axis2dmaw() を外せば良いようだ。

(2021/04/16:修正)出力の axis_out をサイドチャネル付き AXI4-Stream に変更した。

ソースコードの xf_8uc3_2axis.cpp を示す。

// xf_8uc3_2axis.cpp
// 2021/04/13 by marsee
//

#include "ap_int.h"
#include "hls_stream.h"
#include "ap_axi_sdata.h"

int dmar2axis(volatile ap_uint<32>* _src, int rows, int cols, hls::stream<ap_uint<32> >& axis_out);
int xf_8uc3s_2axis(hls::stream<ap_uint<32> >& axis_in, int rows, int cols, hls::stream<ap_axis<32,1,1,1> >& axis_out);

//#define DEBUG

int xf_8uc3_2axis(volatile ap_uint<32>* _src, int rows, int cols, hls::stream<ap_axis<32,1,1,1> >& axis_out){
#pragma HLS INTERFACE axis register_mode=both register port=axis_out
#pragma HLS DATAFLOW
#pragma HLS INTERFACE s_axilite port=cols
#pragma HLS INTERFACE s_axilite port=rows
#pragma HLS INTERFACE m_axi depth=360000 bundle=gmem port=_src offset=slave
#pragma HLS INTERFACE s_axilite port=return

    hls::stream<ap_uint<32> > axis0;

    dmar2axis(_src, rows, cols, axis0);
    xf_8uc3s_2axis(axis0, rows, cols, axis_out);

    return(0);
}

int dmar2axis(volatile ap_uint<32>* _src, int rows, int cols, hls::stream<ap_uint<32> >& axis_out){
    const int rows_cols_limit = (int)((float)rows * (float)cols * 3.0/4.0 + 0.76);
    ap_uint<32>  pix;
    //printf("rows_cols_limit = %d\n",rows_cols_limit);

    LOOP_dr2a: for(int xy=0; xy<rows_cols_limit; xy++){
#pragma HLS PIPELINE II=1
#pragma HLS LOOP_TRIPCOUNT avg=360000 max=360000 min=360000
        pix = _src[xy];

#ifdef DEBUG
        if(xy < 10)
            printf("%x\n", (unsigned int)pix);
#endif

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

int xf_8uc3s_2axis(hls::stream<ap_uint<32> >& axis_in, int rows, int cols, hls::stream<ap_axis<32,1,1,1> >& axis_out){
    ap_uint<32> rgb[3];
    ap_axis<32,1,1,1> pix;

    LOOP_y:for(int y=0; y<rows; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=600 min=600
        LOOP_x:for(int x=0; x<cols; x++){
#pragma HLS PIPELINE II=1
#pragma HLS LOOP_TRIPCOUNT avg=800 max=800 min=800
            int xy = x + y * cols;
            switch(xy%4){
            case 0 :
                axis_in >> rgb[0];
                pix.data = (rgb[0] & 0xffffff) + 0xff000000;
                break;
            case 1 :
                axis_in >> rgb[1];
                pix.data = ((rgb[1] & 0xffff)<<8) + ((rgb[0] & 0xff000000)>>24) + 0xff000000;
                break;
            case 2 :
                axis_in >> rgb[2];
                pix.data = ((rgb[2] & 0xff)<<16) + ((rgb[1] & 0xffff0000)>>16) + 0xff000000;
                break;
            default : // 3
                pix.data = ((rgb[2] & 0xffffff00)>>8) + 0xff000000;
                break;
            }
            if(x==0 && y==0)
                pix.user = 1;
            else
                pix.user = 0;
            if(x == cols-1)
                pix.last = 1;
            else
                pix.last = 0;
            axis_out << pix;
        }
    }
    return(0);
}


テストベンチ・コードの xf_8uc3_2axis_tb.cpp を示す。

// xf_8uc3_2axis_tb.cpp
// 2021/04/13 by marsee
//

#include "ap_int.h"
#include "hls_stream.h"
#include "ap_axi_sdata.h"
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"

int xf_8uc3_2axis(volatile ap_uint<32>* _src, int rows, int cols, hls::stream<ap_axis<32,1,1,1> >& axis_out);

int main(int argc, char **argv){
    hls::stream<ap_axis<32,1,1,1> > axis_out;
    ap_uint<32> *pp;
    ap_axis<32,1,1,1> pix;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <INPUT IMAGE>", argv[0]);
        exit(1);
    }

    cv::Mat in_img, out_img, conv_img;

    in_img = cv::imread(argv[1], 1); // reading in the color image

    if (in_img.data == NULL) {
        fprintf(stderr, "ERROR: Cannot open image %s\n ", argv[1]);
        exit(1);
    }

    out_img.create(in_img.rows, in_img.cols, CV_8UC4);
    conv_img.create(in_img.rows, in_img.cols, CV_8UC3);

    xf_8uc3_2axis((volatile ap_uint<32> *)in_img.data, in_img.rows, in_img.cols, axis_out);

    pp = (ap_uint<32> *)out_img.data;
    for(int y=0; y<in_img.rows; y++){
        for(int x=0; x<in_img.cols; x++){
            axis_out >> pix;
            *pp++ = pix.data;
        }
    }
    cv::cvtColor(out_img, conv_img, cv::COLOR_BGRA2BGR);

    cv::imwrite("output.png", conv_img);

    return(0);
}


なお、このテストベンチ・コードを実行するためには、OpenCV を自分でインストールする必要がある。(”Ubuntu 18.04 LTS のパソコンに OpenCV 3.4.9 をインストールする”参照)

Vitis HLS 2020.2 で ZYBO Z7-20 用の xf_8uc3_2axis プロジェクトを作成した。
Vitis_Vision_disp_3_210415.png

OpenCV を利用するために設定を行っている。(”XF_8UC3 を XF_8UC4 に変換する xf_8uc3_2rgb プロジェクトを作成する2”参照)
Project メニューから Project Settings... を選択して、ダイアログを表示させる。
Simulation をクリックして、 CFLAGS にインストール済みの OpenCV のインクルード・パスを入力する。

-I/usr/local/include


Linker Flagに

-L/usr/local/lib -lopencv_core -lopencv_imgcodecs -lopencv_imgproc


を入力する。
Input Argument に画像のファイル名

test2.jpg


を入力する。
Vitis_Vision_disp_4_210415.png

もう 1 つ設定を行った。
ZYBO Z7-20 なので、アドレスは 32 ビットとなる。Vitis HLS の AXI4-Master アクセスのデフォルトは 64 ビット・アドレスなので、これを変更する。
Vitis HLS 2020.2 の Solution メニューから Solution Settings... を選択する。
Solution Settings (solution1) ダイアログが開く。
左のペインで General をクリックして、 config_interface を開く。
m_axi_addr64 のチェックを外す。
Vitis_Vision_disp_5_210415.png
  1. 2021年04月15日 04:19 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream の IP を使用して Vitis Vision Library で加工されたデータをディスプレイに表示する1(構想編)

Vitis Vision Library の medianblur を ZYBO Z7-20 で使ってみる4”までで画像にメディアン・フィルタをかけてディスプレイに表示することができた。しかし、下図に示すスキームだと DMA のパスが多くてメモリ帯域が逼迫することが考えられる。
median_vision_21_210120.png
図1 DMA Write 出力 xf_8uc3_2rgb IP を使用した median filter ディスプレイ・システム

そこで、作ってきた AXI4-Stream 版ビットマップ・ディスプレイ・コントローラを使用して、下図の様に構成して DMA のパスを削減しよう。
Vitis_Vision_disp_2_210413.png
図2 AXI4-Stream 出力 xf_8uc3_2axis IP を使用した median filter ディスプレイ・システム

図1では同時に動作するメモリ・アクセスは 5 個だが、図2 では 3 個に減少する。

図2 の構成を実現するために、”XF_8UC3 を XF_8UC4 に変換する xf_8uc3_2rgb プロジェクトを作成する1”の xf_8uc3_2rgb を AXI4-Stream 出力にする xf_8uc3_2axis を作成しよう。

今回は、カメラのパスが書いていないが、カメラのデータを AXI4-Stream で出力する IP はあるので、AXI4-Stream 入力を XF_8UC3に変換して、 DMA Write する IP を作成する。そうすれば、カメラで取り込んだ画像データを Vitis Vision Library で加工してディスプレイに出力するシステムをメモリ帯域をあまり圧迫すること無く構築できそうだ。
  1. 2021年04月14日 04:04 |
  2. Vitis_Vision
  3. | トラックバック:0
  4. | コメント:0

OpenCores.org の I2C controller core をシミュレーションする

clock stretching でクロックが伸びることがある I2C スレーブを使う予定があって、Xilinx 社の AXI IIC IP がクロック・ストレッチングをサポートしているか?を調べてみたところ、どうやらサポートしていないようなのだ。(”AXI IIC core clock stretching”参照)
clock stretching をサポートするフリーの I2C Master Core を探していたところ、OpenCores.org の I2C controller core を探し当てた。Wishbone インターフェースなのだが、使う際は AXI4 Lite インターフェースに変換すれば良いか?ということで Vivado 2020.2 でプロジェクトを作製して、シミュレーションを行った。素晴らしいことにシミュレーション用のテストベンチやモデルも用意されている。

Vivado 2020.2 の i2c_master_wb プロジェクトを示す。
i2c_master_wb_1_210413.png

論理シミュレーションを行って、$finish までシミュレーションを行った。
i2c_master_wb_2_210413.png

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

シミュレーション波形を拡大すると、Wishbone バスのトランザクションが見えてくる。
最初のトランザクションは 0 番地に 0xfa を Write している。
i2c_master_wb_4_210413.png

Wishbone バスのトランザクションについては、この辺りを参照する。
ぱたへね! ”Wishbone のバスサイクル”
”WishBoneインターフェース・メモ”

I2C Master Core の使用方法を確認するために Wishbone バスのトランザクションと I2C の送受信を表にまとめた。
i2c_master_wb_6_210413.png

I2C Master Core のアドレスマップを”I2C-Master Core Specification”から引用する。
レジスタのマップは”I2C-Master Core Specification”を参照のこと。
i2c_master_wb_5_210413.png

表を CSV で貼っておく。

番号,時刻,アドレス,レジスタ名,R/W,データ,説明
1,46ns,0,PRERlo,W,0xfa,
2,76ns,0,PRERlo,W,0xc8,
3,106ns,1,PRERhi,W,0x00,
4,126ns,0,PRERlo,R,0xc8,
5,146ns,1,PRERhi,R,0x00,
6,176ns,2,CTR,W,0x80,"EN, I2C core enable bit. "
7,206ns,3,TXR,W,0x20,
8,226ns,4,CR,W,0x90,"STA, WR"
9,256ns,4,SR,R,0x03,"TIP, IF"
10,,,,,,0x20出力、ACK
11,"113,386ns",4,SR,R,0x41,"Busy, IF"
12,"113,446ns",3,TXR,W,0x01,
13,"113,466ns",4,CR,W,0x10,WR
14,"113,496ns",4,SR,R,0x43,"Busy, TIP, IF"
15,,,,,,0x01出力、ACK
16,"214,396ns",4,SR,R,0x41,"Busy, IF"
17,"214,426ns",3,TXR,W,0xa5,
18,"214,446ns",4,CR,W,0x10,WR
19,"314,966ns",4,SR,R,0x43,"Busy, TIP, IF"
20,,,,,,"0xa5, ACK"
21,"409,736ns",4,SR,R,0x41,"Busy, IF"
22,"409,766ns",3,TXR,W,0x5a,
23,"409,786ns",4,CR,W,0x50,"STO, WR"
24,"409,816ns",4,SR,R,0x43,"Busy, TIP, IF"
25,,,,,,0x5a出力、ACK、STOP
26,"521,926ns",4,SR,R,0x41,"Busy, IF"
27,"521,956ns",3,TXR,W,0x20,
28,"521,976ns",4,CR,W,0x90,"STA, WR"
29,"522,006ns",4,SR,R,0x43,"Busy, TIP, IF"
30,,,,,,STOP出力
31,"523,506ns",4,SR,R,0x03,"TIP, IF"
32,,,,,,START出力
33,"531,156ns",4,SR,R,0x43,"Busy, TIP, IF"
34,,,,,,0x20出力、ACK
35,"635,136ns",4,SR,R,0x41,"Busy, IF"
36,"635,166ns",3,TXR,W,0x01,
37,"635,186ns",4,CR,W,0x10,WR
38,"635,216ns",4,SR,R,0x43,"Busy, TIP, IF"
39,,,,,,0x01出力、ACK
40,"736,136ns",4,SR,R,0x41,"Busy, IF"
41,"736,166ns",3,TXR,W,0x21,
42,"736,186ns",4,CR,W,0x90,"STA, WR"
43,"736,216ns",4,SR,R,0x43,"Busy, TIP, IF"
44,,,,,,"Repeat Start, 0x21"
45,"850,366ns",4,SR,R,0x41,"Busy, IF"
46,"850,396ns",4,CR,W,0x20,"RD, ACK"
47,"850,426ns",4,SR,R,0x43,"Busy, TIP, IF"
48,,,,,,0xa5入力、ACK
49,"951,346ns",4,SR,R,0x41,"Busy, IF"
50,"951,376ns",3,RXR,R,0xa5,
51,"951,406ns",4,CR,W,0x20,"RD, ACK"
52,"951,436ns",4,SR,R,0x43,"Busy, TIP, IF"
53,,,,,,0x5a入力、ACK
54,"1,052,326ns",4,SR,R,0x41,"Busy, IF"
55,"1,052,356ns",3,RXR,R,0x5a,
56,"1,052,386ns",4,CR,W,0x20,"RD, ACK"
57,"1,052,416ns",4,SR,R,0x43,"Busy, TIP, IF"
58,,,,,,xx入力
59,"1,153,306ns",4,SR,R,0xX1,
60,"1,153,336ns",3,RXR,R,0xXX,
61,"1,153,366ns",4,CR,W,0x28,"RD, NACK"
62,"1,153,396ns",4,SR,R,0xX3,"TIP, IF"
63,,,,,,"xx入力, NACK"
64,"1,254,286ns",4,SR,R,0xX1,
65,"1,254,316ns",3,RXR,R,0xXX,
66,"1,254,346ns",3,TXR,W,0x20,
67,"1,254,366ns",4,CR,W,0x90,"STA, WR"
68,"1,254,396ns",4,SR,R,0xX3,"TIP, IF"
69,"1,265,046ns",4,SR,R,0xc3,"RxACK, Busy, TIP, IF"
70,,,,,,0x20出力、ACK
71,"1,368,516ns",4,SR,R,0x41,"Busy, IF"
72,"1,368,546ns",3,TXR,W,0x10,
73,"1,368,566ns",4,CR,W,0x10,WR
74,"1,368,596ns",4,SR,R,0x43,"Busy, TIP, IF"
75,,,,,,0x10出力、ACK
76,"1,469,516ns",4,SR,R,0xc1,"RxACK, Busy, IF"
77,"1,469,546ns",4,CR,W,0x40,STO

  1. 2021年04月13日 04:15 |
  2. FPGAを使用したシステム
  3. | トラックバック:0
  4. | コメント:0

Vivado 2020.2 を使用して AXI4-Stream 版ビットマップ・ディスプレイ・コントローラをテストする2

Vivado 2020.2 を使用して AXI4-Stream 版ビットマップ・ディスプレイ・コントローラをテストする1”の続き。

今まで作ってきた IP と Digilent の GitHub の Digilent Vivado library の rgb2dvi IP を使用して AXI4-Stream 版ビットマップ・ディスプレイ・コントローラをテストしてみようということで、前回は、Vivado 2020.2 で bm_disp_axis_test プロジェクトを作成し、IP を接続したブロックデザインを作成して、論理合成、インプリメンテーション、ビットストリームの生成を行って、ハードウェアをエクスポートした。今回は、Vitis 2020.2 を立ち上げて、プラットフォームとアプリケーション・プロジェクトを作成して、実機検証してみよう。

Vitis 2020.2 を Vivado 2020.2 から立ち上げて、 bm_disp_axis_test_wrapper プラットフォームと bm_disp_axis_test アプリケーション・プロジェクトを作成した。

bm_disp_axsi_test_system -> bm_disp_axsi_test -> src に bm_disp_axis_test.c ソースコード・ファイルを作成した。画像を C 言語のヘッダ・ファイルに変換した bmp_data.h をインポートした。(”Vivado HLS 2019.1 を使用してBMPファイルをC のヘッダファイルに変換する”参照)
bm_dispc_axis_60_210412.png

これで、ビットファイルを ZYBO Z7-20 にコンフィギュレーションして、アプリケーション・ソフトウェアを起動したが、HDMI ポートに接続されたディスプレイに画像が表示された。成功だ。
bm_dispc_axis_61_210412.jpg

bm_disp_axis_test.c ソースコードを貼っておく。

// bm_disp_axis_test.c
// 2021/04/09 by marsee
//

#include <stdio.h>
#include "xil_io.h"
#include "xparameters.h"

#include "xdma2axis.h"
#include "bmp_data.h"

#define FRAME_BUFFER_ADDRESS 0x10000000
#define DMA_DEST_ADDRESS 0x10200000

#define HORIZONTAL_PIXELS   800
#define VERTICAL_LINES      600

int bmp_write(unsigned int addr);
void Xil_DCacheFlush(void);

int main(){
    XDma2axis xdma2s_ap;

    XDma2axis_Initialize(&xdma2s_ap, XPAR_DMA2AXIS_0_DEVICE_ID);

    XDma2axis_Set_x_size(&xdma2s_ap, (u32)HORIZONTAL_PIXELS);
    XDma2axis_Set_y_size(&xdma2s_ap, (u32)VERTICAL_LINES);
    XDma2axis_Set_in_V(&xdma2s_ap, (u32)FRAME_BUFFER_ADDRESS);

    bmp_write(FRAME_BUFFER_ADDRESS);
    Xil_DCacheFlush();

    Xil_Out32(XPAR_BITMAP_DISP_CONT_AXIS_0_BASEADDR, (u32)DMA_DEST_ADDRESS); // bm_disp_axis start
    XDma2axis_Start(&xdma2s_ap);
    XDma2axis_EnableAutoRestart(&xdma2s_ap);

    return(0);
}

int bmp_write(unsigned int addr){
    for(int y=0; y<VERTICAL_LINES; y++){
        for(int x=0; x<HORIZONTAL_PIXELS; x++){
            Xil_Out32(addr+(y*HORIZONTAL_PIXELS+x)*sizeof(int),
                ((int)bmp_file_array[y][x][2]<<16)+((int)bmp_file_array[y][x][1]<<8)+(int)bmp_file_array[y][x][0]);
        }
    }
    return(0);
}

  1. 2021年04月12日 05:00 |
  2. IP
  3. | トラックバック:0
  4. | コメント:0

Vivado 2020.2 を使用して AXI4-Stream 版ビットマップ・ディスプレイ・コントローラをテストする1

今まで、Vivado 2020.2 を使用して AXI4-Stream 版ビットマップ・ディスプレイ・コントローラを作ってきた。
Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする1
”Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする2
Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする3
Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする4

AXI4-Stream 版ビットマップ・ディスプレイ・コントローラに画像のピクセル・データを供給する DMA2axis IP も作った。
Vitis HLS 2020.2 で DMA2axis IP を作る1
Vitis HLS 2020.2 で DMA2axis IP を作る2

今まで作ってきた IP と Digilent の GitHub の Digilent Vivado library の rgb2dvi IP を使用して AXI4-Stream 版ビットマップ・ディスプレイ・コントローラをテストしてみよう。

Vivado 2020.2 で bm_disp_axis_test プロジェクトを作成した。
bm_dispc_axis_52_210411.png

bm_disp_axis_test プロジェクトのディレクトリに DMA2axis 、 bitmap_disp_cont_axis 、 rgb2dvi の各IP が入ったディレクトリを作成した。
bm_dispc_axis_59_210411.png

DMA2axis 、 bitmap_disp_cont_axis 、 rgb2dvi の各IP を IP Catalog に登録した。

bm_disp_axis_test ブロックデザインを作成した。
bm_dispc_axis_53_210411.png

processing_system7_0 のクロックの設定を示す。
bm_dispc_axis_54_210411.png

rgb2dvi_0 の設定を示す。
bm_dispc_axis_55_210411.png

Address Map を示す。
bm_dispc_axis_56_210411.png

論理合成、インプリメンテーション、ビットストリームの生成を行った。
Project Summary を示す。
bm_dispc_axis_57_210411.png

ハードウェアをエクスポートして、 bm_disp_axis_test_wrapper.xsa を作成した。
bm_dispc_axis_58_210411.png
  1. 2021年04月11日 03:45 |
  2. IP
  3. | トラックバック:0
  4. | コメント:0

Vitis HLS 2020.2 で DMA2axis IP を作る2

Vitis HLS 2020.2 で DMA2axis IP を作る1”の続き。

”Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする4”で作った bitmap_disp_cont_axis IP を実機でテストするために DMA to axis 変換 IP を作る必要があるので、作成するということで、前回は、ソースコードとテストベンチ、そして Vitis HLS 2020.2 の DMA2axis プロジェクトを紹介した。今回は、 C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行う。

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

C コードの合成結果を示す。
DMA2axis_3_210409.png
DMA2axis_4_210409.png
DMA2axis_5_210409.png

Latency は 2085481 クロックだった。ピクセル数は 1920 X 1080 ピクセル = 2073600 ピクセルなので、約 1.006 クロック/ピクセルとなるので、問題無さそう。
リソース使用量は BRAM 2 個、FF 1251 個、LUT 1468 個だった。

同じソースコードで Vivado HLS 2020.1 で合成した結果を示す。
DMA2axis_11_210409.png

Latency は 2073681 クロックで Vitis HLS の結果よりも良い結果なのだが、リソース使用量は Vitis HLS よりも極端に多い。Vivado HLS では、DMA の領域などをレジスタにマップして設定した時に極端にリソースを消費するので、今までは、固定値にしていたのだが、Vitis HLS ではその必要が無さそうだ。

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

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

後ろの方のみ拡大する。
最後の値が 479999 になっているので正しいようだ。
DMA2axis_8_210409.png

更に拡大して 1 行分のトランザクションを示す。
DMA2axis_9_210409.png

Export RTL を行った。
FF は 1676 個で合成時の予測値よりも増えている。
DMA2axis_10_210409.png

Vivado HLS 2020.1 での Export RTL の結果を示す。
タイミングが満足していない。
DMA2axis_12_210409.png

やはり、Vivado HLS の方が Vitis HLS よりリソース使用量が極端に多い。

Vitis HLS は Vivado HLS に比べて設定値をレジスタにマップする時のリソース使用量が改善されているようだ。
Vitis HLS では汎用 IP が作りやすいようなので、積極的に作っていこうと思う。
  1. 2021年04月09日 05:15 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vitis HLS 2020.2 で DMA2axis IP を作る1

Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする4”で作った bitmap_disp_cont_axis IP を実機でテストするために DMA to axis 変換 IP を作る必要があるので、作成する。

Vitis HLS 2020.2 で DMA2axis プロジェクトを作成した。
DMA2axis_1_210408.png

ソースコードの DMA2axis.cpp を示す。

// DMA2axis.cpp
// 2021/04/08 by marsee
//

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

int DMA2axis(volatile ap_int<32> *in, hls::stream<ap_axis<32,1,1,1> >& outs,
        int x_size, int y_size){
#pragma HLS INTERFACE s_axilite port=y_size
#pragma HLS INTERFACE s_axilite port=x_size
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE m_axi depth=480000 port=in offset=slave
    ap_axis<32,1,1,1> out_val;

    for(int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT avg=600 max=1080 min=480
        for(int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT avg=800 max=1920 min=640
#pragma HLS PIPELINE II=1
            out_val.data = in[y*x_size+x];
            if(x==0 && y==0)
                out_val.user = 1;
            else
                out_val.user = 0;
            if(x == x_size-1)
                out_val.last = 1;
            else
                out_val.last = 0;
            outs << out_val;
        }
    }
    return(0);
}


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

// DMA2axis_tb.cpp
// 2021/04/08 by marsee
//

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

int DMA2axis(volatile ap_int<32> *in, hls::stream<ap_axis<32,1,1,1> >& outs,
        int x_size, int y_size);

int main(){
    hls::stream<ap_axis<32,1,1,1> > outs;
    ap_axis<32,1,1,1> vals;

    // 800 x 600 の領域を確保
    std::vector<ap_int<32>> buf(sizeof(ap_int<32>)*800*600);

    // 確保した領域にデータを入力する
    for(int i=0; i<480000; i++){
        buf[i] = i;
    }

    DMA2axis((volatile ap_int<32> *)&buf[0], outs, 800, 600);

    for(int i=0; i<480000; i++){
        outs >> vals;
        if(vals.data != i){
            printf("Error : i = %d; vals.data = %d\n", i, vals.data);
            return(1);
        }
    }
    printf("Simulation succeeded without error\n");
    return(0);
}

  1. 2021年04月08日 05:17 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする4

Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする3”の続き。

Vitis Vision Library を本格的にやるために、前回は、今まで作ったファイルを使用してシミュレーションを行って、成功した。今回は、IP としてパッケージをしてみよう。

”ビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする5(IP作成)”を参考にやってみよう。

Vivado 2020.2 のTools メニューから Create and Package IP... を選択する。

Create and Package New IP ダイアログが表示された。
bm_dispc_axis_31_210407.png

Package your current project のラジオボタンが設定されていることを確認して、Next > ボタンをクリックした。
bm_dispc_axis_27_210407.png

ここもデフォルトのまま
bm_dispc_axis_28_210407.png

Summary
bm_dispc_axis_29_210407.png

Package IP タブが表示された。
bm_dispc_axis_30_210407.png

Identification
Vendor を marsee101 に変更した。
bm_dispc_axis_31_210407.png

Compatibility
bm_dispc_axis_32_210407.png

File Groups
video_timing_param.vh が入っている。
bm_dispc_axis_33_210407.png

Customization Parameters
bm_dispc_axis_34_210407.png

Ports and Interfaces 。 vid... 信号をRGB インターフェースにまとめる。
bm_dispc_axis_35_210407.png

ここからは、”ZYBO_0 を変更1(ブロックデザインの修正)”を参照する。

vid_…信号を選択して(選択しなくても良さそうだが。。。)、右クリックメニューから Add Bus Interface... を選択し、RGBインターフェースを生成する。

Add Interface ダイアログが表示された。
Interface Definition を選択するために ... ボタンをクリックした。
bm_dispc_axis_36_210407.png

Interface Definition Chooser でRGB インターフェースのvid_io_rtl を選択した。
bm_dispc_axis_37_210407.png

Interface Definition に vid_io_rtl が表示された。
Name に RGB と入力した。
Display name に RGB Video Output と入力し、OKボタンをクリックした。
bm_dispc_axis_39_210407.png

Port Mapping タブをクリックして、ポートをマッピングした。
bm_dispc_axis_40_210407.png

RGB インターフェースを設定することができた。
bm_dispc_axis_41_210407.png

Addressing and Memory
bm_dispc_axis_42_210407.png

Customization GUI
bm_dispc_axis_43_210407.png

Review and Package で Package IP ボタンをクリックしてIP を生成した。
bm_dispc_axis_44_210407.png

IP 生成が成功したというダイアログが表示された。
bm_dispc_axis_45_210407.png

bm_dispc_axis/bm_dispc_axis.srcs ディレクトリに marsee101_user_bitmap_disp_cont_axis_1.0.zip が生成された。
bm_dispc_axis_46_210407.png
  1. 2021年04月07日 04:41 |
  2. IP
  3. | トラックバック:0
  4. | コメント:0

Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする3

Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする2”の続き。

Vitis Vision Library を本格的にやるために、前回は、Vitis_HLS 2020.2 でシミュレーション用の AXI4-Stream Master Model を作った。今回は、今まで作ったファイルを使用してシミュレーションしてみよう。

参考にするのは、”ビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする3(シミュレーション)”だ。シミュレーション用のテストベンチ・ファイルの bitmap_disp_cntrler_axi_master_tb.v もここに貼ってある。

シミュレーションするための Verilog HDL コードは、”ビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする4(ソースコードの公開)”に貼ってあるのだが、 video_timing_param.vh が抜けているので、ここに貼っておく。

// video_timing_param.vh
// by marsee
// 2014/07/26

parameter integer H_ACTIVE_VIDEO = (RESOLUTION=="VGA") ?    640 :   // VGA    25MHz
                    (RESOLUTION=="SVGA") ?                  800 :   // SVGA   40MHz
                    (RESOLUTION=="XGA") ?                   1024 :  // XGA    65MHz
                    (RESOLUTION=="SXGA") ?                  1280 :  // SXGA   108MHz
                    (RESOLUTION=="HD") ?                    1920 : 1920;    // HD     148.5MHz

parameter integer H_FRONT_PORCH = (RESOLUTION=="VGA") ? 16 :    // VGA
                    (RESOLUTION=="SVGA") ?              40 :    // SVGA
                    (RESOLUTION=="XGA") ?               24 :    // XGA
                    (RESOLUTION=="SXGA") ?              48 :    // SXGA
                    (RESOLUTION=="HD") ?                88 : 88;    // HD

parameter integer H_SYNC_PULSE = (RESOLUTION=="VGA") ?  96 :    // VGA
                    (RESOLUTION=="SVGA") ?              128 :   // SVGA
                    (RESOLUTION=="XGA") ?               136 :   // XGA
                    (RESOLUTION=="SXGA") ?              112 :   // SXGA
                    (RESOLUTION=="HD") ?                44 : 44;    // HD

parameter integer H_BACK_PORCH = (RESOLUTION=="VGA") ?  48 :    // VGA
                    (RESOLUTION=="SVGA") ?              88 :    // SVGA
                    (RESOLUTION=="XGA") ?               160 :   // XGA
                    (RESOLUTION=="SXGA") ?              248 :   // SXGA
                    (RESOLUTION=="HD") ?                148 : 148;  // HD

parameter integer V_ACTIVE_VIDEO = (RESOLUTION=="VGA") ?    480 :   // VGA
                    (RESOLUTION=="SVGA") ?                  600 :   // SVGA
                    (RESOLUTION=="XGA") ?                   768 :   // XGA
                    (RESOLUTION=="SXGA") ?                  1024 :  // SXGA
                    (RESOLUTION=="HD") ?                    1080 : 1080;    // HD

parameter integer V_FRONT_PORCH = (RESOLUTION=="VGA") ? 11 :    // VGA
                    (RESOLUTION=="SVGA") ?              1 : // SVGA
                    (RESOLUTION=="XGA") ?               2 : // XGA
                    (RESOLUTION=="SXGA") ?              1 : // SXGA
                    (RESOLUTION=="HD") ?                4 : 4;  // HD

parameter integer V_SYNC_PULSE = (RESOLUTION=="VGA") ? 2 :  // VGA
                    (RESOLUTION=="SVGA") ?              4 : // SVGA
                    (RESOLUTION=="XGA") ?               6 : // XGA
                    (RESOLUTION=="SXGA") ?              3 : // SXGA
                    (RESOLUTION=="HD") ?                5 : 5;  // HD

parameter integer V_BACK_PORCH = (RESOLUTION=="VGA") ?  31 :    // VGA
                    (RESOLUTION=="SVGA") ?              23 :    // SVGA
                    (RESOLUTION=="XGA") ?               29 :    // XGA
                    (RESOLUTION=="SXGA") ?              38 :    // SXGA
                    (RESOLUTION=="HD") ?                36 : 36;    // HD

    parameter H_SUM = H_ACTIVE_VIDEO + H_FRONT_PORCH + H_SYNC_PULSE + H_BACK_PORCH;
    parameter V_SUM = V_ACTIVE_VIDEO + V_FRONT_PORCH + V_SYNC_PULSE + V_BACK_PORCH;

    parameter H_DISPLAY_SIZE = H_ACTIVE_VIDEO/8; // 横?桁
    parameter V_DISPLAY_SIZE = V_ACTIVE_VIDEO/8; // 縦?行
    parameter ALL_CHAR_SIZE = H_DISPLAY_SIZE*V_DISPLAY_SIZE;

    parameter RED_DOT_POS = 15; // 15~13ビット目がRED
    parameter GREEN_DOT_POS = 12; // 12~10ビット目がGREEN
    parameter BLUE_DOT_POS = 9; // 9~7ビット目がBLUE
    parameter COLOR_ATTRIB_WIDHT = 3;   // 色情報のビット幅


Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする1”の bitmap_afifo と”Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする2”の AXI4-Stream Master Model も使用した。

Vivado 2020.2 で bm_dispc_axis プロジェクトを作成して、各ファイルを Add Source した。
bm_dispc_axis_19_210405.png

Run Simulation から Run Behavioral Simulation を選択して、シミュレーションを行った。
bm_dispc_axis_20_210406.png

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

画像データの出力開始部分を拡大した。
bm_dispc_axis_22_210406.png

VSYNC が見えるように 17 ms までシミュレーションを行った。
bm_dispc_axis_23_210406.png

問題無さそうだ。
なお、この AXI4-stream 版ビットマップ・ディスプレイ・コントローラはビデオ信号出力なので、 Digilent の rgb2dvi IP を使用して、HDMI 出力として出力する予定だ。
  1. 2021年04月06日 03:59 |
  2. IP
  3. | トラックバック:0
  4. | コメント:0

Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする2

Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする1”の続き。

Vitis Vision Library を本格的にやるために、前回は、Vivado 2020.2 を使用してビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にするために、非同期FIFO の bitmap_afifo を生成した。今回は、シミュレーションするために、Vitis_HLS 2020.2 で AXI4-Stream Master Model を作ってみよう。

”Vivado HLS でAXI4-Stream Master Model IP を作る”を参考にして、Vitis_HLS 2020.2 で AXI4-Stream Master Model を作っていく。

”Vivado HLS でAXI4-Stream Master Model IP を作る”のソースコードとテストベンチを使って axi4_stream_master プロジェクトを作った。
bm_dispc_axis_10_210405.png

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

C コードを合成した。結果を示す。
bm_dispc_axis_12_210405.png
bm_dispc_axis_13_210405.png

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

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

outs_TVALID と outs_TREADY がずっと1のままなのが分かる。

最初の部分を拡大した。データも思い通りだ。
bm_dispc_axis_16_210405.png

画像の 1 行はちょうど 8 us = 8000 ns で、1 クロックは 10 ns なので、ちょうど 800 ピクセルになる。
bm_dispc_axis_17_210405.png

solution1/syn/veilog ディレクトリの axi4_stream_master.v と axi4_stream_master_regslice_both.v を Vivado のシミュレーション・ファイルとして使用する。
bm_dispc_axis_18_210405.png
  1. 2021年04月05日 05:24 |
  2. IP
  3. | トラックバック:0
  4. | コメント:0

Vivado 2020.2 を使ってビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にする1

Vitis Vision Library を以前やってみたのだが、 Array2xfMat と xfMat2Array を使用する Mat 形式のデータを DMA で持ってきて Vitis Vision Library で処理する形式が一般的のようだ。 AXI4-Stream ー フィルタ ー AXI4-Stream とするパスはうまく実装できなかった。そこで、 Mat 形式のデータを DMA で持ってきて使うために AXI4-Stream 入出力の IP を使いたいと思う。パラレル入力のカメラ・インターフェース IP は AXI4-Stream 出力になっているので、ビットマップ・ディスプレイ・コントローラを AXI4-Stream 入力にしたい。今回は、 Vivado 2020.2 を使用してビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にしようと思う。手始めに非同期FIFO の bitmap_afifo を生成する。

実は、 2016 年の 8 月にビットマップ・ディスプレイ・コントローラをAXI4-Stream対応にしたのだが、もう一度やってみよう。
関連する記事は、以下の通り。
”ビットマップ・ディスプレ イ・コントローラをAXI4-Stream対応にする1(構想編)”
”ビットマップ・ディスプレ イ・コントローラをAXI4-Stream対応にする2(bitmap_afifo)”
”ビットマップ・ディスプレ イ・コントローラをAXI4-Stream対応にする3(シミュレーション)”
”ビットマップ・ディスプレ イ・コントローラをAXI4-Stream対応にする4(ソースコードの公開)”
”ビットマップ・ディスプレ イ・コントローラをAXI4-Stream対応にする5(IP作成)”

今回は、Zynq 用のVivado 2020.2 のプロジェクトを作って、bitmap_afifo (ビットマップのピクセル用の非同期FIFO)を作る。
Vivado 2016.2 と 2020.2 の違いについても確認したい。

Vivado 2020.2 で作成した bm_dispc_axis プロジェクトを示す。まだ空の状態なので、 bitmap_afifo を FIFO Generator で作成する。
IP Catalog をクリックして、IP Catalog ウインドウを表示し、 fifo で検索する。FIFO Generator が表示されるので、それをダブルクリックして、ダイアログを表示させた。
bm_dispc_axis_1_210404.png

Customize IP ダイアログが表示された。
Componet Name を bitmap_afifo とした。
Basic タブで Fifo Implementation を Independent Clocks Block RAM を選択した。
Read Mode は First Word Fall Through とした。
bm_dispc_axis_2_210404.png

Natrive Ports タブでは、以下のとおりに設定した。
Read Mode : Fist Word Fall Through
Data Port Parameters : Write Width 32
Data Port Parameters : Write Depth 512
Data Port Parameters : Read Width 32
Data Port Parameters : Read Depth 512
Enabel Reset Synchronization のチェックを外した
bm_dispc_axis_3_210404.png

Status Flags では、Overflow と Underflow Flag にチェックを入れた。
bm_dispc_axis_4_210404.png

Data Counts では More Accurate Data Counts にチェックを入れた。これを入れないと First Word Fall Through の場合はカウントが狂うというか、First Word Fall Through の回路を含んだカウントにならない。
Write Data Count (Synchronized with Write Clk) にチェックを入れた。
bm_dispc_axis_5_210404.png

Summary タブを示す。
bm_dispc_axis_6_210404.png

OK ボタンをクリックする。
Generate Output Products ダイアログが表示された。
Generate ボタンをクリックする。
bm_dispc_axis_7_210404.png

Generate Output Products ダイアログが表示された。
bm_dispc_axis_8_210404.png

bitmap_afifo が生成された。
bm_dispc_axis_9_210404.png
  1. 2021年04月04日 05:26 |
  2. IP
  3. | トラックバック:0
  4. | コメント:0

私の nngen の現在の状況

「ゼロから作るDeep Learning」のCNNをNNgenでハードウェア化する企画では、nngen での推論がうまく行かなかった。たぶん、「ゼロから作るDeep Learning」のCNNと NNgen はネットワークの構造の定義が何処か違うのではないかな?(”「ゼロから作るDeep Learning」のCNNをNNgenでハードウェア化する3”)
そこで onnx モデルから NNgen のモデルに実行しようとしたのだが、 onnx を動かそうとしているうちに NNgen も動作しなくなってしまった。やはり、Docker で構築したほうが良いかも知れない? Docker を使うとハードディスク容量が食われてしまうので、余り使いたくないのだが。。。

さて、以前ブログに書いた”TensorFlow + Kerasを使ってみた3(以前使用したCNNを使った学習)”の CNN を再度やってみよう。
ネットワークの学習をしたのだが、エラーで動作しなかった。エラーの部分はここだった。

y_train = keras.utils.np_utils.to_categorical(y_train, num_classes)
y_test = keras.utils.np_utils.to_categorical(y_test, num_classes)


Keras Documentation の to_categorical によると

keras.utils.to_categorical(y, num_classes=None, dtype='float32')

となったようで、 np_utils を抜かす様になったようだ。

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

に変更すると実行することができた。

TensorFlow + Keras の MNIST サンプルを学習した。
NNgen2_65_210403.png
NNgen2_66_210403.png

精度は 0.8668 だった。

Python コードを貼っておく。

# My Mnist CNN (Convolution layerの特徴マップは5個)
# Conv2D - ReLU - MaxPooling - Dence - ReLU - Dence
# 2018/05/25 by marsee
# Keras / Tensorflowで始めるディープラーニング入門 https://qiita.com/yampy/items/706d44417c433e68db0d
# のPythonコードを再利用させて頂いている

import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D, Activation
from keras import backend as K

batch_size = 128
num_classes = 10
epochs = 30

img_rows, img_cols = 28, 28

(x_train, y_train), (x_test, y_test) = mnist.load_data()

#Kerasのバックエンドで動くTensorFlowとTheanoでは入力チャンネルの順番が違うので場合分けして書いています
if K.image_data_format() == 'channels_first':
    x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
    x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
    input_shape = (1, img_rows, img_cols)
else:
    x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
    x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
    input_shape = (img_rows, img_cols, 1)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

y_train = y_train.astype('int32')
y_test = y_test.astype('int32')
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test =  keras.utils.to_categorical(y_test, num_classes)

model = Sequential()
model.add(Conv2D(10, kernel_size=(5, 5),
                 input_shape=input_shape))
model.add(Activation(activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(100))
model.add(Activation(activation='relu'))
model.add(Dense(num_classes))
model.add(Activation(activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adadelta(),
              metrics=['accuracy'])
history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs,
          verbose=1, validation_data=(x_test, y_test))


精度とロスのグラフを書いた。
NNgen2_67_210403.png
NNgen2_68_210403.png

keras2onnx で onnx モデルに変換できた。
NNgen で onnx モデルを nngen モデルに変換しようとしたら

ModuleNotFoundError: No module named 'nngen'

と言われてしまった。
NNgen2_69_210403.png

いろいろと onnx 動かそうとしているうちに、 NNgen の必要とするライブラリがアップデートされてしまったようだ。
Docker を使ったほうが良さそうだ。
とりあえず、他のやりたいことがあるので、NNgen はとりあえず休止しようと思う。そして、チュートリアルができたら再開してみよう。
今まで、サポートしていただいた作者の shtaxxx さん、ありがとうございました。そして、NNgen をうまく試すことができなくて、申し訳ありません。
  1. 2021年04月03日 05:05 |
  2. NNgen
  3. | トラックバック:0
  4. | コメント:0

ONNX と keras2onnx をインストールする

オライリー出版社の”「ゼロから作るDeep Learning」”の CNN から NNgen を使って CNN のハードウェアを生成する試みはとりあえず成功しなかった。今度は、ONNX モデルから NNgen に変換してみよう。ということで、自分の Ubuntu 18.04 LTS に tensorflow, keras, onnx, keras2onnx, onnxruntiome をインストールした。

なお、私の環境は Anaconda と pip の混在環境になっている。Anaconda に存在するのに pip でインストールしていると、conda でインストールしようとすると、

Traceback (most recent call last):
  File "/home/masaaki/anaconda3/bin/conda", line 7, in <module>
    from conda.cli import main
ModuleNotFoundError: No module named 'conda'


が表示された。これを治すには、インストールされた anaconda3 のディレクトリをリネームして、もう一度、anaconda3 を再インストールした。

以前、”TensorFlow + Kerasを使ってみた1(インストール編)”で tensorflow, keras をインストールしたはずなのだが、もう一度インストールした。

Tensorflow インストール
conda install tensorflow
NNgen2_55_210401.png
NNgen2_56_210401.png

Keras インストール
conda install keras
NNgen2_57_210401.png
NNgen2_58_210401.png

keras2onnx インストール
pip install keras2onnx
NNgen2_59_210401.png
NNgen2_60_210401.png

ONNX インストールの準備
conda install -c conda-forge potbuf numpy
NNgen2_61_210401.png
NNgen2_62_210401.png

ONNX インストール
onnx/onnx の Installation を参照した。
git clone https://github.com/onnx/onnx.git
cd onnx
git submodule update --init --recursive
python setup.py install

NNgen2_63_210401.png

onnxruntime インストール
pip install onnxruntime
NNgen2_64_210401.png
  1. 2021年04月02日 04:59 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」のCNNをNNgenでハードウェア化する5

「ゼロから作るDeep Learning」のCNNをNNgenでハードウェア化する4”の続き。

2017年の 6 月にやってみたオライリー出版社の”「ゼロから作るDeep Learning」”の deep-learning-from-scratch/ch07/ の畳み込みニューラルネットワーク(CNN)を NNgen でハードウェア化してみることにしたということで、前回は、オライリー出版社の”「ゼロから作るDeep Learning」”の deep-learning-from-scratch/ch07/ の畳み込みニューラルネットワーク(CNN)に NNgen での整数(と同等)の重みとバイアスにして推論してみたが、うまく行った。今回は、Jupyter Notebook の今までの記述をまとめて貼っておこうと思う。
次やるときは、Keras の MNIST の実装を ONNX のモデルに変換してから NNgen に取り込んでみようと思う。

nngen_example_mnist_cnn.ipynb の内容を貼っておく。

# 「ゼロから作るDeep Learning」のCNNをNNgenでハードウェア化する

参照URL”「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化1”
https://marsee101.blog.fc2.com/blog-entry-3829.html

NNgen/nngen https://github.com/NNgen/nngen

”「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化1”のMNIST CNNをもう一度やってみる


```python
# train_convnet.py
# 2017/06/06 FPGAによるハードウェア化をにらんで、量子化を行う by marsee
# 元になったコードは、https://github.com/oreilly-japan/deep-learning-from-scratch にあります。
# 改変したコードもMITライセンスとします。 2017/06/19 by marsee

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from trainer_int import Trainer
from simple_convnet_int import SimpleConvNet

# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False)

# 処理に時間のかかる場合はデータを削減 
#x_train, t_train = x_train[:5000], t_train[:5000]
#x_test, t_test = x_test[:1000], t_test[:1000]

#max_epochs = 5
max_epochs = 20

network = SimpleConvNet(input_dim=(1,28,28), 
                        conv_param = {'filter_num': 10, 'filter_size': 5, 'pad': 0, 'stride': 1},
                        #conv_param = {'filter_num': 30, 'filter_size': 5, 'pad': 0, 'stride': 1},
                        hidden_size=100, output_size=10, weight_init_std=0.01)
                        
trainer = Trainer(network, x_train, t_train, x_test, t_test,
                  epochs=max_epochs, mini_batch_size=100,
                  optimizer='Adam', optimizer_param={'lr': 0.001},
                  evaluate_sample_num_per_epoch=1000)
trainer.train()

'''x_testn, t_testn = x_test[:500], t_test[:500]
test_accn = network.accuracy_msg(x_testn, t_testn)
print(test_accn)'''

'''train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
print(train_acc, test_acc)
train_acc_int = network.accuracy_int(x_train, t_train)'''
#test_acc_int = network.accuracy_int(x_test, t_test)
#print(test_acc_int)

# パラメータの保存
network.save_params("params.pkl")
print("Saved Network Parameters!")

# グラフの描画
markers = {'train': 'o', 'test': 's'}
x = np.arange(max_epochs)
plt.plot(x, trainer.train_acc_list, marker='o', label='train', markevery=2)
plt.plot(x, trainer.test_acc_list, marker='s', label='test', markevery=2)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
```

    train loss:2.301385118388284
    === epoch:1, train acc:0.184, test acc:0.188 ===
    train loss:2.300783416724263
    train loss:2.2985280211636856


    train loss:0.0014416474424052506
    train loss:0.00029753533974773714
    train loss:0.0024815949870809124
    train loss:0.001080201861468442
    train loss:0.009542414638567448
    train loss:0.0003716470091183171
    train loss:0.0010014160283579254
    train loss:0.000636980396385224
    train loss:0.0007263677665641715
    train loss:0.0025971938006329677
    train loss:0.003134432045827001
    train loss:0.0030433384352522848
    train loss:0.0016623108178602428
    train loss:0.004724656919902167
    train loss:0.002754500627006845
    train loss:0.00028287348906820743
    train loss:0.0020828163320118203
    train loss:0.0031006529569975816
    train loss:0.005907909665949748
    train loss:0.0037946706647029875
    train loss:0.0007289706523717551
    train loss:0.0006987468668795674
    train loss:0.0027144383364796636
    train loss:0.006576938863826107
    =============== Final Test Accuracy ===============
    test acc:0.9877
    Saved Network Parameters!



    
![png](output_3_1.png)
    


一旦落としてからもう一度やる時のロード関数


```python
# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from trainer_int import Trainer
from simple_convnet_int import SimpleConvNet

# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False)

# 処理に時間のかかる場合はデータを削減 
#x_train, t_train = x_train[:5000], t_train[:5000]
#x_test, t_test = x_test[:1000], t_test[:1000]

#max_epochs = 5
max_epochs = 20

network = SimpleConvNet(input_dim=(1,28,28), 
                        conv_param = {'filter_num': 10, 'filter_size': 5, 'pad': 0, 'stride': 1},
                        #conv_param = {'filter_num': 30, 'filter_size': 5, 'pad': 0, 'stride': 1},
                        hidden_size=100, output_size=10, weight_init_std=0.01)
network.load_params("params.pkl")
```


```python
test_acc_int = network.accuracy_int(x_test, t_test)
print(test_acc_int)
```

    0.9833


「ゼロから作るDeep Learning」のMNIST CNNの重みやバイアスの配列の形状を見て、reshapeでNNgenに合わせる


```python
print(network.params['W1'].shape)
print(network.params['b1'].shape)
print(network.params['W2'].shape)
print(network.params['b2'].shape)
print(network.params['W3'].shape)
print(network.params['b3'].shape)

W1n = network.params['W1'].transpose(0,2,3,1)
print(W1n.shape)
print(np.max(W1n))
print(np.min(W1n))
B1n = network.params['b1']
print(np.max(B1n))
print(np.min(B1n))
W2n=network.params['W2'].transpose(1,0)
print(W2n.shape)
print(np.max(W2n))
print(np.min(W2n))
B2n = network.params['b2']
print(np.max(B2n))
print(np.min(B2n))
W3n=network.params['W3'].transpose(1,0)
print(W3n.shape)
print(np.max(W3n))
print(np.min(W3n))
B3n = network.params['b3']
print(np.max(B3n))
print(np.min(B3n))
```

    (10, 1, 5, 5)
    (10,)
    (1440, 100)
    (100,)
    (100, 10)
    (10,)
    (10, 5, 5, 1)
    0.6205409499741875
    -0.9768615311384286
    0.001046327841590367
    -0.43582685576224633
    (100, 1440)
    0.8932422073086069
    -0.9514574018404229
    0.17520125723869304
    -0.12940758873286193
    (10, 100)
    0.3224653381346921
    -0.6238471654267962
    0.09732122727552153
    -0.07801633865297178


NNgenのオペレータを使用したMNIST CNNの実装


```python
from __future__ import absolute_import
from __future__ import print_function

import sys
import os

import nngen as ng


# data types
act_dtype = ng.int32
weight_dtype = ng.int8
bias_dtype = ng.int16
scale_dtype = ng.int8
batchsize = 1

# input
input_layer = ng.placeholder(dtype=ng.int32,
                             shape=(batchsize, 28, 28, 1),  # N, H, W, C
                             name='input_layer')

# layer 0: conv2d (with bias and scale (= batchnorm)), relu, max_pool
wn0 = ng.variable(dtype=weight_dtype,
                 shape=(10, 5, 5, 1),  # Och, Ky, Kx, Ich
                 name='wn0')
bn0 = ng.variable(dtype=bias_dtype,
                 shape=(wn0.shape[0],), name='bn0')
sn0 = ng.variable(dtype=scale_dtype,
                 shape=(wn0.shape[0],), name='sn0')

a0 = ng.conv2d(input_layer, wn0,
               strides=(1, 1, 1, 1),
               bias=bn0,
               scale=sn0,
               padding='VALID',
               act_func=ng.relu,
               dtype=act_dtype,
               sum_dtype=ng.int32)

a0p = ng.max_pool_serial(a0,
                         ksize=(1, 2, 2, 1),
                         strides=(1, 2, 2, 1))

a0r = ng.reshape(a0p, [batchsize, -1])

# layer 1: full-connection, relu
wn1 = ng.variable(weight_dtype,
                 shape=(100, a0r.shape[-1]),
                 name='wn1')
bn1 = ng.variable(bias_dtype,
                 shape=(wn1.shape[0],),
                 name='bn1')
sn1 = ng.variable(scale_dtype,
                 shape=(wn1.shape[0],),
                 name='sn1')

a1 = ng.matmul(a0r, wn1,
               bias=bn1,
               scale=sn1,
               transposed_b=True,
               act_func=ng.relu,
               dtype=act_dtype,
               sum_dtype=ng.int32)

# layer 2: full-connection, relu
wn2 = ng.variable(weight_dtype,
                 shape=(10, a1.shape[-1]),
                 name='wn2')
bn2 = ng.variable(bias_dtype,
                 shape=(wn2.shape[0],),
                 name='bn2')
sn2 = ng.variable(scale_dtype,
                 shape=(wn2.shape[0],),
                 name='sn2')

# output
output_layer = ng.matmul(a1, wn2,
                         bias=bn2,
                         scale=sn2,
                         transposed_b=True,
                         name='output_layer',
                         dtype=act_dtype,
                         sum_dtype=ng.int32)
```

NNgenのMIST CNNの重みやバイアスの配列の形状を確認する


```python
print(wn0.shape)
print(bn0.shape)
print(wn1.shape)
print(bn1.shape)
print(wn2.shape)
print(bn2.shape)
print(a0.shape)
print(a0p.shape)
print(a0r.shape)
```

    (10, 5, 5, 1)
    (10,)
    (100, 1440)
    (100,)
    (10, 100)
    (10,)
    (1, 24, 24, 10)
    (1, 12, 12, 10)
    (1, 1440)


「ゼロから作るDeep Learning」のMNIST CNNの重みやバイアスを整数化するために128を乗算するためのテスト


```python
print(W1n[0][0][0][0])
print(W1n[0][0][1][0])
W1n2 = W1n * 127.9
print(W1n2[0][0][0][0])
print(W1n2[0][0][1][0])
```

    0.10187240072522206
    0.08383048453038348
    13.029480052755902
    10.721918971436049


「ゼロから作るDeep Learning」のMNIST CNNの重みやバイアスを整数化するために128を乗算する


```python
W1n2 = W1n * 127.9
print(np.max(W1n2))
print(np.min(W1n2))
B1n2 = B1n * 16383.9
print(np.max(B1n2))
print(np.min(B1n2))
W2n2 = W2n * 127.9
B2n2 = B2n * 16383.9
W3n2 = W3n * 127.9
B3n2 = B3n * 16383.9
```

    79.36718750169858
    -124.94058983260501
    17.142930723832414
    -7140.5436221230675


NNgenのMIST CNNの重みやバイアスに「ゼロから作るDeep Learning」のMNIST CNNの重みやバイアスの値を代入する
Aout = ((Ain * W + bias) * scale) >> rshift_out だそうなので、scaleは要素がすべて1の配列とする


```python
wn0_value = W1n2.astype(np.int8)
wn0.set_value(wn0_value)
print(wn0_value[0][0][0][0])
print(wn0_value[0][0][1][0])
bn0_value = B1n2.astype(np.int16)
bn0.set_value(bn0_value)
sn0_value = np.ones(sn0.shape, dtype=np.int8)
sn0.set_value(sn0_value)
print(sn0_value[0])

wn1_value = W2n2.astype(np.int8)
wn1.set_value(wn1_value)
bn1_value = B2n2.astype(np.int16)
bn1.set_value(bn1_value)
sn1_value = np.ones(sn1.shape, dtype=np.int8)
sn1.set_value(sn1_value)

wn2_value = W3n2.astype(np.int8)
wn2.set_value(wn2_value)
bn2_value = B3n2.astype(np.int16)
bn2.set_value(bn2_value)
sn2_value = np.ones(sn2.shape, dtype=np.int8)
sn2.set_value(sn2_value)
```

    13
    10
    1


ハードウェア属性の割当


```python
# conv2d, matmul
# par_ich: parallelism in input-channel
# par_och: parallelism in output-channel
# par_col: parallelism in pixel column
# par_row: parallelism in pixel row
# cshamt_out: right shift amount after applying bias/scale

par_ich = 2
par_och = 2
cshamt_out = weight_dtype.width - 1

a0.attribute(par_ich=par_ich, par_och=par_och,
             cshamt_out=0)
a1.attribute(par_ich=par_ich, par_och=par_och,
             cshamt_out=0)
output_layer.attribute(par_ich=par_ich, par_och=par_och,
                       cshamt_out=weight_dtype.width +7)

# max_pool
# par: parallelism in in/out channel

par = par_och

a0p.attribute(par=par)
```

NNgenデータフローをソフトウェアとして実行して、DNNモデルの動作を確認


```python
print(t_test[0])
print(t_test[1])
print(t_test[2])
print(t_test[3])
print(t_test[4])
print(x_test[0].shape)
```

    7
    2
    1
    0
    4
    (1, 28, 28)



```python
input_layer_value = x_test[1].transpose(1,2,0)
input_layer_value = input_layer_value.reshape(input_layer.shape)
print(input_layer_value.shape)
input_layer_value = input_layer_value * 127.9
input_layer_value = input_layer_value.astype(np.int8)
eval_outs = ng.eval([output_layer], input_layer=input_layer_value)
output_layer_value = eval_outs[0]

print(output_layer_value)
```

    (1, 28, 28, 1)
    [[-112 -155  -19    7  -57   29  -42 -275   37 -118]]



```python
print(t_test[0:5])
print(network.predict_int(x_test[0:5]))
```

    [7 2 1 0 4]
    [[ -8.59375 -12.0625   -5.34375  -2.25    -29.25    -12.09375 -30.8125
       16.65625  -9.90625  -1.1875 ]
     [ -8.59375   3.25     18.0625   -4.59375 -32.      -12.375    -7.40625
      -23.46875 -12.3125  -23.75   ]
     [-14.15625   4.71875  -5.96875  -8.21875  -3.84375  -9.8125  -11.34375
       -3.90625  -0.375    -5.53125]
     [ 15.25    -12.4375   -6.40625  -6.8125  -16.84375  -5.03125  -0.3125
        0.625   -10.71875  -5.84375]
     [-20.28125 -13.0625   -9.3125  -10.84375  12.5      -6.03125  -9.46875
       -6.5625   -5.375     2.28125]]


「ゼロから作るDeep Learning」の畳み込みニューラルネットワークでNNgen用に変換した重みとバイアスを使って正しい推論ができるかどうか?を検証する


```python
network.params['W1'] = np.int32(network.params['W1'] * 127.9)
network.params['b1'] = np.int32(network.params['b1'] * 16383.9)
network.params['W2'] = np.int32(network.params['W2'] * 127.9)
network.params['b2'] = np.int32(network.params['b2'] * 16383.9)
network.params['W3'] = np.int32(network.params['W3'] * 127.9)
network.params['b3'] = np.int32(network.params['b3'] * 16383.9)
```


```python
print(np.max(network.params['W1']))
print(np.min(network.params['W1']))
print(np.max(network.params['b1']))
print(np.min(network.params['b1']))
print(np.max(network.params['W2']))
print(np.min(network.params['W2']))
print(np.max(network.params['b2']))
print(np.min(network.params['b2']))
print(np.max(network.params['W3']))
print(np.min(network.params['W3']))
print(np.max(network.params['b3']))
print(np.min(network.params['b3']))
```

    79
    -124
    17
    -7140
    114
    -121
    2870
    -2120
    41
    -79
    1594
    -1278



```python
print(t_test[0:5])
x_test_rshft7 = np.int32(x_test * 127.9)
predict_result = network.predict(x_test_rshft7[0:5])/512
predict_result = np.int32(predict_result)
print(predict_result)
```

    [7 2 1 0 4]
    [[-2 -3 -1 -1 -8 -4 -9  5 -3  0]
     [-2  0  4 -2 -9 -3 -1 -8 -2 -6]
     [-3  1 -1 -2 -1 -3 -3 -1  0 -2]
     [ 4 -3 -2 -1 -4 -1  0  0 -2 -1]
     [-4 -3 -2 -3  4 -2 -3 -1 -1  0]]


output_3_1.png
  1. 2021年04月01日 04:46 |
  2. NNgen
  3. | トラックバック:0
  4. | コメント:0