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

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

FPGAの部屋

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

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

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

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

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

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

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

disp_dmar_axis_vga_9_190713.jpg

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

これも良さそうだ。

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

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

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

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

拡大した。
disp_dmar_axis_vga_17_190716.png

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

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

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

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

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

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

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

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

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

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

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

#include "disp_dmar_axis_vga.h"

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

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

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

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

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


次に、vga_dmar_axis.cpp を示す。

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

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

#include "disp_dmar_axis_vga.h"

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

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

    AP_AXIU32 pix;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


axis_expand.cpp を示す。

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


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

#include "disp_dmar_axis_vga.h"

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

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

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

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

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


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


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

#ifndef __DISP_DMAR_AXIS_VGA_H__
#define __DISP_DMAR_AXIS_VGA_H__

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

#define VGA_WIDTH 640
#define VGA_HEIGHT 480

#define SVGA_WIDTH 800
#define SVGA_HEIGHT 600

#define XGA_WIDTH 1024
#define XGA_HEIGHT 768

#define HD_WIDTH 1920
#define HD_HEIGHT 1080

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

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

#endif


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

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

VGA画像をXGA画像やHD画像に変換するdisp_dmar_axis_vga IP 2(性能が足りない)”の続き。

前回は、disp_dmar_axis IP を作って、C シミュレーション、C コードの合成、C/RTL 協調シミュレーションを行ったが、C/RTL 協調シミュレーション波形から性能が足りないということが分かった。今回は、ソースコードを 2 種類書いて試してみたが、うまく行かなかった。

まずは、”2つのHLSストリームを同時に入力して演算してHLSストリーム出力2”を参考にして、DMA 部分とAXI4 Stream 部分を分けた。DMA部分では、VGA画像をDMA してHLS Stream で出力する。AXI4 Stream 部分では、DMA 部分からのAXI4 Stream を受けて、周りに 0 のデータを挿入して、解像度を大きくする。DMA 部分とAXI4 Stream 部分を別関数にして、DATAFLOW 指示子で並列動作を狙った。
ソースコード disp_dmar_axis_vga.cpp を示す。

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

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

#include "disp_dmar_axis_vga.h"

int disp_dmar_stream(AXI_STREAM &ins, AXI_STREAM &outs, int max_width, int max_height);
int fb0_hls_stream(volatile ap_int<32> *fb, AXI_STREAM &outs);
int fb1_hls_stream(volatile ap_int<32> *fb, AXI_STREAM &outs);
int fb2_hls_stream(volatile ap_int<32> *fb, AXI_STREAM &outs);

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

    AP_AXIU32 pix;
    AXI_STREAM indata0;
    AXI_STREAM indata1;
    AXI_STREAM indata2;
#pragma HLS STREAM variable=indata0 depth=64 dim=1
#pragma HLS STREAM variable=indata1 depth=64 dim=1
#pragma HLS STREAM variable=indata2 depth=64 dim=1

    if (active_frame == (ap_uint<2>)0){
        fb2_hls_stream(fb2, indata0);
        disp_dmar_stream(indata0, outs, col, row);
    }else if (active_frame == (ap_uint<2>)1){
        fb0_hls_stream(fb0, indata1);
        disp_dmar_stream(indata1, outs, col, row);
    }else{
        fb1_hls_stream(fb1, indata2);
        disp_dmar_stream(indata2, outs, col, row);
    }

    return(0);
}

