FC2カウンター FPGAの部屋 Vivado HLS によるアンシャープマスクキング・フィルタの作製7(C++ の任意精度固定小数点型)

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

FPGAの部屋

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

Vivado HLS によるアンシャープマスクキング・フィルタの作製7(C++ の任意精度固定小数点型)

Vivado HLS によるアンシャープマスクキング・フィルタの作製4(固定小数点で実装してみた2)”の固定小数点は自分で小数点の位置を管理していた。

今回は、Vivado HLS に備わっている C++ の任意精度固定小数点型で書いてみようと思う。C++ の任意精度固定小数点型については、”Vivado Design Suite ユーザー ガイド 高位合成 UG902 (v2015.1) 2015 年 4 月 1 日”の550ページ ” C++ の任意精度固定小数点型”を参照のこと。

(2015/10/13:C++ソースコードを書き換えました)

小数点以下の数は前回同様にPRECISION で指定するので、整数部分を決定しようと思う。
num_adec_k(Kの小数点の位置)は、define で定義された NUM_ADEC_K に変更しようと思う。
NUM_ADEC_K が大きければ、小さい小数がありえて、例えばNUM_ADEC_K = 2 だと、0.25が最小値だが、k で割るところでは値が4倍になる。NUM_ADEC_K が8だったら、256倍になる可能性がある。(k は0以外)
よって、

iint x1y1 = (9<<(PRECISION+num_adec_k))/k + (8<<PRECISION);

は、

ap_ufixed<5+PRECISION+NUM_ADC_K, (5+PRECISION+NUM_ADC_K)-PRECISION, AP_RND, AP_SAT> x1y1 = 9/k_fixed + 8;

に変更した。

バグが出てデバックしていたが原因が分かった。今のところ、int型から固定小数点型への変換がうまく行っていないのと、整数リテラルと相対的に精度の低い固定小数点型の演算の結果を精度の高い固定小数点型に入れると桁落ちするようだ。

int unsharp_masking(int pix_mat[3][3], k_fixed_td k_fixed)
{
    ap_ufixed<88, AP_RND, AP_SAT> xy[3][3];
    int result=0;
    ap_ufixed<88, AP_RND, AP_SAT> z;
    x1y1_fixed_td x1y1, x1y1_2;
    ap_ufixed<5+PRECISION+NUM_ADC_K+8+3, (5+PRECISION+NUM_ADC_K+8+3)-PRECISION, AP_RND, AP_SAT> y;
    
    x1y1 = (x1y1_fixed_td)9/(x1y1_fixed_td)k_fixed + (x1y1_fixed_td)8// ビット長は 9+8 が2^5=32 より小さく、k_fixedで割るので、NUM_ADC_Kが増える可能性がある
    x1y1_2 = 9/k_fixed + 8;

    cout << "x1y1 = " << x1y1 << endl;
    cout << "x1y1_2 = " << x1y1_2 << endl;


一部抜粋だが、この結果は下に示すようになった。

Compiling ../../../unsharp_mask_axis_tb.cpp in debug mode
Compiling ../../../unsharp_mask_axis.cpp in debug mode
Generating csim.exe
x1y1 = 10.5625
x1y1_2 = 10
x1y1 = 10.5625
x1y1_2 = 10


ちなみに typedef を下に示す。

typedef ap_ufixed


int型から固定小数点型への変換がうまく行かない件は最初から固定小数点型をハードウェア化する関数に渡すように変更した。
つまり、ハードウェア化するとポートになるということだが、うまく4ビット幅の入力ポートになった。これで使える。
但し、ap_ufixed<8, 8, AP_RND, AP_SAT> z; とか小数部分が無い固定小数点型ならば int型から代入しても問題ないようだ。

ソースを貼っておこうと思う。
まずは unsharp_mask_axis.h から貼っておく。
// unsharp_mask_axis.h
// 2015/09/26 by marsee

#ifndef __UNSHARP_MASK_AXIS_H_
#define __UNSHARP_MASK_AXIS_H_

//#define HORIZONTAL_PIXEL_WIDTH    1280
//#define VERTICAL_PIXEL_WIDTH    720

#define HORIZONTAL_PIXEL_WIDTH    64
#define VERTICAL_PIXEL_WIDTH    48

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define PRECISION    6    // 小数点以下の桁数、精度(0 以上の数を指定する)
#define K_BITLEN    4    // k のビット長
#define NUM_ADC_K    2    // k の小数点の位置

typedef ap_ufixed<K_BITLEN, K_BITLEN-NUM_ADC_K> k_fixed_td;
typedef ap_fixed<6+PRECISION+NUM_ADC_K, (6+PRECISION+NUM_ADC_K)-PRECISION, AP_RND, AP_SAT> x1y1_fixed_td;
typedef ap_fixed<6+PRECISION+NUM_ADC_K+8+3, (6+PRECISION+NUM_ADC_K+8+3)-PRECISION, AP_RND, AP_SAT> y_fixed_td;

#endif


次に、 unsharp_mask_axis.cpp を貼っておく。

// unsharp_mask_axis.cpp
// 2015/09/24 by marsee
// ap_fixedバージョン 2015/10/04
//

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

#include "unsharp_mask_axis.h"

using namespace std;

int unsharp_masking(int pix_mat[3][3], k_fixed_td k_fixed);

int unsharp_mask_axis(ap_uint<1> usm_fil_enable, k_fixed_td k_fixed, hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs){
#pragma HLS INTERFACE ap_none port=usm_fil_enable
#pragma HLS INTERFACE ap_none port=k_fixed
#pragma HLS INTERFACE axis port=ins
#pragma HLS INTERFACE axis port=outs
#pragma HLS INTERFACE ap_ctrl_none port=return

    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> usm;

    int line_buf[2][HORIZONTAL_PIXEL_WIDTH];
#pragma HLS array_partition variable=line_buf block factor=2 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    int pix_mat[3][3];
#pragma HLS array_partition variable=pix_mat complete

    int usm_fil_val;

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

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

            for (int i=0; i<3; i++){
                for (int j=0; j<2; j++){
#pragma HLS UNROLL
                    pix_mat[i][j] = pix_mat[i][j+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            pix_mat[2][2] = pix.data;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = pix.data;

            usm.data = unsharp_masking(pix_mat, k_fixed);

            if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので元のデータとする
                usm.data = pix.data;

            if (x==0 && y==0// 最初のデータでは、TUSERをアサートする
                usm.user = 1;
            else
                usm.user = 0;

            if (x == (HORIZONTAL_PIXEL_WIDTH-1))    // 行の最後で TLAST をアサートする
                usm.last = 1;
            else
                usm.last = 0;

            if (usm_fil_enable)
                outs << usm;    // AXI4-Stream へ出力
            else
                outs << pix;    // 入力画像をそのまま出力
        }
    }

    return 0;
}

// アンシャープマスキング・フィルタ
// x0y0 x1y0 x2y0 -k   -j  -k
// x0y1 x1y1 x2y1 -k  9+8k -k x 1/9
// x0y2 x1y2 x2y2 -k   -k  -k
//
// k : 鮮鋭化の強さ(固定小数点) , k != 0
// num_adec_k : Kの小数点の位置
//

int unsharp_masking(int pix_mat[3][3], k_fixed_td k_fixed)
{
    ap_ufixed<88, AP_RND, AP_SAT> xy[3][3];
    int result=0;
    ap_ufixed<88, AP_RND, AP_SAT> z;
    x1y1_fixed_td x1y1;
    y_fixed_td y;
    /*ap_ufixed<8, 8> xy[3][3];    int result=0;    ap_ufixed<8, 8, AP_RND, AP_SAT> z;    x1y1_fixed_td x1y1;    y_fixed_td y;*/
    
    x1y1 = (x1y1_fixed_td)9/(x1y1_fixed_td)k_fixed + (x1y1_fixed_td)8// ビット長は 9+8 が2^5=32 より小さく、k_fixedで割るので、NUM_ADC_Kが増える可能性がある

    for (int i=0; i<=16; i += 8){
        for (int j=0; j<3; j++){
            for (int k=0; k<3; k++){
                xy[j][k] = (pix_mat[j][k] >> i) & 0xff; // RGBのいずれかを抽出
            }
        }

        y = -xy[0][0]    -xy[0][1]        -xy[0][2]
            -xy[1][0]    +x1y1*xy[1][1]    -xy[1][2]
            -xy[2][0]    -xy[2][1]        -xy[2][2];

        y = (k_fixed * y)/(y_fixed_td)9;

        y = y+(y_fixed_td)0.5// 四捨五入
        if (y < 0)
            z = 0;
        else if (y > 255)
            z = 255;
        else
            z = y;

        result += z.to_int()<<i; // i=0 : blue, i=8 : green, i=16 : red
    }
    return(result);
}


最後に、unsharp_mask_axis_tb.cpp を貼っておく。

// unsharp_mask_axis_tb.cpp
// 2015/09/26 by marsee
//

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

#include "bmp_header.h"
#include "unsharp_mask_axis.h"

int unsharp_mask_axis(ap_uint<1> usm_fil_enable, k_fixed_td k_fixed, hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs);

int unsharp_masking_soft(int pix_mat[3][3], int k, int num_adec_k);
int unsharp_mask_axis_soft(ap_uint<1> usm_fil_enable, ap_uint<4> usm_fil_k, hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int width, int height);

#define CLOCK_PERIOD 10

#define K 2.5 // 鮮鋭化の強さ
#define NUM_ADEC_K 2 // Kの小数点の位置

int main()
{
    using namespace std;

    hls::stream<ap_axis<32,1,1,1> > ins;
    hls::stream<ap_axis<32,1,1,1> > ins_soft;
    hls::stream<ap_axis<32,1,1,1> > outs;
    hls::stream<ap_axis<32,1,1,1> > outs_soft;
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> vals;
    ap_axis<32,1,1,1> vals_soft;

    int m_seq = 1// M系列の値
    int i;
    int xor_shift;

    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *hw_usmd;
    int blue, green, red;

    k_fixed_td k_fixed;

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

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

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

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

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

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

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

            ins << pix;
            ins_soft << pix;
        }
    }

    int usm_k = (int)(K * pow(2.0, (double)NUM_ADEC_K));
    k_fixed = K;
    unsharp_mask_axis(1, k_fixed, ins, outs);
    unsharp_mask_axis_soft(1, usm_k, ins_soft, outs_soft, bmpihr.biWidth, bmpihr.biHeight); // k = 2;

    // ハードウェアとソフトウェアのラプラシアン・フィルタの値のチェック
    cout << endl;
    cout << "outs" << endl;
    for(int j=0; j < bmpihr.biHeight; j++){
        for(i=0; i < bmpihr.biWidth; i++){
            outs >> vals;
            outs_soft >> vals_soft;
            ap_int<32> val = vals.data;
            ap_int<32> val_soft = vals_soft.data;

            hw_usmd[(j*bmpihr.biWidth)+i] = (int)val;

            if (val != val_soft){
                printf("ERROR HW and SW results mismatch i = %ld, j = %ld, HW = %d, SW = %d\n", i, j, (int)val, (int)val_soft);
                //return(1);
            }
            if (vals.last)
                cout << "AXI-Stream is end" << endl;
        }
    }
    cout << "Success HW and SW results match" << endl;
    cout << endl;

    // ハードウェアのラプラシアンフィルタの結果を temp_usm.bmp へ出力する
    if ((fbmpw=fopen("temp_usm.bmp""wb")) == NULL){
        fprintf(stderr, "Can't open temp_usm.bmp by binary write mode\n");
        exit(1);
    }
    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(char), 2, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(long), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(short), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(long), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

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

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    free(rd_bmp);
    free(hw_usmd);

    return 0;
}

int unsharp_mask_axis_soft(ap_uint<1> usm_fil_enable, ap_uint<4> usm_fil_k, hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int width, int height){
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> usm;
    int **line_buf;
    int pix_mat[3][3];
    int usm_fil_val;
    int i;

    // line_buf の1次元目の配列をアロケートする
    if ((line_buf =(int **)malloc(sizeof(int *) * 2)) == NULL){
        fprintf(stderr, "Can't allocate line_buf[3][]\n");
        exit(1);
    }

    // メモリをアロケートする
    for (i=0; i<2; i++){
        if ((line_buf[i]=(int *)malloc(sizeof(int) * width)) == NULL){
            fprintf(stderr, "Can't allocate line_buf[%d]\n", i);
            exit(1);
        }
    }

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

    for (int y=0; y<height; y++){
        for (int x=0; x<width; x++){
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix; // AXI4-Stream からの入力

            for (int k=0; k<3; k++){
                for (int m=0; m<2; m++){
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            pix_mat[2][2] = pix.data;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = pix.data;

            usm.data = unsharp_masking_soft(pix_mat, (int)usm_fil_k, 2);

            if (x<2 || y<2// 最初の2行とその他の行の最初の2列は無効データなので元のデータとする
                usm.data = pix.data;

            if (x==0 && y==0// 最初のデータでは、TUSERをアサートする
                usm.user = 1;
            else
                usm.user = 0;

            if (x == (HORIZONTAL_PIXEL_WIDTH-1))    // 行の最後で TLAST をアサートする
                usm.last = 1;
            else
                usm.last = 0;

            if (usm_fil_enable)
                outs << usm;    // AXI4-Stream へ出力
            else
                outs << pix;    // 入力画像をそのまま出力
        }
    }

    for (i=0; i<2; i++)
        free(line_buf[i]);
    free(line_buf);

    return 0;
}

// アンシャープマスキング・フィルタ
// x0y0 x1y0 x2y0 -k   -j  -k
// x0y1 x1y1 x2y1 -k  9+8k -k x 1/9
// x0y2 x1y2 x2y2 -k   -k  -k
//
// k : 鮮鋭化の強さ(固定小数点)
// num_adec_k : Kの小数点の位置
//
int unsharp_masking_soft(int pix_mat[3][3], int k, int num_adec_k)
{
    float y;
    int xy[3][3];
    float rgb[3];
    int result=0;

    float fk = (float)k;
    for (int m=0; m<num_adec_k; m++){ // 小数点の位置を正しい位置にする
        fk /= 2.0;
    }

    for (int i=0; i<=16; i += 8){
        for (int j=0; j<3; j++){
            for (int k=0; k<3; k++){
                xy[j][k] = (pix_mat[j][k] >> i) & 0xff; // RGBのいずれかを抽出
            }
        }
        y = -(float)xy[0][0]         -(float)xy[0][1]      -(float)xy[0][2]
            -(float)xy[1][0] +(9.0/fk+8.0)*(float)xy[1][1] -(float)xy[1][2]
            -(float)xy[2][0]         -(float)xy[2][1]      -(float)xy[2][2];
        y = (fk * y)/9.0;

        int z = (int)(y + 0.5); // 四捨五入

        if (z<0// 飽和演算
            z = 0;
        else if (z>255)
            z = 255;

        result += z<<i; // i=0 : blue, i=8 : green, i=16 : red
    }

    return(result);
}


  1. 2015年10月04日 08:04 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック URL
http://marsee101.blog19.fc2.com/tb.php/3267-a4f498dd
この記事にトラックバックする(FC2ブログユーザー)