int disp_dmar_stream(AXI_STREAM &ins, AXI_STREAM &outs, int max_width, int max_height){
    AP_AXIU32 pix;
    AP_AXIU32 dmad;

    int x_padding = (max_width - VGA_WIDTH)/2;
    int y_padding = (max_height - VGA_HEIGHT)/2;

    LOOP_Y0: for (int y=0; y<max_height; y++){
#pragma HLS LOOP_TRIPCOUNT min=600 max=1080 avg=768
        LOOP_X0: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            if (y < y_padding || y >= y_padding+VGA_HEIGHT)
                pix.data = 0;
            else if (x < x_padding || x >= x_padding+VGA_WIDTH)
                pix.data = 0;
            else{
                ins >> dmad;
                pix.data = dmad.data;
            }

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

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

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

int fb0_hls_stream(volatile ap_int<32> *fb0, AXI_STREAM &outs){
    AP_AXIU32 dmad;

    LOOP_Y1: for(int y=0; y<VGA_HEIGHT; y++){
        LOOP_X1: for(int x=0; x<VGA_WIDTH; x++){
#pragma HLS PIPELINE II=1
            dmad.data = fb0[(y*VGA_WIDTH)+x];
            outs << dmad;
        }
    }
    return(0);
}

int fb1_hls_stream(volatile ap_int<32> *fb1, AXI_STREAM &outs){
    AP_AXIU32 dmad;

    LOOP_Y1: for(int y=0; y<VGA_HEIGHT; y++){
        LOOP_X1: for(int x=0; x<VGA_WIDTH; x++){
#pragma HLS PIPELINE II=1
            dmad.data = fb1[(y*VGA_WIDTH)+x];
            outs << dmad;
        }
    }
    return(0);
}

int fb2_hls_stream(volatile ap_int<32> *fb2, AXI_STREAM &outs){
    AP_AXIU32 dmad;

    LOOP_Y1: for(int y=0; y<VGA_HEIGHT; y++){
        LOOP_X1: for(int x=0; x<VGA_WIDTH; x++){
#pragma HLS PIPELINE II=1
            dmad.data = fb2[(y*VGA_WIDTH)+x];
            outs << dmad;
        }
    }
    return(0);
}


この実装でC コードの合成はうまく行くのだが、C/RTL 協調シミュレーションで出力が出ないで、シミュレーションがいつまで経っても終わらなかった。
いずれの問題も、DMA エンジンを 3 個入れているところがまずいのだと思うが、DMA アドレスのオフセットを毎回変えずに、トリプル・バッファリングを実現するのには、必要となる。


次の実装は、if 文での実装に問題が合って単発転送になっているのじゃないか?という推測のもとに、for 文を細かく区切って実装したら、性能的にはどうだろうか?ということでやってみた。
ソースコード disp_dmar_axis_vga.cpp を示す。

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

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

#include "disp_dmar_axis_vga.h"

int disp_dmar_fb0(volatile ap_int<32> *fb0, AXI_STREAM &outs, int max_width, int max_height);
int disp_dmar_fb1(volatile ap_int<32> *fb1, AXI_STREAM &outs, int max_width, int max_height);
int disp_dmar_fb2(volatile ap_int<32> *fb2, AXI_STREAM &outs, int max_width, int max_height);
void pix_in_zero(AP_AXIU32 &pix, int x, int y, int max_width);

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

    AP_AXIU32 pix;
    int max_width, max_height;

    switch((int)resolution){
        case 0: // SVGA
            max_width = SVGA_WIDTH;
            max_height = SVGA_HEIGHT;
            break;
        case 1: // XGA
            max_width = XGA_WIDTH;
            max_height = XGA_HEIGHT;
            break;
        default: // HD
            max_width = HD_WIDTH;
            max_height = HD_HEIGHT;
            break;
    }

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

int disp_dmar_fb0(volatile ap_int<32> *fb0, AXI_STREAM &outs, int max_width, int max_height){
    AP_AXIU32 pix;
    int x_padding = (max_width - VGA_WIDTH)/2;
    int y_padding = (max_height - VGA_HEIGHT)/2;

    Y00: for (int y=0; y<y_padding; y++){ // 最初の空白行
#pragma HLS LOOP_TRIPCOUNT min=60 max=300 avg=144
        X00: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
    }

    Y01: for (int y=y_padding; y<y_padding+VGA_HEIGHT; y++){
#pragma HLS LOOP_TRIPCOUNT min=480 max=480 avg=480
        X010: for (int x=0; x<x_padding; x++){ // 最初の空白列
#pragma HLS LOOP_TRIPCOUNT min=80 max=640 avg=192
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
        X011: for (int x=x_padding; x<x_padding+VGA_WIDTH; x++){
#pragma HLS LOOP_TRIPCOUNT min=640 max=640 avg=640
#pragma HLS PIPELINE II=1
            pix.data = fb0[((y-y_padding)*VGA_WIDTH)+x-x_padding];
            
            if (x==0 && y==0)
                pix.user = 1;
            else
                pix.user = 0;

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

            outs << pix;
        }
        X012: for (int x=x_padding+VGA_WIDTH; x<max_width; x++){ // 最後の空白列
#pragma HLS LOOP_TRIPCOUNT min=80 max=640 avg=192
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
    }

    Y02: for (int y=y_padding+VGA_HEIGHT; y<max_height; y++){ // 最後の空白行
#pragma HLS LOOP_TRIPCOUNT min=60 max=300 avg=144
        X02: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
    }
    
    return(0);
}

int disp_dmar_fb1(volatile ap_int<32> *fb1, AXI_STREAM &outs, int max_width, int max_height){
    AP_AXIU32 pix;
    int x_padding = (max_width - VGA_WIDTH)/2;
    int y_padding = (max_height - VGA_HEIGHT)/2;

    Y10: for (int y=0; y<y_padding; y++){ // 最初の空白行
#pragma HLS LOOP_TRIPCOUNT min=60 max=300 avg=144
        X10: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
    }

    Y11: for (int y=y_padding; y<y_padding+VGA_HEIGHT; y++){
#pragma HLS LOOP_TRIPCOUNT min=480 max=480 avg=480
        X100: for (int x=0; x<x_padding; x++){ // 最初の空白列
#pragma HLS LOOP_TRIPCOUNT min=80 max=640 avg=192
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
        X101: for (int x=x_padding; x<x_padding+VGA_WIDTH; x++){
#pragma HLS LOOP_TRIPCOUNT min=640 max=640 avg=640
#pragma HLS PIPELINE II=1
            pix.data = fb1[((y-y_padding)*VGA_WIDTH)+x-x_padding];
            
            if (x==0 && y==0)
                pix.user = 1;
            else
                pix.user = 0;

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

            outs << pix;
        }
        X102: for (int x=x_padding+VGA_WIDTH; x<max_width; x++){ // 最後の空白列
#pragma HLS LOOP_TRIPCOUNT min=80 max=640 avg=192
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
    }

    Y12: for (int y=y_padding+VGA_HEIGHT; y<max_height; y++){ // 最後の空白行
#pragma HLS LOOP_TRIPCOUNT min=60 max=300 avg=144
        X12: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
    }
    
    return(0);
}

int disp_dmar_fb2(volatile ap_int<32> *fb2, AXI_STREAM &outs, int max_width, int max_height){
    AP_AXIU32 pix;
    int x_padding = (max_width - VGA_WIDTH)/2;
    int y_padding = (max_height - VGA_HEIGHT)/2;

    Y20: for (int y=0; y<y_padding; y++){ // 最初の空白行
#pragma HLS LOOP_TRIPCOUNT min=60 max=300 avg=144
        X20: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
    }

    Y21: for (int y=y_padding; y<y_padding+VGA_HEIGHT; y++){
#pragma HLS LOOP_TRIPCOUNT min=480 max=480 avg=480
        X210: for (int x=0; x<x_padding; x++){ // 最初の空白列
#pragma HLS LOOP_TRIPCOUNT min=80 max=640 avg=192
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
        X211: for (int x=x_padding; x<x_padding+VGA_WIDTH; x++){
#pragma HLS LOOP_TRIPCOUNT min=640 max=640 avg=640
#pragma HLS PIPELINE II=1
            pix.data = fb2[((y-y_padding)*VGA_WIDTH)+x-x_padding];
            
            if (x==0 && y==0)
                pix.user = 1;
            else
                pix.user = 0;

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

            outs << pix;
        }
        X212: for (int x=x_padding+VGA_WIDTH; x<max_width; x++){ // 最後の空白列
#pragma HLS LOOP_TRIPCOUNT min=80 max=640 avg=192
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
    }

    Y22: for (int y=y_padding+VGA_HEIGHT; y<max_height; y++){ // 最後の空白行
#pragma HLS LOOP_TRIPCOUNT min=60 max=300 avg=144
        X22: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            pix_in_zero(pix, x, y, max_width);
            outs << pix;
        }
    }
    
    return(0);
}

void pix_in_zero(AP_AXIU32 &pix, int x, int y, int max_width){
    pix.data = 0;
    
    if (x==0 && y==0)
        pix.user = 1;
    else
        pix.user = 0;

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


これも C/RTL 協調シミュレーションをやってみたところ、”VGA画像をXGA画像やHD画像に変換するdisp_dmar_axis_vga IP 2(性能が足りない)”と同様に単発転送になってしまった。ここまで書くことは無いようだ。
次回は、うまく行った実装を紹介する。
  1. 2019年07月14日 05:06 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

VGA画像をXGA画像やHD画像に変換するdisp_dmar_axis_vga IP 2(性能が足りない)

VGA画像をXGA画像やHD画像に変換するdisp_dmar_axis_vga IP 1”の続き。

前回は、DisplayPort は接続するディスプレイによって、解像度の設定が違ってしまう。XGA のディスプレイだったらXGA 解像度の画像を出して、HD 解像度だったらHD 解像度の画像をLiveVideo に入力する必要がある。その加工はカメラ画像をDMA Write する側でやっていたのだが、今回のDMA Write はFixstars Tech Blog に書いてあるそのままを使用するので、DMA Read 側でいろいろな解像度の画像にすることにしよう。つまり、disp_dmar_axis IP を作り直そう。ということで、ソースコードやテストベンチのコードを貼った。
今回は、C シミュレーション、C コードの合成、をやってみよう。

Vivado 2018.2 でdisp_dmar_axis_vga プロジェクトを作成した。
disp_dmar_axis_vga_1_190712.png

最初に C シミュレーションを行った。
disp_dmar_axis_vga_2_190712.png

C シミュレーションの結果、dmar_result.bmp が生成された。予定通りの画像になっている。
disp_dmar_axis_vga_9_190713.jpg

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

Latency min が 480014 で、 SVGA の 800 x 600 = 480000 に比べて 12 クロックしか余計じゃないので、期待できそう?と思う。

disp_dmar_fb1 の合成結果を示す。
disp_dmar_axis_vga_4_190712.png

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

合成でのLatency min より、C/RTL 協調シミュレーションのLatency min の方がだいぶ増えてしまっている。

C/RTL 協調シミュレーション波形を見てみよう。全体波形から。
disp_dmar_axis_vga_6_190712.png

拡大してみよう。
disp_dmar_axis_vga_7_190712.png

outs に赤いところ(出力されていないところ)があるのが分かる。
m_axi_gmem_ARLENも 00 で単発転送であることが分かった。
これだとスループットが取れない。これではよろしくない。。。よって作り直そう。
  1. 2019年07月13日 22:00 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

VGA画像をXGA画像やHD画像に変換するdisp_dmar_axis_vga IP 1

Ultra96 MIPI拡張ボードに接続したPcam5C の画像をDisplayPort に表示する3(fpga.binファイルの生成)”で、fpga.bin の生成まで行っておいて何なのだが、disp_dmar_axis IP がこのままではまずいことに気がついた。
DisplayPort は接続するディスプレイによって、解像度の設定が違ってしまう。XGA のディスプレイだったらXGA 解像度の画像を出して、HD 解像度だったらHD 解像度の画像をLiveVideo に入力する必要がある。その加工はカメラ画像をDMA Write する側でやっていたのだが、今回のDMA Write はFixstars Tech Blog に書いてあるそのままを使用するので、DMA Read 側でいろいろな解像度の画像にすることにしよう。つまり、disp_dmar_axis IP を作り直そう。

今度のDMA Read IP の名前は、disp_dmar_axis_vga とした。
最初にソースコードを貼っておく。
disp_dmar_axis_vga.h を貼っておく。

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

#ifndef __DISP_DMAR_AXIS_VGA_H__
#define __DISP_DMAR_AXIS_VGA_H__

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

#define VGA_WIDTH 640
#define VGA_HEIGHT 480

#define SVGA_WIDTH 800
#define SVGA_HEIGHT 600

#define XGA_WIDTH 1024
#define XGA_HEIGHT 768

#define HD_WIDTH 1920
#define HD_HEIGHT 1080

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

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

#endif


disp_dmar_axis_vga.cpp を貼っておく。

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

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

#include "disp_dmar_axis_vga.h"

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

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

    AP_AXIU32 pix;
    int max_width, max_height;

    switch((int)resolution){
        case 0: // SVGA
            max_width = SVGA_WIDTH;
            max_height = SVGA_HEIGHT;
            break;
        case 1: // XGA
            max_width = XGA_WIDTH;
            max_height = XGA_HEIGHT;
            break;
        default: // HD
            max_width = HD_WIDTH;
            max_height = HD_HEIGHT;
            break;
    }

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

int disp_dmar_fb0(volatile ap_int<32> *fb0, AXI_STREAM &outs, int max_width, int max_height){
    AP_AXIU32 pix;
    int x_padding = (max_width - VGA_WIDTH)/2;
    int y_padding = (max_height - VGA_HEIGHT)/2;

    LOOP_Y0: for (int y=0; y<max_height; y++){
#pragma HLS LOOP_TRIPCOUNT min=600 max=1080 avg=768
        LOOP_X0: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            if (y < y_padding || y >= y_padding+VGA_HEIGHT)
                pix.data = 0;
            else if (x < x_padding || x >= x_padding+VGA_WIDTH)
                pix.data = 0;
            else
                pix.data = fb0[((y-y_padding)*VGA_WIDTH)+x-x_padding];

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

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

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

int disp_dmar_fb1(volatile ap_int<32> *fb1, AXI_STREAM &outs, int max_width, int max_height){
    AP_AXIU32 pix;
    int x_padding = (max_width - VGA_WIDTH)/2;
    int y_padding = (max_height - VGA_HEIGHT)/2;

    LOOP_Y1: for (int y=0; y<max_height; y++){
#pragma HLS LOOP_TRIPCOUNT min=600 max=1080 avg=768
        LOOP_X1: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            if (y < y_padding || y >= y_padding+VGA_HEIGHT)
                pix.data = 0;
            else if (x < x_padding || x >= x_padding+VGA_WIDTH)
                pix.data = 0;
            else
                pix.data = fb1[((y-y_padding)*VGA_WIDTH)+x-x_padding];

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

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

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

int disp_dmar_fb2(volatile ap_int<32> *fb2, AXI_STREAM &outs, int max_width, int max_height){
    AP_AXIU32 pix;
    int x_padding = (max_width - VGA_WIDTH)/2;
    int y_padding = (max_height - VGA_HEIGHT)/2;

    LOOP_Y2: for (int y=0; y<max_height; y++){
#pragma HLS LOOP_TRIPCOUNT min=600 max=1080 avg=768
        LOOP_X2: for (int x=0; x<max_width; x++){
#pragma HLS LOOP_TRIPCOUNT min=800 max=1920 avg=1024
#pragma HLS PIPELINE II=1
            if (y < y_padding || y >= y_padding+VGA_HEIGHT)
                pix.data = 0;
            else if (x < x_padding || x >= x_padding+VGA_WIDTH)
                pix.data = 0;
            else
                pix.data = fb2[((y-y_padding)*VGA_WIDTH)+x-x_padding];

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

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

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


disp_dmar_axis_vga_tb.cpp を貼っておく。

// disp_dmar_axis_tb.cpp
// 2019/02/01 by marsee
//

#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include "hls_opencv.h"

#include "disp_dmar_axis_vga.h"

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

#define NUM_FRAME_BUFFER 3
#define FB_NUMBER 0

int main(){
    using namespace cv;
    ap_uint<2> fb_number = FB_NUMBER;
    ap_uint<32> resolution = RESO_XGA;

    AXI_STREAM outs;
    AP_AXIU32 pix;
    ap_uint<32> *frame_buffer;
    
    // OpenCV で 画像を読み込む
    Mat src = imread("bmp_file0.bmp");

    // 画像をセーブするメモリをアロケート
    if ((frame_buffer =(ap_uint<32> *)malloc(NUM_FRAME_BUFFER * sizeof(ap_int<32>) * (src.cols * src.rows))) == NULL){
        fprintf(stderr, "Can't allocate frame_buffer0 ~ 2\n");
        exit(1);
    }
    
    // Mat フォーマットからフレームバッファに画像をロード
    Mat_<Vec3b> src_vec3b = Mat_<Vec3b>(src);
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        for(int y=0; y<src.rows; y++){
            for(int x=0; x<src.cols; x++){
                Vec3b pixel;
                pixel = src_vec3b(y,x);
                int rgb = ((int)(pixel(2))<<16) + ((int)(pixel(1))<<8) + pixel(0);
                frame_buffer[(src.cols*src.rows)*i + (src.cols*y) + x] = rgb;
            }
        }
    }
    
    disp_dmar_axis_vga((volatile ap_int<32> *)frame_buffer,
        (volatile ap_int<32> *)&frame_buffer[src.cols * src.rows],
        (volatile ap_int<32> *)&frame_buffer[2 * (src.cols * src.rows)],
        outs, resolution, fb_number);
        
    // AXI4 Stream から Mat フォーマットに変換しファイルに書き込み
    int row, col;
    switch(resolution){
        case RESO_SVGA:
            row = SVGA_HEIGHT; col = SVGA_WIDTH;
            break;
        case RESO_XGA:
            row = XGA_HEIGHT; col = XGA_WIDTH;
            break;
        default:
            row = HD_HEIGHT; col = HD_WIDTH;
            break;
    }

    Mat dst(row, col, CV_8UC3);
    Mat_<Vec3b> dst_vec3b = Mat_<Vec3b>(dst);
    for(int y=0; y<dst.rows; y++){
        for(int x=0; x<dst.cols; x++){
            outs >> pix;
            int rgb = pix.data;
            //int rgb = frame_buffer[(src.cols*y) + x];
            Vec3b pixel;
            pixel[0] = (rgb & 0xff); // blue
            pixel[1] = (rgb & 0xff00) >> 8; // green
            pixel[2] = (rgb & 0xff0000) >> 16; // red
            dst_vec3b(y,x) = pixel;
        }
    }

    imwrite("dmar_result.bmp", dst);
    
    free(frame_buffer);
    return(0);
}


bmp_file0.bmp を貼っておく。
disp_dmar_axis_vga_8_190712.png
  1. 2019年07月12日 04:53 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS勉強会用のラプラシアンフィルタ実習用回路の製作6(ZYBO Z7-20 編)

Vivado HLS勉強会用のラプラシアンフィルタ実習用回路の製作6(PYNQ編3)”の続き。

今年も筑波大学でやる予定のVivado HLS 勉強会の出し物として、 AXI4 Stream 版のラプラシアンフィルタの実習用回路を作成することにした。勉強会というかセミナでは、なかなか機材が揃わないのでカメラを用意することができない。よって、画像は、C のヘッダ・ファイルに変換した画像を使用してラプラシアンフィルタをかけてディスプレイに表示した。前回は、PYNQ の実機で動作を確認できた。今回は、ZYBO Z7-20 で同様に動作を確認する。

まずは、Vivado HLS 2017.4 で lapfilter_axis_zybo_z7_20 プロジェクトを作成した。
IP をコピーしてIP Catalog に登録し、ブロック・デザイン lapfilter_axis を作成して回路を構成した。
lapfilter_axis_zybo_36_190628.png

lapfilter_axis_zybo_37_190628.png

lapfilter_axis_zybo_38_190628.png

回路が出来上がったので、制約ファイル lapfilter_axis.xdc を追加した。

set_property IOSTANDARD TMDS_33 [get_ports TMDS_tx_0_B_p]
set_property IOSTANDARD TMDS_33 [get_ports TMDS_tx_1_R_p]
set_property IOSTANDARD TMDS_33 [get_ports TMDS_tx_2_G_p]
set_property IOSTANDARD TMDS_33 [get_ports TMDS_tx_clk_p]
set_property PACKAGE_PIN H16 [get_ports TMDS_tx_clk_p]
set_property PACKAGE_PIN D19 [get_ports TMDS_tx_0_B_p]
set_property PACKAGE_PIN C20 [get_ports TMDS_tx_1_R_p]
set_property PACKAGE_PIN B19 [get_ports TMDS_tx_2_G_p]

set_false_path -from [get_clocks clk_fpga_0] -to [get_clocks [get_clocks -of_objects [get_pins lapfilter_axis_i/bitmap_disp_cntrler_axi_master_0/inst/dvi_disp_i/BUFR_pixel_clk_io/O]]]

set_false_path -from [get_clocks [get_clocks -of_objects [get_pins lapfilter_axis_i/bitmap_disp_cntrler_axi_master_0/inst/dvi_disp_i/BUFR_pixel_clk_io/O]]] -to [get_clocks clk_fpga_0]


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

タイミング違反もなく、問題なさそうだ。
ハードウェアをエクスポートし、SDK を起動した。
lapfilter_axis アプリケーション・プロジェクトを作成し、lapfilter_axis.c と bmp_data.h をコピーした。
lapfilter_axis_zybo_41_190628.png

SDK からビットストリームをPYNQ にダウンロードして、アプリケーションソフトを起動するとTeraTerm に表示が出て、ディスプレイにも画像が出た。
lapfilter_axis_zybo_32_190626.png

lapfilter_axis_zybo_16_190623.jpg

TeraTerm 上で、 1 を入力すると、回路上で、AXI4 Stream が切り替わって、ラプラシアンフィルタの画像が表示された。
lapfilter_axis_zybo_17_190623.jpg

ZYBO Z7-20 でも成功した。
  1. 2019年06月28日 04:00 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS勉強会用のラプラシアンフィルタ実習用回路の製作6(PYNQ編3)

Vivado HLS勉強会用のラプラシアンフィルタ実習用回路の製作5(PYNQ編2)”の続き。

今年も筑波大学でやる予定のVivado HLS 勉強会の出し物として、 AXI4 Stream 版のラプラシアンフィルタの実習用回路を作成することにした。勉強会というかセミナでは、なかなか機材が揃わないのでカメラを用意することができない。よって、画像は、C のヘッダ・ファイルに変換した画像を使用してラプラシアンフィルタをかけてディスプレイに表示した。前回は、PYNQ でIP を使用して、ディスプレイに表示する回路を構成し、論理合成、インプリメンテーション、ビットストリームの生成を行った。今回は、SDK でアプリケーションソフトを作成し、PYNQ の実機での動作を確認してみよう。

Vivado 2017.4 でハードウェアをエクスポートし、SDK を起動した。
lapfiler_axis アプリケーション・プロジェクトを作成し、”Vivado HLS勉強会用のラプラシアンフィルタ実習用回路の製作3”で作成した lapfilter_axis.c を lapfilter_axis/src フォルダに追加した。更に、bmp_data.h の画像のヘッダ・ファイルも lapfilter_axis/src フォルダに追加した。
lapfilter_axis_zybo_31_190626.png

SDK からビットストリームをPYNQ にダウンロードして、アプリケーションソフトを起動するとTeraTerm に表示が出て、ディスプレイにも画像が出た。
lapfilter_axis_zybo_32_190626.png

lapfilter_axis_zybo_16_190623.jpg

TeraTerm 上で、 1 を入力すると、回路上で、AXI4 Stream が切り替わって、ラプラシアンフィルタの画像が表示された。
lapfilter_axis_zybo_17_190623.jpg

PYNQ でも成功した。
  1. 2019年06月27日 04:59 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0
»