FC2カウンター FPGAの部屋 2019年01月
FC2ブログ

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

FPGAの部屋

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

” ARRAY_PARTITION COMPLETE has exceeded the threshold (1024)”の解決策

ニューラルネットワークの重みをBRAM じゃなくてLUT にマップしたいというときがありますが、そんな時に1024 以上の配列を array_partition complete するとザイリンクス社のフォーラムの”ARRAY_PARTITION COMPLETE has exceeded the threshold (1024)”の様に、エラーが出てしまいます。

そんな時は、 config_array_partition の maximum_size が 1024 になっているので、これを配列の数に修正すると合成が通ります。ただし、長い時間がかかることを覚悟してください。私はC コードの合成に2日半かかりました。

さて、やり方ですが、Vivado HLS のSolution -> Solution Settings... を選択して、Solution Settings ダイアログを表示します。
config_array_partition_1_190120.png

Command: に config_array_partition を選択して、maximum_size に配列の要素数を入れるとエラーが解消されます。
config_array_partition_3_190120.png

その他、いろいろなオプションを設定できるので、ご覧ください。
  1. 2019年01月30日 23:44 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

カメラ画像をDisplayPortに出力する3(vflip_dma_write2 の2)

カメラ画像をDisplayPortに出力する2(vflip_dma_write2 の1)”の続き。

前回は、vflip_dma_write2 IP のVivado HLS 2018.3 プロジェクトを作成して、ソースコードを貼った。今回は、vflip_dma_write2 プロジェクトで、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行う。

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

XGA 解像度の xga_result0.bmp, xga_result1.bmp, xga_result2.bmp と HD 解像度の hd_result0.bmp, hd_result1.bmp, hd_result2.bmp が生成された。
cam_displayport_8_190130.png

XGA 解像度の xga_result0.bmp を示す。黒枠の真ん中に画像が表示されている。
cam_displayport_9_190130.jpg

HD 解像度の hd_result0.bmp を示す。こちらも同様に黒枠の真ん中に画像が表示されている。
cam_displayport_10_190130.jpg

元画像は、上下が反転した画像となっている。
cam_displayport_11_190130.jpg

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

ピクセル/クロックはほぼ 1 となっている。3 個のDMA エンジンが入っている形なので、リソース使用量が多いな。。。

C/RTL 協調シミュレーションを行ったが、失敗した。
cam_displayport_6_190130.png

Export RTL を行った。
cam_displayport_7_190130.png

LUT + SRL は 2650 個で合成時のレポートの 1/2 以下に抑えられている。
CP achieved post-implementation は 3.686 ns で要求の5 ns を満たしている。
  1. 2019年01月30日 04:31 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

カメラ画像をDisplayPortに出力する2(vflip_dma_write2 の1)

カメラ画像をDisplayPortに出力する1(構想編)”の続き。

前回は、カメラ画像をUltra96 のDisplayPort へ出力するための回路の構想を練った。今回は、その内のvflip_dma_write2 について書いてみよう。
以前作成した vflip_dma_write はSVGA 解像度の垂直のみが反転したカメラ画像を正常に表示できるようにDMA する回路だった。今回のvflip_dma_write2 はXGA解像度やHD解像度の真ん中にSVGA のカメラ画像を表示するDMA である。

vflip_dma_write2.h を示す。

// vflip_dma_write2.h
// 2019/01/28 by marsee
//

#ifndef __VFLIP_DMA_WRITE2_H__
#define __VFLIP_DMA_WRITE2_H__

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

#define SVGA_WIDTH 800
#define SVGA_HEIGHT 600
#define SVGA_START_OFFSET 0
#define XGA_WIDTH 1024
#define XGA_HEIGHT 768
#define XGA_START_OFFSET (((XGA_HEIGHT-SVGA_HEIGHT)/2)*XGA_WIDTH+(XGA_WIDTH-SVGA_WIDTH)/2)
#define XGA_STRIDE (XGA_WIDHT-SVGA_WIDTH)
#define HD_WIDTH 1920
#define HD_HEIGHT 1080
#define HD_START_OFFSET (((HD_HEIGHT-SVGA_HEIGHT)/2)*HD_WIDTH+(HD_WIDTH-SVGA_WIDTH)/2)
#define HD_STRIDE (HD_WIDHT-SVGA_WIDTH)

#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


vflip_dma_write2.cpp を示す。

// vflip_dma_write2.cpp
// 2019/01/28 by marsee
//

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

#include "vflip_dma_write2.h"

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

    AP_AXIU32 pix;
    int max_fb_chk;
    int max_width, max_height;
    int start_offset;
    int stride;

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

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

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

            fb0[start_offset + (y*max_width)+x] = pix.data;
        }
    }

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

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

            fb1[start_offset + (y*max_width)+x] = pix.data;
        }
    }

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

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

            fb2[start_offset + (y*max_width)+x] = pix.data;
        }
    }
end:
    return(0);
}


vflip_dma_write2_tb.cpp を示す。

// vflip_dma_write2_tb.cpp
// 2019/01/28 by marsee
//

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

#include "vflip_dma_write2.h"

int vflip_dma_write2(AXI_STREAM & ins,
    volatile ap_int<32> *fb0, volatile ap_int<32> *fb1, volatile ap_int<32> *fb2,
    ap_uint<32> &resolution, volatile ap_uint<2> &active_frame);
    
int vflip_dmaw(AXI_STREAM &ins, int width, int height, ap_uint<32> resolution,
    char *output_file0, char *output_file1, char *output_file2);

#define NUM_FRAME_BUFFER 3

int main()
{
    using namespace cv;

    AXI_STREAM ins0, ins1;
    AP_AXIU32 pix;
    ap_uint<32> resolution;

    // OpenCV で 画像を読み込む
    Mat src = imread("bmp_file0_vflip.bmp");
    AXI_STREAM src_axi, dst_axi;

    // Mat フォーマットから AXI4 Stream へ変換、3画面分
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        cvMat2AXIvideo(src, src_axi);
        for (int y=0; y<SVGA_HEIGHT; y++){
            for (int x=0; x<SVGA_WIDTH; x++){
                src_axi >> pix;
                ins0 << pix;
                ins1 << pix;
            }
        }
    }
    char output_file0[] = "xga_result0.bmp";
    char output_file1[] = "xga_result1.bmp";
    char output_file2[] = "xga_result2.bmp";
    resolution = RESO_XGA;
    vflip_dmaw(ins0, XGA_WIDTH, XGA_HEIGHT, resolution, output_file0, output_file1, output_file2);
    
    char output_file3[] = "hd_result0.bmp";
    char output_file4[] = "hd_result1.bmp";
    char output_file5[] = "hd_result2.bmp";
    resolution = RESO_HD;
    vflip_dmaw(ins1, HD_WIDTH, HD_HEIGHT, resolution, output_file3, output_file4, output_file5);

    return(0);
}

int vflip_dmaw(AXI_STREAM &ins, int width, int height, ap_uint<32> resolution,
    char *output_file0, char *output_file1, char *output_file2){
    
    using namespace cv;

    ap_uint<2> active_frame;
    ap_uint<32> *frame_buffer;

    // frame buffer をアロケートする、3倍の領域を取ってそれを3つに分ける
    if ((frame_buffer =(ap_uint<32> *)malloc(NUM_FRAME_BUFFER * sizeof(ap_int<32>) * (width * height))) == NULL){
        fprintf(stderr, "Can't allocate frame_buffer0 ~ 2\n");
        exit(1);
    }
    for(int i=0; i<NUM_FRAME_BUFFER; i++){ // 0 clear
        for (int y=0; y<height; y++){
            for (int x=0; x<width; x++){
                frame_buffer[i*(width * height)+y*width+x] = 0;
            }
        }
    }

    vflip_dma_write2(ins, (volatile ap_int<32> *)frame_buffer,
        (volatile ap_int<32> *)&frame_buffer[width * height],
        (volatile ap_int<32> *)&frame_buffer[2 * (width * height)],
        resolution, active_frame);

    // AXI4 Stream から Mat フォーマットへ変換
    // dst は宣言時にサイズとカラー・フォーマットを定義する必要がある
    Mat dst[3];
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        dst[i] = Mat(height, width, CV_8UC3);
    }

    // dst[i] にframe_bufferから画像データをロード
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        Mat_<Vec3b> dst_vec3b = Mat_<Vec3b>(dst[i]);
        for(int y=0; y<dst[i].rows; y++){
            for(int x=0; x<dst[i].cols; x++){
                Vec3b pixel;
                int rgb = frame_buffer[i*(width * height)+y*width+x];
                pixel[0] = (rgb & 0xff); // blue
                pixel[1] = (rgb & 0xff00) >> 8; // green
                pixel[2] = (rgb & 0xff0000) >> 16; // red
                dst_vec3b(y,x) = pixel;
            }
        }
    }

    // DMAされたデータをBMPフィルに書き込む
    char output_file[100];
    for (int i=0; i<NUM_FRAME_BUFFER; i++){
        switch (i){
            case 0:
                strcpy(output_file, output_file0);
                break;
            case 1:
                strcpy(output_file, output_file1);
                break;
            case 2:
                strcpy(output_file, output_file2);
                break;
        }
        // Mat フォーマットからファイルに書き込み
        imwrite(output_file, dst[i]);
    }

    free(frame_buffer);
    
    return(0);
}


Vivado HLS 2018.3 で vflip_dma_write2 プロジェクトを作成した。
cam_displayport_2_190130.png
  1. 2019年01月30日 03:42 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

カメラ画像をDisplayPortに出力する1(構想編)

Ultra96のDisplayPortを使用するためのプロジェクトを作成する5(HD解像度のテストパターン)”で、Ultra96 のDisplayPort にテストパターンを出力することができた。今回は、カメラ画像をUltra96 のDisplayPort に出力してみたい。

カメラ画像をUltra96 のDisplayPort に出力するに当たって、とりあえずOV5642 で成功している画像のサイズはSVGA の 800 x 600 ピクセルだ。
カメラの表示について3つの方法を考えた。

1. SVGA解像度のカメラ画像を黒枠付けて異なる解像度に表示
2. SVGA解像度のカメラ画像を拡大して異なる解像度に表示
3. カメラ画像をカメラのI2C設定で設定を変更し、その解像度で出力


とりあえずは一番簡単な「1. SVGA解像度のカメラ画像を黒枠付けて異なる解像度に表示」で行ってみようかと思う。ディスプレイの解像度としては、XGA とHD 解像度をサポートすることにする。
回路のブロック図としてはこんな感じになると思う。
cam_displayport_1_190128.png

OV5642 カメラからカメラ・インターフェースの mt9d111_inf_axis を通って、AXI4-Stream で画像データを vflip_dma_write2 に渡す。vflip_dma_write2 は黒枠部分をストライドとしてDDR SDRAM にDMA 転送する。
DDR SDRAM からは、disp_dmar_axis でDMA 転送を行い、AXI4-Stream でaxis2video_out にデータを渡して video_out でPS のDisplayPort に入力する。

vflip_dma_write2 IP と disp_dma_aixs IP を作る必要がある。
  1. 2019年01月28日 04:53 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:2

Ultra96のDisplayPortを使用するためのプロジェクトを作成する5(HD解像度のテストパターン)

Ultra96のDisplayPortを使用するためのプロジェクトを作成する4(完成)”の続き。

前回は、デバック結果を元にVerilog HDL ソースコードを修正して、テストパターンの表示を行って成功した。だが、前回の回路では、XGA解像度では、テストパターンを表示できても、HD解像度では、パターン生成回路やAXI4-Stream の動作周波数が 100 MHz で、HD解像度の 148.5 MHz で動作する回路のスループットを満たせずにテストパターンが表示できなかった。今回は、HD解像度のテストパターンを表示してする。

まずは、Zynq UltraSclae+ MPSoC のpl_clk0 のクロック周波数を 200 MHz 程度にしたいということで、設定しようとしたが、 AXI4-Stream のクロック周波数が 100 MHz でだめと言われてpl_clk0 のクロック周波数を 200 MHz 程度にできなかったので、axis2video_out IP のIP Package をやり直すことにした。
Package IP - axi22video_out のウインドウのPorts and Interface で、axis を右クリックし、右クリックメニューからAssciate Clock... を選択する。
DisplayPort_test_117_19027.png

Assciate Clocks ダイアログで、axi_clk にチェックを入れる。
DisplayPort_test_122_19027.png

これで、pl_clk0 のクロック周波数を 200 MHz 程度に設定できるようになったので、Zynq UltraSclae+ MPSoC をダブルクリックして、ダイアログを表示する。
DisplayPort_test_115_19027.png

Page Navigator のClock Configuration をクリックして、Output Clocks -> Low Power Domain Clocks -> PL Fabric Clocks のPL0 のRequested Freq(MHz) を220 に設定した。Actial Frequency (MHz) は 214.285721 MHz となった。
DisplayPort_test_116_19027.png

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

なお、pl_clk0 と disp_clk はFalse Path を設定してある。
displayport_test.xdc を示す。

create_clock -period 6.734 -name disp_clk -waveform {0.000 3.367} [get_pins displayport_test_i/zynq_ultra_ps_e_0/dp_video_ref_clk]

#set_property PACKAGE_PIN D7 [get_ports {red[0]}]
#set_property PACKAGE_PIN F8 [get_ports {red[1]}]
#set_property PACKAGE_PIN F7 [get_ports {red[2]}]
#set_property PACKAGE_PIN G7 [get_ports {red[3]}]
#set_property PACKAGE_PIN F6 [get_ports {blue[0]}]
#set_property PACKAGE_PIN G5 [get_ports {blue[1]}]
#set_property PACKAGE_PIN A6 [get_ports {blue[2]}]
#set_property PACKAGE_PIN A7 [get_ports {blue[3]}]
#set_property PACKAGE_PIN G6 [get_ports {green[0]}]
#set_property PACKAGE_PIN E6 [get_ports {green[1]}]
#set_property PACKAGE_PIN E5 [get_ports {green[2]}]
#set_property PACKAGE_PIN D6 [get_ports {green[3]}]
#set_property PACKAGE_PIN D5 [get_ports {hsyncx[0]}]
#set_property PACKAGE_PIN C7 [get_ports {vsyncx[0]}]

#set_property IOSTANDARD LVCMOS18 [get_ports {blue[3]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {blue[2]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {blue[1]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {blue[0]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {hsyncx[0]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {vsyncx[0]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {green[3]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {green[2]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {green[1]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {green[0]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {red[3]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {red[2]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {red[1]}]
#set_property IOSTANDARD LVCMOS18 [get_ports {red[0]}]

set_false_path -from [get_clocks clk_pl_0] -to [get_clocks disp_clk]
set_false_path -from [get_clocks disp_clk] -to [get_clocks clk_pl_0]


ハードウェアをエクスポートして、SDK を立ち上げた。そうするとHDF ファイルを展開してdisplayport_test_wrapper_hw_platform_0 ディスプレイを作成してくれるので、ビットファイルがその下に生成されている。
そのビットファイルからbin ファイルを生成し、Ultra96 にSFTP する。

Ultra96 のDebian では、クロック周波数が変更になるので、fclk0-zynqmp.dts を変更する必要がある。(これを忘れていて1日潰しました。。。)
fclk0-zynqmp.dts のクロック周波数を 200 MHz に変更した。

/dts-v1/;/plugin/;
/ {
    fragment@0 {
        target-path = "/amba";
        __overlay__ {
            fclk0 {
                compatible    = "ikwzm,fclkcfg-0.10.a";
                clocks        = <&clk 0x47>;
                insert-rate   = "200000000";
                insert-enable = <1>;
                remove-rate   = "1000000";
                remove-enable = <0>;
            };
        };
    };
};


dts ファイルを ./dtc_script.sh でコンパイルした。
./dtc_script.sh を示す。

#!/bin/bash

dtc -I dts -O dtb -o fpga-load.dtb fpga-load.dts
dtc -I dts -O dtb -o fclk0-zynqmp.dtb fclk0-zynqmp.dts
dtc -I dts -O dtb -o pattern_gen_axis.dtb pattern_gen_axis.dts


./lddtovray.sh を起動して、デバイスツリーをロードした。
DisplayPort_test_112_19027.png

その時のシリアルポートのレポートを示す。
DisplayPort_test_113_19027.png

クロック周波数が 214.285713 MHz になっているのが分かる。

./pattern_gen_axis2 を起動して、HD解像度の 1920 x 1080 ピクセルに設定した。
./disp_pattern.sh を起動した。
DisplayPort_test_114_19027.png

HD解像度のテストパターンが表示された。
DisplayPort_test_121_19027.jpg

2019/02/07 追記: テスト・パターンの色が違っています。詳しくは、”Zynq UltraScale+ MPSoC のDisplayPort のLiveVideo のピクセルデータ”を参照ください)

Vivado Analyzer の波形を貼っておく。
まずは、disp_clk の 148.5 MHz でキャプチャしたディスプレイの同期信号やDE 信号を示す。
DisplayPort_test_119_19027.png

DE 信号の 1 の間隔は 1920 クロックだった。

次に、axi_clk の 214.285713 MHz でキャプチャした波形を示す。
DisplayPort_test_120_19027.png

DE 信号の 1 の間隔は 2770 クロックだった。
計算をしてみよう。
1920 × (214.285713÷148.5) = 2770.5628 クロックということで、計測値とだいたい合っている。

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

// pattern_gen_axis.c
// 2019/01/18 by marsee
//

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

int main(){
    int fd1;
    volatile unsigned *pga;
    int i, val;

    // uio initialize (uio1)
    fd1 = open("/dev/uio1", O_RDWR|O_SYNC); // pattern_gen_axis IP
    if (fd1 < 1){
        fprintf(stderr, "/dev/uio1 (pattern_gen_axis) open error\n");
        exit(1);
    }
    pga = (volatile unsigned *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0);
    if (!pga){
        fprintf(stderr, "pattern_gen_axis mmap error\n");
        exit(1);
    }
    
    pga[8] = 0x1; // init_done = 1

    while(1){
        printf("h_size = ");
        scanf("%d", &val);
        if(val == 9999)
            break;
        pga[6] = val; // h_size
        
        printf("v_size = ");
        scanf("%d", &val);
        pga[4] = val;
        printf("v_size = %d, h_size = %d\n\n", pga[4], pga[6]);
    }
    
    munmap((void *)pga, 0x10000);
    close(fd1);
    
    return(0);
}

  1. 2019年01月27日 05:47 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのプロジェクトを作成する4(完成)

Ultra96のDisplayPortを使用するためのプロジェクトを作成する3(デバック)”の続き。

前回は、Ultra96 のDisplayPort でテストパターンがうまく表示できなかったので、Vivado Analyzer を使ってデバックを行った。今回は、デバック結果を元にVerilog HDL ソースコードを修正して、テストパターンの表示を行ってみよう。

前回のVivado Analyzer によるデバックでは、ip_done が来た後でも、1クロック分 ip_start が余計に 1 にアサートされているのが問題だった。そのVerilog HDL ソースコードを”Ultra96のDisplayPortを使用するためのIPを作成する7(テストベンチ の作成3)"のaxis2video_out.v から拾ってみよう。

    // IP start State Machine
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            cs_start <= IDLE_START;
            ip_start <= 1'b0;
        end else begin
            case (cs_start)
                IDLE_START : begin
                    ip_start <= 1'b0;
                    if (vsync_falling_edge_axi)
                        cs_start <= IP_START_1;
                end
                IP_START_1 : begin
                    ip_start <= 1'b1;
                    if (ip_done)
                        cs_start <= IDLE_START;
                end
            endcase
        end
    end


IP start State Machine で ip_start を出力しているが、ムーア型のステートマシンで、ステートにip_start の値を書いてしまっている。この書き方だとステートに遷移してからip_start の値を出力するので、1クロック遅れてしまう。そこで、以下のようにIP start State Machine を書き換えた。

    // IP start State Machine
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            cs_start <= IDLE_START;
            ip_start <= 1'b0;
        end else begin
            case (cs_start)
                IDLE_START : begin
                    ip_start <= 1'b0;
                    if (vsync_falling_edge_axi) begin
                        cs_start <= IP_START_1;
                        ip_start <= 1'b1;
                    end
                end
                IP_START_1 : begin
                    if (ip_done) begin
                        cs_start <= IDLE_START;
                        ip_start <= 1'b0;
                    end
                end
            endcase
        end
    end


上のソースコードのように書くと、ステートの遷移と同時にip_start の値も変化する。
axi2video_out.v の全体を貼っておく。
(2019/02/07 修正:LiveVideo のビット・フィールドをBRG に変更)

// axis2video_out.v
// 2019/01/14 by marsee
// 2019/01/23 : Fixed IP start state machine bug
//

`default_nettype none

module axis2video_out
    (
        // Clock and Reset
        input wire  disp_clk,
        input wire  axi_clk,
        input wire  axi_rst_n,
        input wire  init_done,
        
        // AXI4-Stream
        input wire  [31:0] axis_tdata,
        input wire  axis_tvalid,
        output wire axis_tready,
        input wire  [3:0] axis_tkeep,
        input wire  [3:0] axis_tstrb,
        input wire  axis_tuser,
        input wire  axis_tlast,
        input wire  axis_tid,
        input wire  axis_tdest,
        
        // IP
        output reg  ip_start,
        input wire  ip_done,
        
        // video in
        input wire  de_in,
        input wire  vsync_in,
        input wire  hsync_in,
        
        // video_out
        output wire  [35:0] disp_pixel,
        output wire  de_out,
        output wire vsync_out,
        output wire  hsync_out
    );

    parameter       IDLE_START =    1'b0,
                    IP_START_1 =    1'b1;
    
    reg     reset_disp_2b = 1'b1, reset_disp_1b = 1'b1;
    wire    reset_disp;
    reg     fifo_reset_axi_2b = 1'b0, fifo_reset_axi_1b = 1'b0;
    wire    fifo_reset_axi;
    reg     fifo_reset_disp_2b = 1'b0, fifo_reset_disp_1b = 1'b0;
    wire    fifo_reset_disp;
    reg     de_1d, vsync_1d, hsync_1d;
    reg     vsync_axi_1b, vsync_axi_2b;
    wire    vsync_axi;
    reg     vsync_axi_1d, vsync_axi_2d;
    reg     cs_start;
    wire    pfifo_empty, pfifo_full;
    wire [33:0] pfifo_dout;
    reg    vsync_rising_edge_axi, vsync_falling_edge_axi;
    
    always @(posedge disp_clk) begin
        if(reset_disp) begin
            de_1d <= 1'b0;
            vsync_1d <= 1'b0;
            hsync_1d <= 1'b0;
        end else begin
            de_1d <= de_in;
            vsync_1d <= vsync_in;
            hsync_1d <= hsync_in;
        end
    end
    
    // reset signals    
    always @(posedge axi_clk) begin
        fifo_reset_axi_2b <= ~init_done | ~axi_rst_n | vsync_1d;
        fifo_reset_axi_1b <= fifo_reset_axi_2b;
    end
    assign fifo_reset_axi = fifo_reset_axi_1b;
        
    always @(posedge disp_clk) begin
        fifo_reset_disp_2b <= ~init_done | ~axi_rst_n | vsync_1d;
        fifo_reset_disp_1b <= fifo_reset_disp_2b;
    end
    assign fifo_reset_disp = fifo_reset_disp_1b;
        
    always @(posedge disp_clk) begin
        reset_disp_2b <= ~init_done | ~axi_rst_n;
        reset_disp_1b <= reset_disp_2b;
    end
    assign reset_disp = reset_disp_1b;
    
    // vsync_rising_edge, vsync_falling_edge
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_axi_2b <= 1'b1;
            vsync_axi_1b <= 1'b1;
        end else begin
            vsync_axi_2b <= vsync_1d;
            vsync_axi_1b <= vsync_axi_2b;
        end
    end
    assign vsync_axi = vsync_axi_1b;
    
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_axi_1d <= 1'b1;
            vsync_axi_2d <= 1'b1;
        end else begin
            vsync_axi_1d <= vsync_axi;
            vsync_axi_2d <= vsync_axi_1d;
        end
    end

    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_rising_edge_axi = 1'b0;
            vsync_falling_edge_axi = 1'b0;
        end else begin
            vsync_rising_edge_axi <= ~vsync_axi_2d & vsync_axi_1d;
            vsync_falling_edge_axi <= vsync_axi_2d & ~vsync_axi_1d;
        end
    end
    
    // IP start State Machine
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            cs_start <= IDLE_START;
            ip_start <= 1'b0;
        end else begin
            case (cs_start)
                IDLE_START : begin
                    ip_start <= 1'b0;
                    if (vsync_falling_edge_axi) begin
                        cs_start <= IP_START_1;
                        ip_start <= 1'b1;
                    end
                end
                IP_START_1 : begin
                    if (ip_done) begin
                        cs_start <= IDLE_START;
                        ip_start <= 1'b0;
                    end
                end
            endcase
        end
    end
    
    // data width 34 bits, 512 depth
    pixel_fifo pixel_fifo_i (
        .wr_rst(fifo_reset_axi),
        .wr_clk(axi_clk),
        .rd_rst(fifo_reset_disp),
        .rd_clk(disp_clk),
        .din({axis_tuser, axis_tlast, axis_tdata}),
        .dout(pfifo_dout),
        .wr_en(~pfifo_full & axis_tvalid),
        .full(pfifo_full),
        .rd_en(de_1d),
        .empty(pfifo_empty)
    );
    assign axis_tready = ~pfifo_full;
    
    assign disp_pixel = {pfifo_dout[7:0], 4'd0, pfifo_dout[23:16], 4'd0, pfifo_dout[15:8], 4'd0}; //BRG
    assign de_out = de_1d;
    assign vsync_out = vsync_1d;
    assign hsync_out = hsync_1d;
endmodule

`default_nettype wire


axis2video_out.v の修正後にもう一度、Vivado Analyzer で波形を観察した。
axis_tuser の立ち上がりでトリガした波形を示す。axis_tuser の立ち上がりの前にip_start がアサートされている状態は無かった。これで正常になったと言える。
DisplayPort_test_107_19023.png

次に、ip_done の立ち上がりでトリガを掛けた。ip_done と同時に ip_start も落ちている。これも正常だ。
DisplayPort_test_108_19023.png

これで、DisplayPort の画像を見てみると、正常にテストパターンが表示されていた。やった〜〜〜!!!長かった。。。
DisplayPort_test_111_19025.jpg

2019/02/07 追記: テスト・パターンの色が違っています。詳しくは、”Zynq UltraScale+ MPSoC のDisplayPort のLiveVideo のピクセルデータ”を参照ください)

右の四角のほうが左よりも短い気がするが、これはディスプレイの特性かもしれない?
  1. 2019年01月25日 04:39 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのプロジェクトを作成する3(デバック)

Ultra96のDisplayPortを使用するためのプロジェクトを作成する2(表示失敗)”の続き。

前回は、Ultra96 のDebian を起動し、デバイス・ツリーをロードし、アプリケーション・ソフトを起動して、Displayport への表示を行ったが、テストパターンがうまく表示できなかった。今回は、Vivado Analyzer を使ってデバックを行う。

Vivado Analyzer には2種類のプロービング方法があるが、その2つとも使用した。1つは、ブロック・デザインで配線を選択して、右クリックメニューからDebug を選択して、ブロック・デザインの配線上でSystem ILA を接続する方法と、Open Syntesized Design を開いて、Tools メニューのSet Up Debug... を選択して、論理合成後のネットをプロービングする方法だ。
今回はその両方を使用してデバックした。

最初にブロック・デザインを表示して、pattern_gen_axis IP のAXI4 Lite インターフェースとAXI4-Stream インターフェースにSystem ILA を接続した。
DisplayPort_test_109_19024.png

論理合成を行って、Open Syntesized Design を開いて、Tools メニューのSet Up Debug... を選択し、Zynq UltraSlace+ MPSoC のdp_live_video_in とdp_video_out 、ip_start, ip_done などを論理合成後のネットリストから選択した。

Vivado Anaylzer での波形を貼っておく。
1. 最初に、ネットでプローブビングした信号の画像のピクセルクロックで動作している信号波形を示す。
DisplayPort_test_103_19023.png

axis2video_out_0_de_out の 1 の幅が 1024 クロックで予想通りだ。

2. 次に、ブロック・デザインからプロービングしたAXI4 Lite と AX4-Stream インターフェースの波形を示す。
DisplayPort_test_104_19023.png

特におかしいところは良くわからない。

3. ネットでプロービングした信号のaxi_clk で動作する信号を示す。
DisplayPort_test_105_19023.png

何回もキャプチャすると、tlast と画像出力の位置が変化するようだ。これはおかしい。。。
クロックを変更して、1. と 3. の波形を同じウインドウに表示してみよう。

Open Syntesized Design を開いて、Tools メニューのSet Up Debug... を選択したダイアログでの信号のリストの一部を示す。
すべての信号のクロックを pl_clk0 に変更した。
DisplayPort_test_110_19024.png

これでVivado Analyzer で波形を観測した。
下の波形で ip_start と ip_done に注目して欲しい。ip_done が来た後でも、1クロック分 ip_start が余計に 1 にアサートされている。ip_start が 1クロック分遅延してしまっているので、pattern_gen_axis IP が再スタートと誤認して、axis_tuser を 1 にアサートしているのが分かった。これがバグの原因だ。やっと見つけた。。。
DisplayPort_test_106_19023.png
  1. 2019年01月24日 04:57 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:3

Ultra96のDisplayPortを使用するためのプロジェクトを作成する2(表示失敗)

Ultra96のDisplayPortを使用するためのプロジェクトを作成する1(DisplayPort_test_XGA1_sync)”の続き。

前回は、これまで作ってきたIP を接続してUltra96のDisplayPortを使用するためのプロジェクトを作成した。プロジェクト名はDisplayPort_test なのだが、DisplayPort_test_XGA1_sync ディレクトリに作ってある。このプロジェクトを論理合成、インプリメンテーション、ビットストリームの生成を行った。ハードウェアをエクスポートして、SDKを立ち上げ、ビットストリームを加工して、bin ファイルを生成した。今回は、Ultra96 のDebian を起動し、デバイス・ツリーをロードし、アプリケーション・ソフトを起動して、Displayport への表示を行う。

まずは、前回作成したdisplayport_test_xga1_sync.bin をUltra96 のDebian にSFTP した。
displayport_test_xga1_sync.bin を /lib/firmware/ にコピーする。
sudo cp displayport_test_xga1_sync.bin /lib/firmware/
DisplayPort_test_100_19022.png

./lddtovray.sh を起動して、デバイスツリーをロードする。
./pattern_gen_axis を起動して表示領域のピクセル値の設定とinit_done のアサートを行う。
./disp_pattern.sh でDisplayPort をLiveVideoを表示するように、設定変更を行う。
DisplayPort_test_101_19022.png

実行結果の現在の表示を示す。少し下に流れている。
DisplayPort_test_102_19023.jpg

うまく表示できない。

lddtovray.sh を示す。

#!/bin/bash

sudo mkdir /config/device-tree/overlays/fpga
sudo cp fpga-load.dtb /config/device-tree/overlays/fpga/dtbo
sudo mkdir /config/device-tree/overlays/fclk0
sudo cp fclk0-zynqmp.dtb /config/device-tree/overlays/fclk0/dtbo
sudo mkdir /config/device-tree/overlays/pattern_gen_axis
sudo cp pattern_gen_axis.dtb /config/device-tree/overlays/pattern_gen_axis/dtbo

sleep 0.5
sudo chmod 666 /dev/uio*


rmdtovray.sh を示す。

#!/bin/bash

sudo rmdir /config/device-tree/overlays/pattern_gen_axis/
sudo rmdir /config/device-tree/overlays/fclk0
sudo rmdir /config/device-tree/overlays/fpga/


fpga-load.dts を示す。

/dts-v1/;
/ {
    fragment@0 {
        target-path = "/fpga-full";
        __overlay__ {
            firmware-name = "displayport_test_xga1_sync.bin";
        };
    };
};


fclk0-zynqmp.dts を示す。

/dts-v1/;/plugin/;
/ {
    fragment@0 {
        target-path = "/amba";
        __overlay__ {
            fclk0 {
                compatible    = "ikwzm,fclkcfg-0.10.a";
                clocks        = <&clk 0x47>;
                insert-rate   = "100000000";
                insert-enable = <1>;
                remove-rate   = "1000000";
                remove-enable = <0>;
            };
        };
    };
};


pattern_gen_axis.dts を示す。

/dts-v1/;/plugin/;
/ {
    fragment@0 {
        target-path = "/amba_pl@0";
        #address-cells = <2>;
        #size-cells = <2>;

        __overlay__ {
            #address-cells = <2>;
            #size-cells = <2>;

            pattern_gen_axis-uio {
                compatible = "generic-uio";
                reg = <0x0 0xA0000000 0x0 0x10000>;
            };
        };
    };
};


pattern_gen_axis.c を示す。

// pattern_gen_axis.c
// 2019/01/18 by marsee
//

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

int main(){
    int fd1;
    volatile unsigned *pga;
    int i;

    // uio initialize (uio1)
    fd1 = open("/dev/uio1", O_RDWR|O_SYNC); // pattern_gen_axis IP
    if (fd1 < 1){
        fprintf(stderr, "/dev/uio1 (pattern_gen_axis) open error\n");
        exit(1);
    }
    pga = (volatile unsigned *)mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0);
    if (!pga){
        fprintf(stderr, "pattern_gen_axis mmap error\n");
        exit(1);
    }

    pga[4] = 768; // v_size
    pga[6] = 1024; // h_size
    pga[8] = 0x1; // init_done = 1
    
    printf("sizeof volatile unsigned = %d\n", sizeof(volatile unsigned));
    
    printf("v_size = %d, h_size = %d, init_done = %d\n", pga[4], pga[6], pga[8]);
    
    munmap((void *)pga, 0x10000);
    close(fd1);
    
    return(0);
}


disp_pattern.sh を示す。

#!/bin/bash

sudo ./memwrite fd4ab070 54
sudo ./memwrite fd4aa00c ff

  1. 2019年01月23日 06:14 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのプロジェクトを作成する1(DisplayPort_test_XGA1_sync)

これまで作ってきたIP を接続してUltra96のDisplayPortを使用するためのプロジェクトを作成した。プロジェクト名はDisplayPort_test なのだが、DisplayPort_test_XGA1_sync ディレクトリに作ってある。

DisplayPort_test_XGA1_sync ディレクトリのDisplayPort_test プロジェクトを示す。なお、Vivado 2018.3 で作成されている。
DisplayPort_test_94_19022.png

displayport_test ブロック・デザインを示す。
DisplayPort_test_95_19022.png

なお、init_done はなぜだか?どうしても 1 にならなかったので、1 の固定値を入れてある。

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

ハードウェアをエクスポートして、SDK を立ち上げた。これでHDF ファイルからビットファイルに変換された。
DisplayPort_test_97_19022.png

DisplayPort_test.sdk ディレクトリに bootgen.sh とdisplayport_test_wrapper.bif を作成した。
displayport_test_wrapper_hw_platform_0 からdisplayport_test_wrapper.bit をDisplayPort_test.sdk ディレクトリにコピーした。
DisplayPort_test_98_19022.png

bootgen.sh を示す。

#!/bin/bash
bootgen -image displayport_test_wrapper.bif -arch zynqmp -w -o displayport_test_xga1_sync.bin



displayport_test_wrapper.bif を示す。

all:
{
    [destination_device = pl] displayport_test_wrapper.bit
}



./bootgen.sh を実行して、displayport_test_xga1_sync.bin を生成した。
DisplayPort_test_99_19022.png
  1. 2019年01月22日 05:20 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのIPを作成する7(テストベンチ の作成3)

Ultra96のDisplayPortを使用するためのIPを作成する6(テストベンチ の作成2)”の続き。

前回からブログが2日空いてしまった。土曜日が大切な仕事で出勤だったためもあるのだが、いろいろとプロジェクトを作って試していたが、どうしてもうまくテストバターンが表示されなかった。Vivado Analyzer を使って、実際の波形を確認したところ、アクティブローだと思っていたZynq UltraScale+ MPSoC のdp_video_out_hsync とdp_video_out_vsync がアクティブハイだということがわかった。逆の波形で実装していたので、これは表示できないというのが分かった。それを踏まえて、今回は、”Ultra96のDisplayPortを使用するためのIPを作成する6(テストベンチ の作成2)”で行ったシミュレーションをdp_video_out_hsync とdp_video_out_vsync 、dp_live_video_de_out のモデルを作って実行した。

dp_video_out_hsync とdp_video_out_vsync 、dp_live_video_de_out のモデルはVivado HLS 2018.3 を使って実装した。
なお、モデルは、最初に vsync をアサートしたほうがシミュレーションの実行時間が短くて済む。よって、最初に vsync をアサートするようにしたのだが、その際に static 変数を使用した。
ソースコードの XGA_sync_gen.cpp を示す。

// XGA_sync_gen.cpp
// 2019/01/20 by marsee
//

#include <stdio.h>
#include <string.h>
#include <ap_int.h>
#include "ap_utils.h"

// XGA 解像度 65 MHz
#define H_ACTIVE_VIDEO    1024
#define H_FRONT_PORCH    24
#define H_SYNC_PULSE    136
#define H_BACK_PORCH    160
#define H_SUM            (H_ACTIVE_VIDEO + H_FRONT_PORCH + H_SYNC_PULSE + H_BACK_PORCH)

#define V_ACTIVE_VIDEO    768
#define V_FRONT_PORCH    2
#define V_SYNC_PULSE    6
#define V_BACK_PORCH    29
#define V_SUM            (V_ACTIVE_VIDEO + V_FRONT_PORCH + V_SYNC_PULSE + V_BACK_PORCH)

void XGA_sync_gen(ap_uint<1> &de, ap_uint<1> &hsync, ap_uint<1> &vsync){
#pragma HLS INTERFACE ap_none port=vsyncx
#pragma HLS INTERFACE ap_none port=hsyncx
#pragma HLS INTERFACE ap_none port=de
#pragma HLS INTERFACE ap_ctrl_hs port=return
 ap_uint<16> h_count, v_set, v_count;
 static int flag = 0;

 if(flag == 0){
     v_set = 768;
     flag = 1;
 }else{
     v_set = 0;
 }

    for (v_count=v_set; v_count<V_SUM; v_count++){
        for (h_count=0; h_count<H_SUM; h_count++){
#pragma HLS PIPELINE II=1 rewind
            if (h_count >= (H_ACTIVE_VIDEO +H_FRONT_PORCH) && h_count < (H_ACTIVE_VIDEO + H_FRONT_PORCH + H_SYNC_PULSE))
                hsync = 1;
            else
                hsync = 0;

            if (v_count >= (V_ACTIVE_VIDEO + V_FRONT_PORCH) && v_count < (V_ACTIVE_VIDEO + V_FRONT_PORCH + V_SYNC_PULSE))
                vsync = 1;
            else
                vsync = 0;

            if (h_count < H_ACTIVE_VIDEO && v_count < V_ACTIVE_VIDEO)
                de = 1;
            else
                de = 0;
        }
    }
}


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

// XGA_sync_gen.cpp
// 2019/01/20 by marsee
//

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

void XGA_sync_gen(ap_uint<1> &de, ap_uint<1> &hsyncx, ap_uint<1> &vsyncx);

int main(){
    ap_uint<1> de;
    ap_uint<1> hsyncx;
    ap_uint<1> vsyncx;

    XGA_sync_gen(de, hsyncx, vsyncx);
    XGA_sync_gen(de, hsyncx, vsyncx);

    return 0;
}


XGA_sync_gen プロジェクトを示す。
DisplayPort_test_84_19020.png

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

XGA_sync_gen.v と XGA_sync_gen_mul_mul_10ns_12ns_22_1_1.v が生成された。
DisplayPort_test_86_19020.png

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

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

最初に vsync_V が 1 に変化しているのが分かるだろうか?これで、vsync の立ち上がりからスタートするaxis2video_out.v の動作を確認するシミュレーション時間が短くて済む。

拡大してみよう。
DisplayPort_test_88_19020.png

これで、dp_video_out_hsync とdp_video_out_vsync 、dp_live_video_de_out のモデルのXGA_sync_gen.v と XGA_sync_gen_mul_mul_10ns_12ns_22_1_1.v ができた。

このモデルを使って、”Ultra96のDisplayPortを使用するためのIPを作成する6(テストベンチ の作成2)”のテストベンチのaxis2video_out_tb.v を変更した。
テストベンチのaxis2video_out_tb.v を示す。

`default_nettype none
`timescale 100ps / 1ps

// axis2video_out_tb.v
// 2019/01/17 by marsee
//

module axis2video_out_tb;

    reg  axi_clk;
    reg  axi_rst_n;
    wire ip_start;
    wire ip_done;
    wire [31:0] TDATA;
    wire TVALID;
    wire TREADY;
    wire [3:0] TKEEP;
    wire [3:0] TSTRB;
    wire TUSER;
    wire TLAST;
    wire TID;
    wire TDEST;
    reg  init_done;
    wire init_done_out;
    reg  disp_clk;
    wire  de_in;
    wire  vsync_in;
    wire  hsync_in;
    wire [35:0] disp_pixel;
    wire de_out;
    wire vsync_out;
    wire hsync_out;
    reg  ap_start;
    
    // pattern_gen_axis instance
    pattern_gen_axis pga_inst(
        .ap_clk(axi_clk),
        .ap_rst_n(axi_rst_n),
        .ap_start(ip_start),
        .ap_done(ip_done),
        .ap_idle(),
        .ap_ready(),
        .outs_TDATA(TDATA),
        .outs_TVALID(TVALID),
        .outs_TREADY(TREADY),
        .outs_TKEEP(TKEEP),
        .outs_TSTRB(TSTRB),
        .outs_TUSER(TUSER),
        .outs_TLAST(TLAST),
        .outs_TID(TID),
        .outs_TDEST(TDEST),
        .init_done_V(init_done),
        .init_done_out_V(init_done_out),
        .ap_return()
    );
    
    XGA_sync_gen xgasg (
        .ap_clk(disp_clk),
        .ap_rst(!axi_rst_n),
        .ap_start(ap_start),
        .ap_done(),
        .ap_idle(),
        .ap_ready(),
        .de_V(de_in),
        .hsync_V(hsync_in),
        .vsync_V(vsync_in)
    );
    
    // axis2video_out instance
    axis2video_out axis2vo_inst(
        .disp_clk(disp_clk),
        .axi_clk(axi_clk),
        .axi_rst_n(axi_rst_n),
        .init_done(init_done),
        .axis_tdata(TDATA),
        .axis_tvalid(TVALID),
        .axis_tready(TREADY),
        .axis_tkeep(TKEEP),
        .axis_tstrb(TSTRB),
        .axis_tuser(TUSER),
        .axis_tlast(TLAST),
        .axis_tid(TID),
        .axis_tdest(TDEST),
        .ip_start(ip_start),
        .ip_done(ip_done),
        .de_in(de_in),
        .vsync_in(vsync_in),
        .hsync_in(hsync_in),
        .disp_pixel(disp_pixel),
        .de_out(de_out),
        .vsync_out(vsync_out),
        .hsync_out(hsync_out)
    );
    
    // axi_clk = 200 MHz (5 ns)
    always #25
        axi_clk = ~axi_clk;
    
    // disp_clk = 65 MHz (13.4 ns)
    always #77
        disp_clk = ~disp_clk;
        
    initial  begin
        axi_clk = 1'b1;
        disp_clk = 1'b1;
        axi_rst_n = 1'b0;
        init_done = 1'b0;
        ap_start = 1'b0;

        // Wait 100 ns for global reset to finish
        #1000;
        axi_rst_n = 1'b1;
        
        #1000;
        init_done = 1'b1; 
        ap_start = 1'b1;     
    end
endmodule

`default_nettype wire


hsync_in と vsync_in をアクティブ・ハイに変更した axis2video_out.v を示す。
(2019/01/25 :修正)このソースコードにはバグがあります。バグ修正後のソースコードについては、”Ultra96のDisplayPortを使用するためのプロジェクトを作成する4(完成)”をご覧ください。

// axis2video_out.v
// 2019/01/14 by marsee
//

`default_nettype none

module axis2video_out
    (
        // Clock and Reset
        input wire  disp_clk,
        input wire  axi_clk,
        input wire  axi_rst_n,
        input wire  init_done,
        
        // AXI4-Stream
        input wire  [31:0] axis_tdata,
        input wire  axis_tvalid,
        output wire axis_tready,
        input wire  [3:0] axis_tkeep,
        input wire  [3:0] axis_tstrb,
        input wire  axis_tuser,
        input wire  axis_tlast,
        input wire  axis_tid,
        input wire  axis_tdest,
        
        // IP
        output reg  ip_start,
        input wire  ip_done,
        
        // video in
        input wire  de_in,
        input wire  vsync_in,
        input wire  hsync_in,
        
        // video_out
        output wire  [35:0] disp_pixel,
        output wire  de_out,
        output wire vsync_out,
        output wire  hsync_out
    );

    parameter       IDLE_START =    1'b0,
                    IP_START_1 =    1'b1;
    
    reg     reset_disp_2b = 1'b1, reset_disp_1b = 1'b1;
    wire    reset_disp;
    reg     fifo_reset_axi_2b = 1'b0, fifo_reset_axi_1b = 1'b0;
    wire    fifo_reset_axi;
    reg     fifo_reset_disp_2b = 1'b0, fifo_reset_disp_1b = 1'b0;
    wire    fifo_reset_disp;
    reg     de_1d, vsync_1d, hsync_1d;
    reg     vsync_axi_1b, vsync_axi_2b;
    wire    vsync_axi;
    reg     vsync_axi_1d, vsync_axi_2d;
    reg     cs_start;
    wire    pfifo_empty, pfifo_full;
    wire [33:0] pfifo_dout;
    reg    vsync_rising_edge_axi, vsync_falling_edge_axi;
    
    always @(posedge disp_clk) begin
        if(reset_disp) begin
            de_1d <= 1'b0;
            vsync_1d <= 1'b0;
            hsync_1d <= 1'b0;
        end else begin
            de_1d <= de_in;
            vsync_1d <= vsync_in;
            hsync_1d <= hsync_in;
        end
    end
    
    // reset signals    
    always @(posedge axi_clk) begin
        fifo_reset_axi_2b <= ~init_done | ~axi_rst_n | vsync_1d;
        fifo_reset_axi_1b <= fifo_reset_axi_2b;
    end
    assign fifo_reset_axi = fifo_reset_axi_1b;
        
    always @(posedge disp_clk) begin
        fifo_reset_disp_2b <= ~init_done | ~axi_rst_n | vsync_1d;
        fifo_reset_disp_1b <= fifo_reset_disp_2b;
    end
    assign fifo_reset_disp = fifo_reset_disp_1b;
        
    always @(posedge disp_clk) begin
        reset_disp_2b <= ~init_done | ~axi_rst_n;
        reset_disp_1b <= reset_disp_2b;
    end
    assign reset_disp = reset_disp_1b;
    
    // vsync_rising_edge, vsync_falling_edge
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_axi_2b <= 1'b1;
            vsync_axi_1b <= 1'b1;
        end else begin
            vsync_axi_2b <= vsync_1d;
            vsync_axi_1b <= vsync_axi_2b;
        end
    end
    assign vsync_axi = vsync_axi_1b;
    
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_axi_1d <= 1'b1;
            vsync_axi_2d <= 1'b1;
        end else begin
            vsync_axi_1d <= vsync_axi;
            vsync_axi_2d <= vsync_axi_1d;
        end
    end

    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_rising_edge_axi = 1'b0;
            vsync_falling_edge_axi = 1'b0;
        end else begin
            vsync_rising_edge_axi <= ~vsync_axi_2d & vsync_axi_1d;
            vsync_falling_edge_axi <= vsync_axi_2d & ~vsync_axi_1d;
        end
    end
    
    // IP start State Machine
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            cs_start <= IDLE_START;
            ip_start <= 1'b0;
        end else begin
            case (cs_start)
                IDLE_START : begin
                    ip_start <= 1'b0;
                    if (vsync_falling_edge_axi)
                        cs_start <= IP_START_1;
                end
                IP_START_1 : begin
                    ip_start <= 1'b1;
                    if (ip_done)
                        cs_start <= IDLE_START;
                end
            endcase
        end
    end
    
    // data width 34 bits, 512 depth
    pixel_fifo pixel_fifo_i (
        .wr_rst(fifo_reset_axi),
        .wr_clk(axi_clk),
        .rd_rst(fifo_reset_disp),
        .rd_clk(disp_clk),
        .din({axis_tuser, axis_tlast, axis_tdata}),
        .dout(pfifo_dout),
        .wr_en(~pfifo_full & axis_tvalid),
        .full(pfifo_full),
        .rd_en(de_1d),
        .empty(pfifo_empty)
    );
    assign axis_tready = ~pfifo_full;
    
    assign disp_pixel = {pfifo_dout[23:16], 4'd0, pfifo_dout[15:8], 4'd0, pfifo_dout[7:0], 4'd0};
    assign de_out = de_1d;
    assign vsync_out = vsync_1d;
    assign hsync_out = hsync_1d;
endmodule

`default_nettype wire


axis2video_out のVivado 2018.3 プロジェクトを示す。
DisplayPort_test_92_19020.png

シミュレーションの階層を示す。
DisplayPort_test_91_19020.png

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

うまく動いていそうだ。

拡大波形を示す。
DisplayPort_test_90_19020.png

水平 1 ラインの時間が 20.6976 us であることが分かる。
これは、1クロック、 15.4 us X (1024 + 24 + 136 + 160) = 20.6976 us で計算が合う。
  1. 2019年01月21日 04:02 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのIPを作成する6(テストベンチ の作成2)

Ultra96のDisplayPortを使用するためのIPを作成する5(テストベンチ の作成1)”の続き。

前回は、テストベンチを作成する1歩として、 pattern_gen_axis をテストベンチに使用するために修正した。今回は、テストベンチ本体を作成してテストを行う。

(2019/01/21:追記)dp_video_out_hsync とdp_video_out_vsync の極性をアクティブローとしてVerilog HDL を作成しましたが、実際はアクティブハイのためこのVerilog HDL コードは動作しません。
詳しくは、”Ultra96のDisplayPortを使用するためのIPを作成する7(テストベンチ の作成3)”をご覧ください。

まずは、axis2video_out.v を貼っておく。

// axis2video_out.v
// 2019/01/14 by marsee
//

`default_nettype none

module axis2video_out
    (
        // Clock and Reset
        input wire  disp_clk,
        input wire  axi_clk,
        input wire  axi_rst_n,
        input wire  init_done,
        
        // AXI4-Stream
        input wire  [31:0] axis_tdata,
        input wire  axis_tvalid,
        output wire axis_tready,
        input wire  [3:0] axis_tkeep,
        input wire  [3:0] axis_tstrb,
        input wire  axis_tuser,
        input wire  axis_tlast,
        input wire  axis_tid,
        input wire  axis_tdest,
        
        // IP
        output reg  ip_start,
        input wire  ip_done,
        
        // video in
        input wire  de_in,
        input wire  vsync_in,
        input wire  hsync_in,
        
        // video_out
        output wire  [35:0] disp_pixel,
        output wire  de_out,
        output wire vsync_out,
        output wire  hsync_out
    );

    parameter       IDLE_START =    1'b0,
                    IP_START_1 =    1'b1;
    
    reg     reset_disp_2b = 1'b1, reset_disp_1b = 1'b1;
    wire    reset_disp;
    reg     fifo_reset_axi_2b = 1'b0, fifo_reset_axi_1b = 1'b0;
    wire    fifo_reset_axi;
    reg     fifo_reset_disp_2b = 1'b0, fifo_reset_disp_1b = 1'b0;
    wire    fifo_reset_disp;
    reg     de_1d, vsync_1d, hsync_1d;
    reg     vsync_axi_1b, vsync_axi_2b;
    wire    vsync_axi;
    reg     vsync_axi_1d, vsync_axi_2d;
    reg     cs_start;
    wire    pfifo_empty, pfifo_full;
    wire [31:0] pfifo_dout;
    reg    vsync_rising_edge_axi, vsync_falling_edge_axi;
    
    always @(posedge disp_clk) begin
        if(reset_disp) begin
            de_1d <= 1'b0;
            vsync_1d <= 1'b1;
            hsync_1d <= 1'b1;
        end else begin
            de_1d <= de_in;
            vsync_1d <= vsync_in;
            hsync_1d <= hsync_in;
        end
    end
    
    // reset signals    
    always @(posedge axi_clk) begin
        fifo_reset_axi_2b <= ~init_done | ~axi_rst_n | ~vsync_1d;
        fifo_reset_axi_1b <= fifo_reset_axi_2b;
    end
    assign fifo_reset_axi = fifo_reset_axi_1b;
        
    always @(posedge disp_clk) begin
        fifo_reset_disp_2b <= ~init_done | ~axi_rst_n | ~vsync_1d;
        fifo_reset_disp_1b <= fifo_reset_disp_2b;
    end
    assign fifo_reset_disp = fifo_reset_disp_1b;
        
    always @(posedge disp_clk) begin
        reset_disp_2b <= ~init_done | ~axi_rst_n;
        reset_disp_1b <= reset_disp_2b;
    end
    assign reset_disp = reset_disp_1b;
    
    // vsync_rising_edge, vsync_falling_edge
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_axi_2b <= 1'b1;
            vsync_axi_1b <= 1'b1;
        end else begin
            vsync_axi_2b <= vsync_1d;
            vsync_axi_1b <= vsync_axi_2b;
        end
    end
    assign vsync_axi = vsync_axi_1b;
    
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_axi_1d <= 1'b1;
            vsync_axi_2d <= 1'b1;
        end else begin
            vsync_axi_1d <= vsync_axi;
            vsync_axi_2d <= vsync_axi_1d;
        end
    end

    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            vsync_rising_edge_axi = 1'b0;
            vsync_falling_edge_axi = 1'b0;
        end else begin
            vsync_rising_edge_axi <= ~vsync_axi_2d & vsync_axi_1d;
            vsync_falling_edge_axi <= vsync_axi_2d & ~vsync_axi_1d;
        end
    end
    
    // IP start State Machine
    always @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            cs_start <= IDLE_START;
            ip_start <= 1'b0;
        end else begin
            case (cs_start)
                IDLE_START : begin
                    ip_start <= 1'b0;
                    if (vsync_rising_edge_axi)
                        cs_start <= IP_START_1;
                end
                IP_START_1 : begin
                    ip_start <= 1'b1;
                    if (ip_done)
                        cs_start <= IDLE_START;
                end
            endcase
        end
    end
    
    // data width 34 bits, 512 depth
    pixel_fifo pixel_fifo_i (
        .wr_rst(fifo_reset_axi),
        .wr_clk(axi_clk),
        .rd_rst(fifo_reset_disp),
        .rd_clk(disp_clk),
        .din({axis_tuser, axis_tlast, axis_tdata}),
        .dout(pfifo_dout),
        .wr_en(~pfifo_full & axis_tvalid),
        .full(pfifo_full),
        .rd_en(de_1d),
        .empty(pfifo_empty)
    );
    assign axis_tready = ~pfifo_full;
    
    assign disp_pixel = {pfifo_dout[23:16], 4'd0, pfifo_dout[15:8], 4'd0, pfifo_dout[7:0], 4'd0};
    assign de_out = de_1d;
    assign vsync_out = vsync_1d;
    assign hsync_out = hsync_1d;
endmodule

`default_nettype wire


次に、テストベンチのaxis2video_out_tb.v を貼っておく。

`default_nettype none
`timescale 100ps / 1ps

// axis2video_out_tb.v
// 2019/01/17 by marsee
//

module axis2video_out_tb;

    reg  axi_clk;
    reg  axi_rst_n;
    wire ip_start;
    wire ip_done;
    wire [31:0] TDATA;
    wire TVALID;
    wire TREADY;
    wire [3:0] TKEEP;
    wire [3:0] TSTRB;
    wire TUSER;
    wire TLAST;
    wire TID;
    wire TDEST;
    reg  init_done;
    wire init_done_out;
    reg  disp_clk;
    reg  de_in;
    reg  vsync_in;
    reg  hsync_in;
    wire [35:0] disp_pixel;
    wire de_out;
    wire vsync_out;
    wire hsync_out;
    
    // pattern_gen_axis instance
    pattern_gen_axis pga_inst(
        .ap_clk(axi_clk),
        .ap_rst_n(axi_rst_n),
        .ap_start(ip_start),
        .ap_done(ip_done),
        .ap_idle(),
        .ap_ready(),
        .outs_TDATA(TDATA),
        .outs_TVALID(TVALID),
        .outs_TREADY(TREADY),
        .outs_TKEEP(TKEEP),
        .outs_TSTRB(TSTRB),
        .outs_TUSER(TUSER),
        .outs_TLAST(TLAST),
        .outs_TID(TID),
        .outs_TDEST(TDEST),
        .init_done_V(init_done),
        .init_done_out_V(init_done_out),
        .ap_return()
    );
    
    // axis2video_out instance
    axis2video_out axis2vo_inst(
        .disp_clk(disp_clk),
        .axi_clk(axi_clk),
        .axi_rst_n(axi_rst_n),
        .init_done(init_done),
        .axis_tdata(TDATA),
        .axis_tvalid(TVALID),
        .axis_tready(TREADY),
        .axis_tkeep(TKEEP),
        .axis_tstrb(TSTRB),
        .axis_tuser(TUSER),
        .axis_tlast(TLAST),
        .axis_tid(TID),
        .axis_tdest(TDEST),
        .ip_start(ip_start),
        .ip_done(ip_done),
        .de_in(de_in),
        .vsync_in(vsync_in),
        .hsync_in(hsync_in),
        .disp_pixel(disp_pixel),
        .de_out(de_out),
        .vsync_out(vsync_out),
        .hsync_out(hsync_out)
    );
    
    // axi_clk = 200 MHz (5 ns)
    always #25
        axi_clk = ~axi_clk;
    
    // disp_clk = 65 MHz (13.4 ns)
    always #77
        disp_clk = ~disp_clk;
        
    initial  begin
        axi_clk = 1'b1;
        disp_clk = 1'b1;
        axi_rst_n = 1'b0;
        init_done = 1'b0;
        de_in = 1'b0;
        vsync_in = 1'b1;
        hsync_in = 1'b1;

        // Wait 100 ns for global reset to finish
        #1000;
        axi_rst_n = 1'b1;
        
        #500;
        init_done = 1'b1;
        
        #1000;
        vsync_in = 1'b0;
        
        #1000;
        vsync_in = 1'b1;
        
        #1000;
        de_in = 1'b1;
        
        #5000;
        de_in = 1'b0;
        
        #1000;
        hsync_in = 1'b0;
        
        #1000;
        hsync_in = 1'b1;
    end
endmodule

`default_nettype wire


Vivado 2018.3 のaxis2video_out プロジェクトを示す。
DisplayPort_test_77_190117.png

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

あまり検証していないが、大丈夫そうだろう?実機で確認しよう。

Tools メニューからCreate and Package New IP... を選択してIP にする。
ダイアログで適当に選ぶと、Package IP タブが表示された。
DisplayPort_test_79_190117.png

Package Steps のCustomization GUI
DisplayPort_test_80_190117.png

Package Steps のReview and Package で、Edit packaging settings をクリックする。表示されたSetings ダイアログで、Create archive of IP にチェックが入っていることを確認する。
Package IP ボタンをクリックする。
DisplayPort_test_81_190117.png

axis2video_out がIP になった。
DisplayPort_test_82_190118.png

axis2video_out ディレクトリにuser.org_user_axis2video_out_1.0.zip ができた。
DisplayPort_test_83_190118.png
  1. 2019年01月18日 04:08 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのIPを作成する5(テストベンチ の作成1)

Ultra96のDisplayPortを使用するためのIPを作成する4(pixel_fifo の作成)”の続き。

前回は、axi2video_out.v に使用する非同期FIFO のpixel_fifo を生成した。今回は、axis2video_out.v は書き上がったのだが、検証していないので、まだ貼れないため、テストベンチの作成について書いてみよう。テストベンチではカメラなどの複雑な入力のモデルを作るのが面倒なのだが、ここにVivado HLS という便利なツールがある。C で書いてHDL で吐き出してくれるので、モデルを C で書いてHDLを作ろう。今回の pattern_gen_axis はVivado HLS で作ってあるので、それをモデルにしやすいようにパラメータを固定しただけでHDL モデルとして使用することにした。皆さんも例えばカメラのインターフェースのテストベンチを作成するのにVivado HLS を使ってC 言語で書いてHDL モデルを作るのはどうだろうか?

pattern_gen_axis.cpp を変更し、AXI4 Lite Slave を止めて、ap_none にして、h_size, v_size をdefine に変更した。
モデル用のpattern_gen_axis.cpp を示す。

// pattern_gen_axis.cpp
// 2019/01/13 by marsee
//

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

#define V_SIZE 60
#define H_SIZE 80

int pattern_gen_axis(hls::stream<ap_axis<32,1,1,1> >& outs,
        ap_uint<1> &init_done, ap_uint<1> &init_done_out
){
#pragma HLS INTERFACE ap_none register port=init_done_out
#pragma HLS INTERFACE ap_none port=init_done
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE ap_ctrl_hs port=return
    ap_axis<32,1,1,1> out_val;

    init_done_out = init_done;

    LOOP_Y: for(int y=0; y<V_SIZE; y++){
#pragma HLS LOOP_TRIPCOUNT min=768 max=768 avg=768
        LOOP_X: for(int x=0; x<H_SIZE; x++){
#pragma HLS LOOP_TRIPCOUNT min=1024 max=1024 avg=1024
#pragma HLS PIPELINE II=1
            if (y < V_SIZE/2){
                if (x < H_SIZE/2){
                    out_val.data = 0xff0000; // *red=0xff; *green=0; *blue=0;
                } else if (x < H_SIZE){
                    out_val.data = 0x00ff00; // *red=0; *green=0xff; *blue=0;
                } else {
                    out_val.data = 0x000000; // *red=0; *green=0; *blue=0;
                }
            } else if (y < V_SIZE){
                if (x < H_SIZE/2){
                    out_val.data = 0x0000ff; // *red=0; *green=0; *blue=0xff;
                } else if (x < H_SIZE){
                    out_val.data = 0xffffff; // *red=0xff; *green=0xff; *blue=0xff;
                } else {
                    out_val.data = 0x000000; // *red=0; *green=0; *blue=0;
                }
            } else {
                out_val.data = 0x000000; // *red=0; *green=0; *blue=0;
            }

            if(x==0 && y==0)
                out_val.user = 1;
            else
                out_val.user = 0;
            if(x == H_SIZE-1)
                out_val.last = 1;
            else
                out_val.last = 0;

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


Vivado HLS 2018.3 で pattern_gen_axis_test プロジェクトを作成した。
DisplayPort_test_74_190117.png

C コードの合成を行って、pattern_gen_axis.v が生成されている。
DisplayPort_test_75_190117.png

Vivado HLS で生成されたpattern_gen_axis.v を axis2video_out のVivado 2018.3 プロジェクトにシミュレーション用ファイルとして追加した。
DisplayPort_test_76_190117.png
  1. 2019年01月17日 05:09 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのIPを作成する4(pixel_fifo の作成)

Ultra96のDisplayPortを使用するためのIPを作成する3(pattern_gen_axis IPの変更)”の続き。

前回は、axis2video_out.v を書いている時に、axis2video_out.v からpattern_gen_axis IP をスタートさせたいという欲求があったので、pattern_gen_axis IP のブロック・レベル・インターフェースを ap_ctrl_hs に変更した。今回は、axi2video_out.v に使用する非同期FIFO のpixel_fifo を生成する。

Xilinx 社のFIFO Generator を使用してFIFO を生成するには、Flow Navigator のPROJECT MANAGER を開いて、IP Catlog をクリックする。

IP Catalog が開くので、Memories & Storage Elements -> FIFOs -> FIFO Generator をダブルクリックして設定を行う。

FIFO Generator のダイアログが開く。
Componet Name を pixel_fifo と入力する。
Interface Type はNative になっていると思うので、Fifo Implementation をIndependent Clocks Block RAM に変更する。
DisplayPort_test_68_190115.png

Native Ports タブをクリックする。
Read Mode のFirst Word Fall Through ラジオボタンをクリックする。
Write Width を 34 ビットに設定する。
Initialization のEnable Reset Synchronization のチェックを外す。
DisplayPort_test_69_190115.png

Status Flags のタブをクリックする。
ここはデフォルトのままとする。
DisplayPort_test_70_190115.png

Data Counts タブをクリックする。
Data Count Options のMore Accurate Data Counts にチェックを入れる。(Data Count を使ってないので、これは関係ないかもしれないが。。。)
DisplayPort_test_71_190115.png

Summary タブをクリックする。
Summary が表示された。OK ボタンをクリックする。
DisplayPort_test_72_190115.png

pixel_fifo が生成された。
DisplayPort_test_73_190115.png
  1. 2019年01月16日 04:53 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのIPを作成する3(pattern_gen_axis IPの変更)

Ultra96のDisplayPortを使用するためのIPを作成する2(pattern_gen_axis IP)”の続き。

前回は、pattern_gen_axis IP をVivado HLS 2018.3 で作成したのだが、次のaxis2video_out.v を書いている時に、axis2video_out.v からpattern_gen_axis IP をスタートさせたいという欲求があったので、pattern_gen_axis IP のブロック・レベル・インターフェースを ap_ctrl_hs に変更した。

新しいブロック図は下図になった。
DisplayPort_test_65_190115.png

axis2video_out.v からpattern_gen_axis IP をスタートするために、ブロック・レベルのインターフェースのstart と done を使用している。

pattern_gen_axis IP の変更した。ソースコードを示す。ブロック・レベルのインターフェースを ap_ctrl_hs に変更しただけである。

// pattern_gen_axis.cpp
// 2019/01/13 by marsee
//

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

int pattern_gen_axis(hls::stream<ap_axis<32,1,1,1> >& outs,
        ap_uint<11> v_size, ap_uint<11> h_size,
        ap_uint<1> &init_done, ap_uint<1> &init_done_out
){
#pragma HLS INTERFACE ap_none register port=init_done_out
#pragma HLS INTERFACE s_axilite port=init_done
#pragma HLS INTERFACE s_axilite port=v_size
#pragma HLS INTERFACE s_axilite port=h_size
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE ap_ctrl_hs port=return
    ap_axis<32,1,1,1> out_val;

    init_done_out = init_done;

    LOOP_Y: for(int y=0; y<v_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=768 max=768 avg=768
        LOOP_X: for(int x=0; x<h_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=1024 max=1024 avg=1024
#pragma HLS PIPELINE II=1
            if (y < v_size/2){
                if (x < h_size/2){
                    out_val.data = 0xff0000; // *red=0xff; *green=0; *blue=0;
                } else if (x < h_size){
                    out_val.data = 0x00ff00; // *red=0; *green=0xff; *blue=0;
                } else {
                    out_val.data = 0x000000; // *red=0; *green=0; *blue=0;
                }
            } else if (y < v_size){
                if (x < h_size/2){
                    out_val.data = 0x0000ff; // *red=0; *green=0; *blue=0xff;
                } else if (x < h_size){
                    out_val.data = 0xffffff; // *red=0xff; *green=0xff; *blue=0xff;
                } else {
                    out_val.data = 0x000000; // *red=0; *green=0; *blue=0;
                }
            } else {
                out_val.data = 0x000000; // *red=0; *green=0; *blue=0;
            }

            if(x==0 && y==0)
                out_val.user = 1;
            else
                out_val.user = 0;
            if(x == h_size-1)
                out_val.last = 1;
            else
                out_val.last = 0;

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


pattern_gen_axis IP の新しいレジスタマップを示す。

//------------------------Address Info-------------------
// 0x00 : reserved
// 0x04 : reserved
// 0x08 : reserved
// 0x0c : reserved
// 0x10 : Data signal of v_size_V
//        bit 10~0 - v_size_V[10:0] (Read/Write)
//        others   - reserved
// 0x14 : reserved
// 0x18 : Data signal of h_size_V
//        bit 10~0 - h_size_V[10:0] (Read/Write)
//        others   - reserved
// 0x1c : reserved
// 0x20 : Data signal of init_done_V
//        bit 0  - init_done_V[0] (Read/Write)
//        others - reserved
// 0x24 : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


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

Export RTL の結果を示す。問題ないようだ。
DisplayPort_test_67_190115.png
  1. 2019年01月15日 05:26 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのIPを作成する2(pattern_gen_axis IP)

Ultra96のDisplayPortを使用するためのIPを作成する1(概要編)”の続き。

前回は、Ultra96 の PS のDisp;ayPort に出力するために、Video_out に同期信号に同期した画像出力をPS の Live Video の video_in に入れる必要があったので、そのブロック図を示した。今回は、その内のpattern_gen_axis IP (pattern_out_axis のはずだったが作ってみたら手元が狂って名前が変わってしまった。悪しからずご了承ください)をVivado HLS 2018.3 で作成してみよう。

pattern_gen_axis.cpp を貼っておく。

// pattern_gen_axis.cpp
// 2019/01/13 by marsee
//

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

int pattern_gen_axis(hls::stream<ap_axis<32,1,1,1> >& outs,
        int v_size, int h_size,
        ap_uint<1> &init_done, ap_uint<1> &init_done_out
){
#pragma HLS INTERFACE ap_none register port=init_done_out
#pragma HLS INTERFACE s_axilite port=init_done
#pragma HLS INTERFACE s_axilite port=v_size
#pragma HLS INTERFACE s_axilite port=h_size
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE s_axilite port=return
    ap_axis<32,1,1,1> out_val;

    init_done_out = init_done;

    LOOP_Y: for(int y=0; y<v_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=768 max=768 avg=768
        LOOP_X: for(int x=0; x<h_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=1024 max=1024 avg=1024
#pragma HLS PIPELINE II=1
            if (y < v_size/2){
                if (x < h_size/2){
                    out_val.data = 0xff0000; // *red=0xff; *green=0; *blue=0;
                } else if (x < h_size){
                    out_val.data = 0x00ff00; // *red=0; *green=0xff; *blue=0;
                } else {
                    out_val.data = 0x000000; // *red=0; *green=0; *blue=0;
                }
            } else if (y < v_size){
                if (x < h_size/2){
                    out_val.data = 0x0000ff; // *red=0; *green=0; *blue=0xff;
                } else if (x < h_size){
                    out_val.data = 0xffffff; // *red=0xff; *green=0xff; *blue=0xff;
                } else {
                    out_val.data = 0x000000; // *red=0; *green=0; *blue=0;
                }
            } else {
                out_val.data = 0x000000; // *red=0; *green=0; *blue=0;
            }

            if(x==0 && y==0)
                out_val.user = 1;
            else
                out_val.user = 0;
            if(x == h_size-1)
                out_val.last = 1;
            else
                out_val.last = 0;

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


pattern_gen_axis_tb.cpp を貼っておく。

// pattern_gen_axis_tb.cpp
// 2019/01/13 by marsee
//

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

int pattern_gen_axis(hls::stream<ap_axis<32,1,1,1> >& outs,
        int v_size, int h_size,
        ap_uint<1> &init_done, ap_uint<1> &init_done_out
);

#define V_SIZE 60
#define H_SIZE 80

int main(){
    using namespace cv;

    hls::stream<ap_axis<32,1,1,1> > outs;
    ap_axis<32,1,1,1> pix;
    ap_uint<1> init_done = 1;
    ap_uint<1> init_done_out = 0;

    pattern_gen_axis(outs, V_SIZE, H_SIZE, init_done, init_done_out);

    Mat dst = Mat(V_SIZE, H_SIZE, 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++){
            Vec3b pixel;
            outs >> pix;
            ap_int<32> rgb = pix.data;
            pixel[0] = (rgb & 0xff); // blue
            pixel[1] = (rgb & 0xff00) >> 8; // green
            pixel[2] = (rgb & 0xff0000) >> 16; // red
            dst_vec3b(y,x) = pixel;
        }
    }
    imwrite("pattern_out.bmp", dst);

    return(0);
}



pattern_gen_axis プロジェクトを示す。
DisplayPort_test_58_190114.png

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

pattern_out.bmp が生成されていた。テストバターンになっている。
DisplayPort_test_60_190114.png

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

TRIPCONT は XGA 解像度にしたので、1024 X 768 = 786432 ピクセルということになる。レイテンシは、786436 クロックなので、4 クロックしか増えていないので、ほぼ 1 クロック 1 ピクセル処理を実現できている。

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

テストベンチでは、 80 X 60 ピクセルのテストバターンを使用しているので、80 X 60 = 4800 ピクセルになる。結果は4854 クロックなので、ほぼ 1 クロックで 1 ピクセル処理になっている。

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

outs_TVALID と outs_TREADY が安定して 1 であることが分かる。

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

HD 解像度のクロック 148.5 MHz まで対応することを考えているので、200 MHz で動作するように周期を 5 ns に設定した。
CP achieved post-implementation は 2.259 ns で大丈夫そうだ。
  1. 2019年01月14日 05:40 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのIPを作成する1(概要編)

Ultra96のDisplayPortを使用するためのテスト5(Vivado、実機テスト2)”で、テストパターン発生IP のクロックをdp_video_ref_clk から取ったプロジェクトでテストパターンを表示することができた。だが、まだ、同期が取れていなくて静止して表示されないので、Verilog HDL でPS の dp_video_out_hsync, dp_video_out_vsync, dp_live_video_de_out を使用して、それらの信号に同期した画像出力をする必要があるようだ。

それらの情報は、ひでみさんの”FPGAの内容が薄い本”に載っている。それらの情報を元にしている。ありがとうございます。

さて、テストパターン作成用IP (pattern_gen) の構成を見ていこう。下にブロック図を貼っておく。
DisplayPort_test_52_190112.png

pattern_gen は pattern_out_axis と axis2video_out 、 init_done_axi4ls モジュールで構成される。pattern_out_axis と axis2video_out モジュールの間はAXI4-Stream で接続される。pattern_out_axis はテストパターンを生成して、生成したデータをAXI4-Stream で出力するモジュールだ。 axis2video_out は、video_timing 入力との同期を取りながら、pattern_out_axis からのAXI4-Stream を受けたデータを Video_out に出力する。
init_done_axi4ls モジュールはAXI4 Lite Slave としてARMプロセッサにインターフェースされ、init_done レジスタを持つ。init_done レジスタから接続されるのは、 init_done_out 信号出力だ。init_done_out 信号出力は、axis2video_out モジュールのリセット信号として使用される。
pattern_out_axis, init_done_axi4ls モジュールはVivado HLS で作成してみよう。axis2video_out モジュールはVerilog HDL で作成し、PS の dp_video_out_hsync, dp_video_out_vsync, dp_live_video_de_out の video_timing に同期して Video_out を出力する。

次にカメラからの画像を出力する vflip_dma_disp_cont のブロック図も貼っておく。
DisplayPort_test_53_190112.png

vflip_dma_disp_cont は、vflip_dma_axis, axis2video_out 、 init_done_axi4ls モジュールで構成される。つまり、pattern_gen のpattern_out_axis を vflip_dma_axis に交換した構造になる。
vflip_dma_axis はOV5642の垂直方向にフリップされた画像を正常にDMA するためのVivado HLS で作成されたモジュールにする予定だ。

今、考えたところ、init_done_axis モジュールは、pattern_out_axis, vflip_dma_axis モジュールに取り込もうと思う。そして、それら 2 つのモジュールもAXI4 Lite Slave でレジスタマップしよう。

新しいテストパターン作成用IP (pattern_gen) と vflip_dma_disp_cont のブロック図を貼っておく。
DisplayPort_test_54_190113.png

DisplayPort_test_55_190113.png

(おまけ)
試しに作った init_done_axi4ls.cpp を示します。
DisplayPort_test_56_190113.png

C コードの合成結果です。
DisplayPort_test_57_190113.png

ソースコード。

// init_done_axils.cpp
// 2019/01/12 by marsee
//

#include "ap_int.h"

int init_done_axi4ls(ap_uint<1> &init_done, ap_uint<1> &init_done_out){
#pragma HLS INTERFACE ap_none register port=init_done_out
#pragma HLS INTERFACE s_axilite port=init_done
#pragma HLS INTERFACE s_axilite port=return
    init_done_out = init_done;

    return(0);
}


  1. 2019年01月13日 05:07 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのテスト5(Vivado、実機テスト2)

Ultra96のDisplayPortを使用するためのテスト4(Vivado、実機テスト)”の続き。

前回は、Vivado 2018.3 の DisplayPort_test プロジェクトの display_cont_sub IP と交換して論理合成、インプリメンテーション、ビットストリームの生成を行って、実機テストをしたのだが、テストパターンが出なかった。実は良くブロック・デザインを見てみると、、PSのdp_video_in_clk に繋ぎ忘れていた。。。これはまずいということで、今回はPSのdp_video_in_clk に接続してやってみた。

ブロック・デザインを示す。
DisplayPort_test_44_190112.png

今度は、きちんとPSのdp_video_in_clk も、dp_video_ref_clk に接続した。
そして、実は、今回からDisplayPort_test をSVGA, XGA1, XGA2 に分割した。SVGA はその名の通りにSVGA解像度、XGA1 はXGA 解像度だが、テストパターン発生IP のクロックをdp_video_ref_clk から取ったプロジェクトで、XGA2 はpl_clk0 からクロックを取っている。
DisplayPort_test_45_190112.png

従って、Ultra96 のDebian もdisplayport_test の下を同様に分割した。
DisplayPort_test_46_190112.png

さて、現在は、XGA1 を使用してる。これは、XGA 解像度だが、テストパターン発生IP のクロックをdp_video_ref_clk から取ったプロジェクトだ。早速手順に従って実機テストした。
DisplayPort_test_47_190112.png

そうすると、テストパターンが表示された。しかし、完全には同期が取れずに上に流れていた。
DisplayPort_test_51_190112.jpg

DisplayPort_test_50_190112.jpg

とりあえず表示できて、安心した。

なお、ツィッターでsmilingflutist さんに教えてもらった情報で、Xilinx Wiki Device Tree Tips の 4.3 Clock Status At Runtime を見るとクロックの有効/無効が確認できるとのことだった。smilingflutist さんありがとうございました。
自分のホーム以下にコピーして見た。
DisplayPort_test_49_190112.png

dp_video_ref は約 65 MHz になっていた。これは、XGA のビデオクロックだ。
  1. 2019年01月12日 14:28 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのテスト4(Vivado、実機テスト)

Ultra96のDisplayPortを使用するためのテスト3(XGA解像度のdisplay_cont)”の続き。

前回は、XGA 解像度のdisplay_cont をVivado HLS 2018.3 で作成した。今回は、Vivado 2018.3 の DisplayPort_test プロジェクトの display_cont_sub IP と交換して論理合成、インプリメンテーション、ビットストリームの生成を行って、実機テストをしてみよう。

~/HDL/Ultra96/DisplayPort_test/display_cont_ip の中身を”Ultra96のDisplayPortを使用するためのテスト3(XGA解像度のdisplay_cont)”で生成したIP に入れ替えた。

Diagram で display_cont_sub_0 の ap_clk をPS の dp_video_ref_clk に接続した。
DisplayPort_test_32_190109.png
(2019/01/12 : 追記)Diagram で display_cont_sub_0 の ap_clk をPS の dp_video_ref_clk に接続したのですが、PSのdp_video_in_clk に繋ぎ忘れていたので、テストパターンが出なかった様です。

論理合成、インプリメンテーション、ビットストリームの生成を行ったが、Project Summary を見ると、Timing の値が表示されていない。dp_video_ref_clk ではタイミング制約は自動生成されないようだ。
そこで、クロック周期のタイミング制約を生成しよう。
Flow Navigator のSYNTHESIS を展開して、Open Synthesized Design をクリックし、その下のEdit Timinig Constraints をクリックする。Timing Constraints タブが開くので、Clocks -> Create Clock をクリックし、”Double Click to Create Clock constraints”をダブルクリックする。
DisplayPort_test_33_190109.png

Create Clock ダイアログが表示された。
Clock name に XGA_clock と入力して、Source objects: の右端の”…”ボタンをクリックする。
DisplayPort_test_34_190109.png

Specify Clock Source ダイアログのOptions で”*dp_video_ref_clk*”と入力してFind ボタンをクリックすると dp_video_ref_clk が出てくるので、これを選択し、右向き矢印ボタンをクリックして、Selected: のウインドウに入れる。
Set ボタンをクリックする。
DisplayPort_test_35_190109.png

Source Object に”[get_pins displayport_test_i/zynq_ultra_ps_e_0/dp_video_ref_clk]”が入った。
Period: を 15 ns に変更し、OKボタンをクリックする。
DisplayPort_test_36_190109.png

XGA_clock のエントリに設定した制約が入った。
displayport_test.xdc としてセーブした。
DisplayPort_test_37_190109.png

もう一度、インプリメンテーション、ビットストリームの生成を行った。
今度はTiming に値が入ってしかもプラスのスラックだったので、問題ない。
DisplayPort_test_38_190110.png

bootgen で displayport_test_wapper.bin を生成した。
DisplayPort_test_39_190110.png

DisplayPort_test_40_190110.png

Ultra96 のDebian を立ち上げて、SFTP でUltra96 に displayport_test_wapper.bin をアップロードした。
DisplayPort_test_41_190110.png

displayport_test_wapper.bin を/lib/firmware/ にコピーして、./lddtovray.sh でデバイスツリーをロードした。
sudo ./memwrite fd4ab070 54
sudo ./memwrite fd4aa00c ff

を実行した。
DisplayPort_test_42_190110.png

結果は、ログイン・プロンプトの画面が多少薄くなっただけで、テストパターンは表示されない。。。
dp_video_ref_clk のクロックが出ていないのだろうか?
  1. 2019年01月10日 06:27 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのテスト3(XGA解像度のdisplay_cont)

Ultra96のDisplayPortを使用するためのテスト3(実機テスト)”の続き。

前回は、SVGA の画像パターン発生用IPのdisplay_cont をZynq UltraScale+ MPSoC のLive Video 入力に接続したのだが、XGA 解像度だったので、画像パターンが流れてしまった。本来は、Zynq UltraScale+ MPSoC のdp_video_out の水平動機信号や垂直同期信号に同期させる必要があるのだが、これは、Verilog HDL で書く必要がありそうなので、とりあえず、display_cont をXGA 対応にして、様子を見てみようと思う。

Vivado HLS 2018.3 で display_cont_dp プロジェクトを作成した。
display_cont プロジェクトのC ソースコードとC テストベンチをそのまま使用して、C ソースコードの解像度をXGA に変更した。

// XGA 解像度 65 MHz
#define H_ACTIVE_VIDEO 1024
#define H_FRONT_PORCH 24
#define H_SYNC_PULSE 136
#define H_BACK_PORCH 160
#define H_SUM (H_ACTIVE_VIDEO + H_FRONT_PORCH + H_SYNC_PULSE + H_BACK_PORCH)

#define V_ACTIVE_VIDEO 768
#define V_FRONT_PORCH 2
#define V_SYNC_PULSE 6
#define V_BACK_PORCH 29
#define V_SUM (V_ACTIVE_VIDEO + V_FRONT_PORCH + V_SYNC_PULSE + V_BACK_PORCH)


display_cont_dp プロジェクトを示す。
DisplayPort_test_29_190109.png

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

XGA 画像の総ピクセル数は(1024+24+136+160)×(768+2+6+29) = 1081920 ピクセルである。そしてLatency の min も同じく 1081920 クロックとなった。

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

問題ない。
  1. 2019年01月09日 05:22 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのテスト3(実機テスト)

Ultra96のDisplayPortを使用するためのテスト2(デバイス・ツリーの作成)”の続き。

前回は、生成されたビットストリーム・ファイルをUltra96 にFTP し、ビットストリームやクロックをコンフィギュレーションするDTB ファイルを作成した。今回は、memwrite アプリを作成してUltra96 のDisplayPort を使ってみよう。

なお、参考にしているのは、ひでみさんの”FPGAの内容が薄い本”だ。本に書いてあることはなるべくブログに書かないようにするので、是非購入してください。

まずは、Ultra96 のDebian の ~/examples/displayport_test ディレクトリを示す。
DisplayPort_test_22_190108.png

memwrite.c と memwrite があるが、ひでみさんの本にある memwrite を自分で作った。
Raspberry PiでC言語版Lチカを試す(その2)レジスタを操作する”を参考にした。ありがとうございました。
memwrite.c を示す。
DisplayPort_test_23_190108.png

// memwrite.c
// 2018/01/07 : by marsee
// I referred to http://independence-sys.net/main/?p=2209
//

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define BLOCK_SIZE    4096

volatile uint32_t *reg;

int main(int argc, char **argv){
    int fd;
    void *memp;
    uint32_t phy_addr;
    uint32_t phy_addr_base;
    int addr, wd;
    uint32_t write_data;
    
    if (argc != 3){
        fprintf(stderr, "Usage : ./memwrite <address(hex)> <write data(hex)>\n");
        exit(-1);
    }
    
    fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (fd == -1){
        fprintf(stderr, "/dev/mem open error\n");
        exit(-1);
    }
    
    sscanf(argv[1], "%x", &addr);
    phy_addr = (uint32_t)addr;
    phy_addr_base = phy_addr & 0xfffff000; // 4k byte boundary
    memp = mmap(NULL, BLOCK_SIZE,
                    PROT_READ | PROT_WRITE, MAP_SHARED,
                    fd, phy_addr_base );
    if ((int64_t)memp == -1){
        fprintf(stderr,"/dev/mem map error\n");
        exit(-1);
    }
    close(fd);
    
    reg = (uint32_t *)memp;
    int index = (phy_addr & 0xfff)/sizeof(uint32_t);
    
    sscanf(argv[2], "%x", &wd);
    write_data = (uint32_t)wd;
    reg[index] = write_data;
    
    munmap((void *)memp, BLOCK_SIZE);
    return(0);
}


gcc -o memwrite memwrite.c
でコンパイルして、memwrite を生成した。

さてやってみよう。最初に、デバイスツリーをロードする。
./lddtovray.sh
を実行した。
DisplayPort_test_20_190108.png

ロードされたデバイスツリーのメッセージを示す。
DisplayPort_test_21_190108.png

現在の初期画面を示す。ログイン・プロンプトが表示されている。画像のサイズは 1024 x 768 の XGA だった。
DisplayPort_test_26_190108.jpg

memwrite で 0xfdab070 の AV_BUF_OUTPUT_AUDIO_VIDEO_SELECT (DP) Register に 0x54 を書いた。
memwrite で 0xfd4aa00c の V_BLEND_SET_GLOBAL_ALPHA_REG (DP) Register に 0xff を書いた。
sudo ./memwrite fd4ab070 54
sudo ./memwrite fd4aa00c ff

DisplayPort_test_24_190108.png
DisplayPort_test_27_190108.jpg

画像は上に流れている。XGA のところにSVGA の画像を入れているので仕方ない。

次にLiveVideo のみにしたかったので、memwrite で 0xfdab070 の AV_BUF_OUTPUT_AUDIO_VIDEO_SELECT (DP) Register に 0x00 を書いた。
sudo ./memwrite fd4ab070 00
DisplayPort_test_25_190108.png

すると、ログイン・プロンプトは消えた。
DisplayPort_test_28_190108.jpg

だが、同様に画像は上に流れている。
またしばらくすると、Debian が落ちてしまった。まずいのかもしれない?

(追記)
sudo ./memwrite fd4ab070 00 に設定してしばらく置いておいたが、問題なかった。大丈夫そうだ。
  1. 2019年01月08日 05:24 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのテスト2(デバイス・ツリーの作成)

Ultra96のDisplayPortを使用するためのテスト1(Vivado プロジェクトを作成した)”の続き。

前回は、Ultra96 ボードのDisplayPort を使ってみたいということで、ひでみさんの”FPGAの内容が薄い本”を参照して、やってみることにした。そして、SVGA のテストバターンを画像出力するIP をZynq UltraScale+ MPSoC のLiveVideo 端子に接続し、ビットストリームを生成した。今回は、生成されたビットストリーム・ファイルをUltra96 にFTP し、ビットストリームやクロックをコンフィギュレーションするDTB ファイルを作成する。

Vivado 2018.3 でハードウェアをエクスポートした。
SDK を立ち上げた。
DisplayPort_test_14_190107.png

displayport_test_wrapper.bif を作成した。

all:
{
    [destination_device = pl] displayport_test_wrapper.bit
}


コマンドを実行して、displayport_test_wrapper.bin を作成した。
bootgen -image displayport_test_wrapper.bif -arch zynqmp -w -o displayport_test_wrapper.bin
DisplayPort_test_15_190107.png

DisplayPort_test_16_190107.png

Ultra96 のDebian を立ち上げて、displayport_test_wrapper.bin をSFTP でアップロードした。
DisplayPort_test_17_190107.png

displayport_test_wrapper.bin を /lib/firmware/ にコピーした。
sudo cp displayport_test_wrapper.bin /lib/firmware/
DisplayPort_test_18_190107.png

fpga-load.dts を作成した。

/dts-v1/;
/ {
    fragment@0 {
        target-path = "/fpga-full";
        __overlay__ {
            firmware-name = "displayport_test_wrapper.bin";
        };
    };
};


fclk0-zynqmp.dts を作成した。

/dts-v1/;/plugin/;
/ {
    fragment@0 {
        target-path = "/amba";
        __overlay__ {
            fclk0 {
                compatible    = "ikwzm,fclkcfg-0.10.a";
                clocks        = <&clk 0x47>;
                insert-rate   = "40000000";
                insert-enable = <1>;
                remove-rate   = "1000000";
                remove-enable = <0>;
            };
        };
    };
};


2 つの DTS ファイルをコンパイルした。
dtc -I dts -O dtb -o fpga-load.dtb fpga-load.dts
dtc -I dts -O dtb -o fclk0-zynqmp.dtb fclk0-zynqmp.dts

DisplayPort_test_19_190107.png

lddtovray.sh と rmdtovray.sh を作成した。
lddtovray.sh

#!/bin/bash

sudo mkdir /config/device-tree/overlays/fpga
sudo cp fpga-load.dtb /config/device-tree/overlays/fpga/dtbo
sudo mkdir /config/device-tree/overlays/fclk0
sudo cp fclk0-zynqmp.dtb /config/device-tree/overlays/fclk0/dtbo


rmdtovray.sh

#!/bin/bash

sudo rmdir /config/device-tree/overlays/fclk0
sudo rmdir /config/device-tree/overlays/fpga/


  1. 2019年01月07日 06:21 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

Ultra96のDisplayPortを使用するためのテスト1(Vivado プロジェクトを作成した)

Ultra96 ボードのDisplayPort のことを調べていたが、DisplayPort を使用するための情報がひでみさんの”FPGAの内容が薄い本”に載っていたので、もう一度試してみようと思う。
Ultra96用PMOD拡張ボードを使って、PMOD VGAで画像出力1(Vivado HLS編)”で使用したSVGA 解像度のパターン・ジェネレータのdisplay_cont_sub IP をDisplayPort に接続して、画像を表示してみたい。
なお、”Ultra96 ボードのDisplayPort について”を参考にしている。

まずは、Vivado 2018.3 でUltra96v1 ボード・ファイルを使用して、DisplayPort_test プロジェクトを作成した。
DisplayPort_test_1_190106.png

DisplayPort_test プロジェクトのディレクトリにdisplay_cont_ip をコピー&ペーストした。
DisplayPort_test_2_190106.png

IP Catalog を開いて、Display_cont_sub をリポジトリに登録した。
DisplayPort_test_3_190106.png

displayport_test ブロック・デザインを作成した。
Zynq UltraScale+ MPSoC をAdd IP して、Display_cont_sub もAdd IP してオートで接続した状態。
DisplayPort_test_4_190106.png

Zynq UltraScale+ MPSoC をダブルクリックして設定を行った。
PS-PL Configuration で AXI HPM0 FPD, AXI HPM1 FPD のチェックを外した。
DisplayPort_test_5_190106.png

I/O Configuration では、DisplayPort にチェックがついていた。
DisplayPort_test_6_190106.png

Clock Configuration では、Input Clocks のGT Lane Reference frequency のDisplayPort はRef Clk1 を使用して 27 MHz と設定されていた。
DisplayPort_test_7_190106.png

Page Navigator のPS-PL Configuration をクリックしてGeneral -> Ohters を開き、Live Video を 0 から 1 に変更する。
DisplayPort_test_8_190106.png

Clock Configuration の Output Clocks の Low Power Domain Clocks の PL Fablic Clocks の PL0 を 40 MHz に設定する。
DisplayPort_test_9_190106.png

設定ダイアログをOK ボタンをクリックして閉じると、Zynq UltraScale+ MPSoC にDisplayPort のポートが追加されていた。
DisplayPort_test_10_190106.png

Concat, Constant を追加して、Display_cont_sub の red, green, blue ポートから dp_live_video_in_pixel1 に接続した。
DisplayPort_test_11_190106.png

displayport_test ブロック・デザインの HDL ラッパー・ファイルを作成した。
DisplayPort_test_12_190106.png

論理合成、インプリメンテーション、ビットストリームの生成を行った。成功だ。
DisplayPort_test_13_190106.png
  1. 2019年01月06日 05:34 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

ZYBO Z7-20でのMNISTの実装にOV5642を使用する3

ZYBO Z7-20でのMNISTの実装にOV5642を使用する2”の続き。

前回は、OV5642 カメラ画像を垂直方向にフリップした画像で、ディスプレイに表示することができた。その後、垂直方向にフリップするDMA の vflip_dma_write もできたので、これを使用してMNIST 手書き数字判定システムを修正して行こう。

Vivado 2018.3 で MNIST_CNN_Z7_OV5642 プロジェクトを MNIST_CNN_Z7_OV5642vfd_183 ディレクトリに作成した。
MNIST_CNN_Z7_OV5642_1_190105.png

MNIST_CNN_Z7 ブロック・デザインを示す。
MNIST_CNN_Z7_OV5642_2_190105.png

FCLK_CLK2 は 24 MHz に設定した。

次に、camera_interface モジュールを示す。
MNIST_CNN_Z7_OV5642_3_190105.png

AXI_VDMA を vflip_dma_write IP に変更してある。

Address Editor を示す。
MNIST_CNN_Z7_OV5642_8_190105.png

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

タイミングも満足している。成功だ。
ハードウェアをエクスポートして、SDK を立ち上げた。
mnist_cnn_soft_vflip プロジェクトを作成して、mnist_cnn_soft_vflip.c アプリケーション・ソフトを作成した。
MNIST_CNN_Z7_OV5642_5_190105.png

FPGA にビット・ファイルをダウンロードして、mnist_cnn_soft_vflip.elf を起動した。
OV5642のカメラ画像が表示された。
MNIST_CNN_Z7_OV5642_7_190105.jpg

gtk_term を立ち上げて、キーでピンクの四角を数字の 3 に合わせて、r コマンドでmnist_cnn の結果を表示させた。
MNIST_CNN_Z7_OV5642_6_190105.png

ハードウェア、ソフトウェアともに 3 と判定している。成功だ。

最後に、mnist_cnn_soft_vflip.c を載せておく。

/*
 * mnist_conv_soft_vflip.c (for ZYBO Z7-20, for OV5642)
 *
 *  Created on: 2019/01/03
 *      Author: marsee
 */

#include <stdio.h>
#include <stdlib.h>
#include "xil_io.h"
#include "xparameters.h"
#include "sleep.h"
#include "xtime_l.h"

#include "xvflip_dma_write.h"
#include "xmnist_conv_nn.h"
#include "xsquare_frame_gen.h"
#include "af1_bias_float.h"
#include "af1_weight_float.h"
#include "af2_bias_float.h"
#include "af2_weight_float.h"
#include "conv1_bias_float.h"
#include "conv1_weight_float.h"

#define FRAME_BUFFER_ADDRESS 0x10000000
#define NUMBER_OF_WRITE_FRAMES  3 // Note: If not at least 3 or more, the image is not displayed in succession.

#define HORIZONTAL_PIXELS   800
#define VERTICAL_LINES      600
#define PIXEL_NUM_OF_BYTES  4

int max_int(int out[10]);
int max_float(float out[10]);
int mnist_conv_nn_float(int in[22400], int addr_offset, float out[10]);
float conv_rgb2y_soft(int rgb);

float buf[28][28];
float conv_out[10][24][24];
float pool_out[10][12][12];
float dot1[100];
float dot2[10];

void cam_i2c_init(volatile unsigned *ov5642_i2c_axi_lites) {
    ov5642_i2c_axi_lites[64] = 0x2; // reset tx fifo ,address is 0x100, i2c_control_reg
    ov5642_i2c_axi_lites[64] = 0x1; // enable i2c
}

void cam_i2x_write_sync(void) {
    // unsigned c;

    // c = *cam_i2c_rx_fifo;
    // while ((c & 0x84) != 0x80)
        // c = *cam_i2c_rx_fifo; // No Bus Busy and TX_FIFO_Empty = 1
    usleep(1000);
}

void cam_i2c_write(volatile unsigned *ov5642_axi_iic, unsigned int device_addr, unsigned int write_addr, unsigned int write_data){
    ov5642_axi_iic[66] = 0x100 | (device_addr & 0xfe); // Slave IIC Write Address, address is 0x108, i2c_tx_fifo
    ov5642_axi_iic[66] = (write_addr >> 8) & 0xff;  // address upper byte
    ov5642_axi_iic[66] = write_addr & 0xff;           // address lower byte
    ov5642_axi_iic[66] = 0x200 | (write_data & 0xff);      // data
    cam_i2x_write_sync();
}


int cam_reg_set(volatile unsigned *axi_iic, unsigned int device_addr);

int main(){
    XMnist_conv_nn mcnn;
    XSquare_frame_gen sf_gen;
    XVflip_dma_write vfdma;
    int inbyte_in;
    int xval, yval;
    int i, res;
    int result[10];
    float result_float[10];
    int max_id;
    int result_disp = 0;
    int conv_addr;
    int max_id_float;
    XTime start_time, end_time;

    // vflip_dma_write Initialization sequence
    XVflip_dma_write_Initialize(&vfdma, 0);

    // Frame buffer address set
    XVflip_dma_write_Set_fb0_V(&vfdma, FRAME_BUFFER_ADDRESS);
    XVflip_dma_write_Set_fb1_V(&vfdma, FRAME_BUFFER_ADDRESS);
    XVflip_dma_write_Set_fb2_V(&vfdma, FRAME_BUFFER_ADDRESS);
    
    // Mnist_conv_nn, Square_frame_gen Initialize
    XMnist_conv_nn_Initialize(&mcnn, 0);
    XSquare_frame_gen_Initialize(&sf_gen, 0);

    // square_frame_gen initialize
    XSquare_frame_gen_Set_x_pos(&sf_gen, HORIZONTAL_PIXELS/2);
    xval = HORIZONTAL_PIXELS/2;
    XSquare_frame_gen_Set_y_pos(&sf_gen, VERTICAL_LINES/2);
    yval = VERTICAL_LINES/2;
    XSquare_frame_gen_Set_width(&sf_gen, 28);
    XSquare_frame_gen_Set_height(&sf_gen, 28);
    XSquare_frame_gen_Set_off_on(&sf_gen, 1); // on

    // XSquare_frame_gen start
    XSquare_frame_gen_DisableAutoRestart(&sf_gen);
    while(!XSquare_frame_gen_IsIdle(&sf_gen)) ;
    XSquare_frame_gen_Start(&sf_gen);
    XSquare_frame_gen_EnableAutoRestart(&sf_gen);

    // mnist_conv_nn initialize
    XMnist_conv_nn_Set_addr_offset(&mcnn, HORIZONTAL_PIXELS/2);
    XMnist_conv_nn_Set_in_r(&mcnn, FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*(VERTICAL_LINES/2)*sizeof(int));

    // axis_switch_1, 1to2 ,Select M00_AXIS
    // Refer to http://marsee101.blog19.fc2.com/blog-entry-3177.html
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_1_BASEADDR+0x40), 0x80000000); // disable
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_1_BASEADDR+0x44), 0x0); // square_frame_gen enable
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_1_BASEADDR), 0x2); // Commit registers

    // axis_switch_0, 2to1, Select S00_AXIS
    // Refer to http://marsee101.blog19.fc2.com/blog-entry-3177.html
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_0_BASEADDR+0x40), 0x1);
    Xil_Out32((XPAR_CAMERA_INTERFACE_AXIS_SWITCH_0_BASEADDR), 0x2); // Commit registers

    // vflip_dma_write start
    XVflip_dma_write_Start(&vfdma);
    XVflip_dma_write_EnableAutoRestart(&vfdma);
    
    // ov5642_inf_axis_0, axi_iic_0, bitmap_disp_cntrler_axi_master_0
    volatile unsigned int *bmdc_axi_lites;
    volatile unsigned int *ov5642_axi_lites;
    volatile unsigned int *ov5642_i2c_axi_lites;

    bmdc_axi_lites = (volatile unsigned *)XPAR_BMD_CONTROLLER_AXIM_0_BASEADDR;
    ov5642_axi_lites = (volatile unsigned *)XPAR_CAMERA_INTERFACE_MT9D111_INF_AXIS_0_BASEADDR;
    ov5642_i2c_axi_lites = (volatile unsigned *)XPAR_CAMERA_INTERFACE_AXI_IIC_0_BASEADDR;

    bmdc_axi_lites[0] = (volatile unsigned int)FRAME_BUFFER_ADDRESS; // Bitmap Display Controller start
    //ov5642_axi_lites[0] = (volatile unsigned int)FRAME_BUFFER_ADDRESS; // Camera Interface start (Address is dummy)

    // CMOS Camera initialize, MT9D111
    cam_i2c_init(ov5642_i2c_axi_lites);

    cam_reg_set(ov5642_i2c_axi_lites, 0x78); // OV5642 register set

    ov5642_axi_lites[0] = (volatile unsigned int)FRAME_BUFFER_ADDRESS; // Camera Interface start (Address is dummy)
    ov5642_axi_lites[1] = 0; // One_shot_mode is disabled

    while(1){
        printf("mnist_conv_nn_test, <h> : left, <k> : up, <j> : down, <l> : right, <q> : exit\n");
        inbyte_in = inbyte();
        switch(inbyte_in) {
            case 'h' : // left
            case 'H' : // left -5
                if(inbyte_in == 'h' && xval > 0)
                    --xval;
                else if(inbyte_in == 'H' && xval >= 5)
                    xval -= 5;
                XSquare_frame_gen_Set_x_pos(&sf_gen, xval);
                XMnist_conv_nn_Set_addr_offset(&mcnn, xval);
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'l' : // right
            case 'L' : // right +5
                if(inbyte_in == 'l' && xval < HORIZONTAL_PIXELS-28)
                    xval++;
                else if(inbyte_in == 'L' && xval <= HORIZONTAL_PIXELS-28-5)
                    xval += 5;
                XSquare_frame_gen_Set_x_pos(&sf_gen, xval);
                XMnist_conv_nn_Set_addr_offset(&mcnn, xval);
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'k' : // up
            case 'K' : // up +5 (vflip)
                if(inbyte_in == 'k' && yval > 0)
                    yval++;
                else if(inbyte_in == 'K' && yval >= 5)
                    yval += 5;
                XSquare_frame_gen_Set_y_pos(&sf_gen, yval);
                XMnist_conv_nn_Set_in_r(&mcnn, FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*(VERTICAL_LINES-1-yval-28)*sizeof(int));
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'j' : // down
            case 'J' : // down -5 (vflip)
                if(inbyte_in == 'j' && yval < VERTICAL_LINES-28)
                    --yval;
                else if(inbyte_in == 'J' && yval <= VERTICAL_LINES-28-5)
                    yval -= 5;
                XSquare_frame_gen_Set_y_pos(&sf_gen, yval);
                XMnist_conv_nn_Set_in_r(&mcnn, FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*(VERTICAL_LINES-1-yval-28)*sizeof(int));
                printf("X_POS = %d, Y_POS = %d\n", xval, yval);
                break;
            case 'r' : // result check
                result_disp = 1;
                break;
            case 'q' : // exit
                return(0);
        }

        if(result_disp){
            printf("\nHardware\n");
            // XMnist_conv_nn start
            XMnist_conv_nn_DisableAutoRestart(&mcnn);
            while(!XMnist_conv_nn_IsIdle(&mcnn));
            XTime_GetTime(&start_time);
            XMnist_conv_nn_Start(&mcnn);
            while(!XMnist_conv_nn_IsIdle(&mcnn));
            XTime_GetTime(&end_time);
            printf("conv_time = %f ms\n", (float)((long)end_time-(long)start_time)/325000.0);

            // mnist cnn result check
            for(i=0; i<5; i++){
                XMnist_conv_nn_Read_out_V_Words(&mcnn, i, &res, 1);
                result[i*2] = res & 0x0fff;
                if(result[i*2] & 0x800) // minus
                    result[i*2] = 0xfffff000 | result[i*2]; // Sign extension

                result[i*2+1] = (res & 0x0fff0000) >> 16;
                if(result[i*2+1] & 0x800) // minus
                    result[i*2+1] = 0xfffff000 | result[i*2+1]; // Sign extension
            }

            max_id = max_int(result);

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

            printf("\nSoftware\n");
            conv_addr = FRAME_BUFFER_ADDRESS+HORIZONTAL_PIXELS*(VERTICAL_LINES-1-yval-28)*sizeof(int);
            XTime_GetTime(&start_time);
            mnist_conv_nn_float((int *)conv_addr, xval, result_float);
            XTime_GetTime(&end_time);
            max_id_float = max_float(result_float);
            printf("conv_time = %f ms\n", (float)((long)end_time-(long)start_time)/325000.0);
            for(i=0; i<10; i++){
                printf("result_float[%d] = %f\n", i, result_float[i]);
            }
            printf("max_id_float = %d\n", max_id_float);

            result_disp = 0;
        }
    }
}

int max_int(int out[10]){
    int max_id;
    int max, i;

    for(i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

int mnist_conv_nn_float(int in[22400], int addr_offset, float out[10]){

    // 手書き数字の値を表示
    /*for (int i=0; i<28; i++){
        for (int j=0; j<800; j++){
            if (j>=addr_offset && j<addr_offset+28)
                printf("%2x, ", (int)(conv_rgb2y_soft(in[i*800+j])*256.0));
        }
        printf("\n");
    } */

    buf_copy1: for(int i=0; i<28; i++){
        buf_copy2: for(int j=0; j<800; j++){
            if (j>=addr_offset && j<addr_offset+28)
                buf[i][j-addr_offset] = (float)0.99609375 - (float)conv_rgb2y_soft(in[i*800+j]);
        }
    }

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<10; i++){ // カーネルの個数
        CONV2: for(int j=0; j<24; j++){
            CONV3: for(int k=0; k<24; k++){
                conv_out[i][j][k] = 0;
                CONV4: for(int m=0; m<5; m++){
                    CONV5: for(int n=0; n<5; n++){
                        conv_out[i][j][k] += buf[j+m][k+n] * conv1_fweight[i][0][m][n];
                    }
                }
                conv_out[i][j][k] += conv1_fbias[i];

                if(conv_out[i][j][k]<0) // ReLU
                    conv_out[i][j][k] = 0;
            }
        }
    }

    // Pooling Kernel = 2 x 2, Stride = 2
    POOL1: for(int i=0; i<10; i++){
        POOL2: for(int j=0; j<24; j += 2){
            POOL3: for(int k=0; k<24; k += 2){
                POOL4: for(int m=0; m<2; m++){
                    POOL5: for(int n=0; n<2; n++){
                        if(m==0 && n==0){
                            pool_out[i][j/2][k/2] = conv_out[i][j][k];
                        } else if(pool_out[i][j/2][k/2] < conv_out[i][j+m][k+n]){
                            pool_out[i][j/2][k/2] = conv_out[i][j+m][k+n];
                        }
                    }
                }
            }
        }
    }

    af1_dot1: for(int col=0; col<100; col++){
        dot1[col] = 0;
        af1_dot2: for(int i=0; i<10; i++){
            af1_dot3: for(int j=0; j<12; j++){
                af1_dot4: for(int k=0; k<12; k++){
                    dot1[col] += pool_out[i][j][k]*af1_fweight[i*12*12+j*12+k][col];
                }
            }
        }
        dot1[col] += af1_fbias[col];

        if(dot1[col] < 0)   // ReLU
            dot1[col] = 0;
    }

    af2_dot1: for(int col=0; col<10; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<100; row++){
            dot2[col] += dot1[row]*af2_fweight[row][col];
        }
        dot2[col] += af2_fbias[col];

        out[col] = dot2[col];
    }

    return(0);
}

int max_float(float out[10]){
    int max_id;
    float max;

    for(int i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}


// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
// 2017/06/30 : retval を float にした
float conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;
    float y_float;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8; // 256で割る

    if (y >= 256)
        y = 255;

    y_float = (float)y/256.0;

    return(y_float);
}

int cam_reg_set(volatile unsigned *axi_iic, unsigned int device_addr){
    cam_i2c_write(axi_iic, device_addr, 0x3103, 0x93);
    cam_i2c_write(axi_iic, device_addr, 0x3008, 0x82);
    cam_i2c_write(axi_iic, device_addr, 0x3017, 0x7f);
    cam_i2c_write(axi_iic, device_addr, 0x3018, 0xfc);
    cam_i2c_write(axi_iic, device_addr, 0x3810, 0xc2);
    cam_i2c_write(axi_iic, device_addr, 0x3615, 0xf0);
    cam_i2c_write(axi_iic, device_addr, 0x3000, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3001, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3002, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3003, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3000, 0xf8);
    cam_i2c_write(axi_iic, device_addr, 0x3001, 0x48);
    cam_i2c_write(axi_iic, device_addr, 0x3002, 0x5c);
    cam_i2c_write(axi_iic, device_addr, 0x3003, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x3004, 0x07);
    cam_i2c_write(axi_iic, device_addr, 0x3005, 0xb7);
    cam_i2c_write(axi_iic, device_addr, 0x3006, 0x43);
    cam_i2c_write(axi_iic, device_addr, 0x3007, 0x37);
    cam_i2c_write(axi_iic, device_addr, 0x3011, 0x08); // 0x08 - 15fps, 0x10 - 30fps
    cam_i2c_write(axi_iic, device_addr, 0x3010, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x460c, 0x22);
    cam_i2c_write(axi_iic, device_addr, 0x3815, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x370d, 0x06);
    cam_i2c_write(axi_iic, device_addr, 0x370c, 0xa0);
    cam_i2c_write(axi_iic, device_addr, 0x3602, 0xfc);
    cam_i2c_write(axi_iic, device_addr, 0x3612, 0xff);
    cam_i2c_write(axi_iic, device_addr, 0x3634, 0xc0);
    cam_i2c_write(axi_iic, device_addr, 0x3613, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3605, 0x7c);
    cam_i2c_write(axi_iic, device_addr, 0x3621, 0x09);
    cam_i2c_write(axi_iic, device_addr, 0x3622, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3604, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x3603, 0xa7);
    cam_i2c_write(axi_iic, device_addr, 0x3603, 0x27);
    cam_i2c_write(axi_iic, device_addr, 0x4000, 0x21);
    cam_i2c_write(axi_iic, device_addr, 0x401d, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x3600, 0x54);
    cam_i2c_write(axi_iic, device_addr, 0x3605, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x3606, 0x3f);
    cam_i2c_write(axi_iic, device_addr, 0x3c01, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5000, 0x4f);
    cam_i2c_write(axi_iic, device_addr, 0x5020, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x5181, 0x79);
    cam_i2c_write(axi_iic, device_addr, 0x5182, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5185, 0x22);
    cam_i2c_write(axi_iic, device_addr, 0x5197, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x5001, 0xff);
    cam_i2c_write(axi_iic, device_addr, 0x5500, 0x0a);
    cam_i2c_write(axi_iic, device_addr, 0x5504, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5505, 0x7f);
    cam_i2c_write(axi_iic, device_addr, 0x5080, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x300e, 0x18);
    cam_i2c_write(axi_iic, device_addr, 0x4610, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x471d, 0x05);
    cam_i2c_write(axi_iic, device_addr, 0x4708, 0x06);
    cam_i2c_write(axi_iic, device_addr, 0x3710, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x3632, 0x41);
    cam_i2c_write(axi_iic, device_addr, 0x3702, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x3620, 0x37);
    cam_i2c_write(axi_iic, device_addr, 0x3631, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x3808, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x3809, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x380a, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x380b, 0xe0);
    cam_i2c_write(axi_iic, device_addr, 0x380e, 0x07);
    cam_i2c_write(axi_iic, device_addr, 0x380f, 0xd0);
    cam_i2c_write(axi_iic, device_addr, 0x501f, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5000, 0x4f);
    cam_i2c_write(axi_iic, device_addr, 0x4300, 0x61); // RGB565
    cam_i2c_write(axi_iic, device_addr, 0x3503, 0x07);
    cam_i2c_write(axi_iic, device_addr, 0x3501, 0x73);
    cam_i2c_write(axi_iic, device_addr, 0x3502, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x350b, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3503, 0x07);
    cam_i2c_write(axi_iic, device_addr, 0x3824, 0x11);
    cam_i2c_write(axi_iic, device_addr, 0x3501, 0x1e);
    cam_i2c_write(axi_iic, device_addr, 0x3502, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x350b, 0x7f);
    cam_i2c_write(axi_iic, device_addr, 0x380c, 0x0c);
    cam_i2c_write(axi_iic, device_addr, 0x380d, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x380e, 0x03);
    cam_i2c_write(axi_iic, device_addr, 0x380f, 0xe8);
    cam_i2c_write(axi_iic, device_addr, 0x3a0d, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x3a0e, 0x03);
    cam_i2c_write(axi_iic, device_addr, 0x3818, 0xc1);
    cam_i2c_write(axi_iic, device_addr, 0x3705, 0xdb);
    cam_i2c_write(axi_iic, device_addr, 0x370a, 0x81);
    cam_i2c_write(axi_iic, device_addr, 0x3801, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x3621, 0xc7);
    cam_i2c_write(axi_iic, device_addr, 0x3801, 0x50);
    cam_i2c_write(axi_iic, device_addr, 0x3803, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x3827, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x3810, 0xc0);
    cam_i2c_write(axi_iic, device_addr, 0x3804, 0x05);
    cam_i2c_write(axi_iic, device_addr, 0x3805, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5682, 0x05);
    cam_i2c_write(axi_iic, device_addr, 0x5683, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3806, 0x03);
    cam_i2c_write(axi_iic, device_addr, 0x3807, 0xc0);
    cam_i2c_write(axi_iic, device_addr, 0x5686, 0x03);
    cam_i2c_write(axi_iic, device_addr, 0x5687, 0xc0);
    cam_i2c_write(axi_iic, device_addr, 0x3a00, 0x78);
    cam_i2c_write(axi_iic, device_addr, 0x3a1a, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x3a13, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x3a18, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3a19, 0x7c);
    cam_i2c_write(axi_iic, device_addr, 0x3a08, 0x12);
    cam_i2c_write(axi_iic, device_addr, 0x3a09, 0xc0);
    cam_i2c_write(axi_iic, device_addr, 0x3a0a, 0x0f);
    cam_i2c_write(axi_iic, device_addr, 0x3a0b, 0xa0);
    cam_i2c_write(axi_iic, device_addr, 0x3004, 0xff);
    cam_i2c_write(axi_iic, device_addr, 0x350c, 0x07);
    cam_i2c_write(axi_iic, device_addr, 0x350d, 0xd0);
    cam_i2c_write(axi_iic, device_addr, 0x3500, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3501, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3502, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x350a, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x350b, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3503, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x528a, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x528b, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x528c, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x528d, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x528e, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x528f, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5290, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5292, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5293, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x5294, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5295, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x5296, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5297, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x5298, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5299, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x529a, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x529b, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x529c, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x529d, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x529e, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x529f, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x3a0f, 0x3c);
    cam_i2c_write(axi_iic, device_addr, 0x3a10, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x3a1b, 0x3c);
    cam_i2c_write(axi_iic, device_addr, 0x3a1e, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x3a11, 0x70);
    cam_i2c_write(axi_iic, device_addr, 0x3a1f, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x3030, 0x0b);
    cam_i2c_write(axi_iic, device_addr, 0x3a02, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3a03, 0x7d);
    cam_i2c_write(axi_iic, device_addr, 0x3a04, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3a14, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3a15, 0x7d);
    cam_i2c_write(axi_iic, device_addr, 0x3a16, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3a00, 0x78);
    cam_i2c_write(axi_iic, device_addr, 0x3a08, 0x09);
    cam_i2c_write(axi_iic, device_addr, 0x3a09, 0x60);
    cam_i2c_write(axi_iic, device_addr, 0x3a0a, 0x07);
    cam_i2c_write(axi_iic, device_addr, 0x3a0b, 0xd0);
    cam_i2c_write(axi_iic, device_addr, 0x3a0d, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x3a0e, 0x06);
    cam_i2c_write(axi_iic, device_addr, 0x5193, 0x70);
    cam_i2c_write(axi_iic, device_addr, 0x3620, 0x57);
    cam_i2c_write(axi_iic, device_addr, 0x3703, 0x98);
    cam_i2c_write(axi_iic, device_addr, 0x3704, 0x1c);
    cam_i2c_write(axi_iic, device_addr, 0x589b, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x589a, 0xc5);
    cam_i2c_write(axi_iic, device_addr, 0x528a, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x528b, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x528c, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x528d, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x528e, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x528f, 0x28);
    cam_i2c_write(axi_iic, device_addr, 0x5290, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x5292, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5293, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5294, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5295, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x5296, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5297, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x5298, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5299, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x529a, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x529b, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x529c, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x529d, 0x28);
    cam_i2c_write(axi_iic, device_addr, 0x529e, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x529f, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x5282, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5300, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5301, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5302, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5303, 0x7c);
    cam_i2c_write(axi_iic, device_addr, 0x530c, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x530d, 0x0c);
    cam_i2c_write(axi_iic, device_addr, 0x530e, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x530f, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5310, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5311, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5308, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5309, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x5304, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5305, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x5306, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5307, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5314, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x5315, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5319, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x5316, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5317, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x5318, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x5380, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x5381, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5382, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5383, 0x4e);
    cam_i2c_write(axi_iic, device_addr, 0x5384, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5385, 0x0f);
    cam_i2c_write(axi_iic, device_addr, 0x5386, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5387, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5388, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x5389, 0x15);
    cam_i2c_write(axi_iic, device_addr, 0x538a, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x538b, 0x31);
    cam_i2c_write(axi_iic, device_addr, 0x538c, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x538d, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x538e, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x538f, 0x0f);
    cam_i2c_write(axi_iic, device_addr, 0x5390, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5391, 0xab);
    cam_i2c_write(axi_iic, device_addr, 0x5392, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5393, 0xa2);
    cam_i2c_write(axi_iic, device_addr, 0x5394, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x5480, 0x14);
    cam_i2c_write(axi_iic, device_addr, 0x5481, 0x21);
    cam_i2c_write(axi_iic, device_addr, 0x5482, 0x36);
    cam_i2c_write(axi_iic, device_addr, 0x5483, 0x57);
    cam_i2c_write(axi_iic, device_addr, 0x5484, 0x65);
    cam_i2c_write(axi_iic, device_addr, 0x5485, 0x71);
    cam_i2c_write(axi_iic, device_addr, 0x5486, 0x7d);
    cam_i2c_write(axi_iic, device_addr, 0x5487, 0x87);
    cam_i2c_write(axi_iic, device_addr, 0x5488, 0x91);
    cam_i2c_write(axi_iic, device_addr, 0x5489, 0x9a);
    cam_i2c_write(axi_iic, device_addr, 0x548a, 0xaa);
    cam_i2c_write(axi_iic, device_addr, 0x548b, 0xb8);
    cam_i2c_write(axi_iic, device_addr, 0x548c, 0xcd);
    cam_i2c_write(axi_iic, device_addr, 0x548d, 0xdd);
    cam_i2c_write(axi_iic, device_addr, 0x548e, 0xea);
    cam_i2c_write(axi_iic, device_addr, 0x548f, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5490, 0x05);
    cam_i2c_write(axi_iic, device_addr, 0x5491, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5492, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x5493, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5494, 0x03);
    cam_i2c_write(axi_iic, device_addr, 0x5495, 0x60);
    cam_i2c_write(axi_iic, device_addr, 0x5496, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x5497, 0xb8);
    cam_i2c_write(axi_iic, device_addr, 0x5498, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x5499, 0x86);
    cam_i2c_write(axi_iic, device_addr, 0x549a, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x549b, 0x5b);
    cam_i2c_write(axi_iic, device_addr, 0x549c, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x549d, 0x3b);
    cam_i2c_write(axi_iic, device_addr, 0x549e, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x549f, 0x1c);
    cam_i2c_write(axi_iic, device_addr, 0x54a0, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x54a1, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x54a2, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x54a3, 0xed);
    cam_i2c_write(axi_iic, device_addr, 0x54a4, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x54a5, 0xc5);
    cam_i2c_write(axi_iic, device_addr, 0x54a6, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x54a7, 0xa5);
    cam_i2c_write(axi_iic, device_addr, 0x54a8, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x54a9, 0x6c);
    cam_i2c_write(axi_iic, device_addr, 0x54aa, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x54ab, 0x41);
    cam_i2c_write(axi_iic, device_addr, 0x54ac, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x54ad, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x54ae, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x54af, 0x16);
    cam_i2c_write(axi_iic, device_addr, 0x3406, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5192, 0x04); // 0x04
    cam_i2c_write(axi_iic, device_addr, 0x5191, 0xf8); // 0xf8
    cam_i2c_write(axi_iic, device_addr, 0x5193, 0x70);
    cam_i2c_write(axi_iic, device_addr, 0x5194, 0xf0);
    cam_i2c_write(axi_iic, device_addr, 0x5195, 0xf0);
    cam_i2c_write(axi_iic, device_addr, 0x518d, 0x3d);
    cam_i2c_write(axi_iic, device_addr, 0x518f, 0x54);
    cam_i2c_write(axi_iic, device_addr, 0x518e, 0x3d);
    cam_i2c_write(axi_iic, device_addr, 0x5190, 0x54);
    cam_i2c_write(axi_iic, device_addr, 0x518b, 0xc0);
    cam_i2c_write(axi_iic, device_addr, 0x518c, 0xbd);
    cam_i2c_write(axi_iic, device_addr, 0x5187, 0x18);
    cam_i2c_write(axi_iic, device_addr, 0x5188, 0x18);
    cam_i2c_write(axi_iic, device_addr, 0x5189, 0x6e);
    cam_i2c_write(axi_iic, device_addr, 0x518a, 0x68);
    cam_i2c_write(axi_iic, device_addr, 0x5186, 0x1c);
    cam_i2c_write(axi_iic, device_addr, 0x5181, 0x50);
    cam_i2c_write(axi_iic, device_addr, 0x5184, 0x25);
    cam_i2c_write(axi_iic, device_addr, 0x5182, 0x11);
    cam_i2c_write(axi_iic, device_addr, 0x5183, 0x14);
    cam_i2c_write(axi_iic, device_addr, 0x5184, 0x25);
    cam_i2c_write(axi_iic, device_addr, 0x5185, 0x24);
    cam_i2c_write(axi_iic, device_addr, 0x5025, 0x82);
    cam_i2c_write(axi_iic, device_addr, 0x5583, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x5584, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x5580, 0x02); // 0x02
    cam_i2c_write(axi_iic, device_addr, 0x3633, 0x07);
    cam_i2c_write(axi_iic, device_addr, 0x3702, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x3703, 0xb2);
    cam_i2c_write(axi_iic, device_addr, 0x3704, 0x18);
    cam_i2c_write(axi_iic, device_addr, 0x370b, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x370d, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x3620, 0x52);
    cam_i2c_write(axi_iic, device_addr, 0x3c00, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x5001, 0xFF);
    cam_i2c_write(axi_iic, device_addr, 0x5282, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5300, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5301, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5302, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5303, 0x7c);
    cam_i2c_write(axi_iic, device_addr, 0x530c, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x530d, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x530e, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x530f, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5310, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5311, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5308, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5309, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x5304, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5305, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x5306, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5307, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5314, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x5315, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5319, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x5316, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5317, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5318, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x5500, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5502, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5503, 0x06);
    cam_i2c_write(axi_iic, device_addr, 0x5504, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5505, 0x7f);
    cam_i2c_write(axi_iic, device_addr, 0x5025, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5300, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5301, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5302, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5303, 0x7c);
    cam_i2c_write(axi_iic, device_addr, 0x530c, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x530d, 0x0c);
    cam_i2c_write(axi_iic, device_addr, 0x530e, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x530f, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5310, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5311, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5308, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5309, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x5304, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5305, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x5306, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5307, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5314, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x5315, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x5319, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x5316, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5317, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x5318, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x5380, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x5381, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5382, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5383, 0x1f);
    cam_i2c_write(axi_iic, device_addr, 0x5384, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5385, 0x06);
    cam_i2c_write(axi_iic, device_addr, 0x5386, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5387, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5388, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5389, 0xE1);
    cam_i2c_write(axi_iic, device_addr, 0x538A, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x538B, 0x2B);
    cam_i2c_write(axi_iic, device_addr, 0x538C, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x538D, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x538E, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x538F, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5390, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5391, 0xB3);
    cam_i2c_write(axi_iic, device_addr, 0x5392, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5393, 0xA6);
    cam_i2c_write(axi_iic, device_addr, 0x5394, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x5480, 0xd);
    cam_i2c_write(axi_iic, device_addr, 0x5481, 0x18);
    cam_i2c_write(axi_iic, device_addr, 0x5482, 0x2a);
    cam_i2c_write(axi_iic, device_addr, 0x5483, 0x49);
    cam_i2c_write(axi_iic, device_addr, 0x5484, 0x56);
    cam_i2c_write(axi_iic, device_addr, 0x5485, 0x62);
    cam_i2c_write(axi_iic, device_addr, 0x5486, 0x6c);
    cam_i2c_write(axi_iic, device_addr, 0x5487, 0x76);
    cam_i2c_write(axi_iic, device_addr, 0x5488, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x5489, 0x88);
    cam_i2c_write(axi_iic, device_addr, 0x548a, 0x96);
    cam_i2c_write(axi_iic, device_addr, 0x548b, 0xa2);
    cam_i2c_write(axi_iic, device_addr, 0x548c, 0xb8);
    cam_i2c_write(axi_iic, device_addr, 0x548d, 0xcc);
    cam_i2c_write(axi_iic, device_addr, 0x548e, 0xe0);
    cam_i2c_write(axi_iic, device_addr, 0x548f, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5490, 0x3);
    cam_i2c_write(axi_iic, device_addr, 0x5491, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x5492, 0x3);
    cam_i2c_write(axi_iic, device_addr, 0x5493, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x5494, 0x2);
    cam_i2c_write(axi_iic, device_addr, 0x5495, 0xa0);
    cam_i2c_write(axi_iic, device_addr, 0x5496, 0x2);
    cam_i2c_write(axi_iic, device_addr, 0x5497, 0x48);
    cam_i2c_write(axi_iic, device_addr, 0x5498, 0x2);
    cam_i2c_write(axi_iic, device_addr, 0x5499, 0x26);
    cam_i2c_write(axi_iic, device_addr, 0x549a, 0x2);
    cam_i2c_write(axi_iic, device_addr, 0x549b, 0xb);
    cam_i2c_write(axi_iic, device_addr, 0x549c, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x549d, 0xee);
    cam_i2c_write(axi_iic, device_addr, 0x549e, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x549f, 0xd8);
    cam_i2c_write(axi_iic, device_addr, 0x54a0, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x54a1, 0xc7);
    cam_i2c_write(axi_iic, device_addr, 0x54a2, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x54a3, 0xb3);
    cam_i2c_write(axi_iic, device_addr, 0x54a4, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x54a5, 0x90);
    cam_i2c_write(axi_iic, device_addr, 0x54a6, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x54a7, 0x62);
    cam_i2c_write(axi_iic, device_addr, 0x54a8, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x54a9, 0x27);
    cam_i2c_write(axi_iic, device_addr, 0x54aa, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x54ab, 0x09);
    cam_i2c_write(axi_iic, device_addr, 0x54ac, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x54ad, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x54ae, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x54af, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x54b0, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x54b1, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x54b2, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x54b3, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x54b4, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x54b5, 0xf0);
    cam_i2c_write(axi_iic, device_addr, 0x54b6, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x54b7, 0xdf);
    cam_i2c_write(axi_iic, device_addr, 0x5583, 0x5d);
    cam_i2c_write(axi_iic, device_addr, 0x5584, 0x5d);
    cam_i2c_write(axi_iic, device_addr, 0x5580, 0x06);
    cam_i2c_write(axi_iic, device_addr, 0x5587, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5588, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x558a, 0x09);
    cam_i2c_write(axi_iic, device_addr, 0x5589, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5000, 0xcf);
    cam_i2c_write(axi_iic, device_addr, 0x5800, 0x48);
    cam_i2c_write(axi_iic, device_addr, 0x5801, 0x31);
    cam_i2c_write(axi_iic, device_addr, 0x5802, 0x21);
    cam_i2c_write(axi_iic, device_addr, 0x5803, 0x1b);
    cam_i2c_write(axi_iic, device_addr, 0x5804, 0x1a);
    cam_i2c_write(axi_iic, device_addr, 0x5805, 0x1e);
    cam_i2c_write(axi_iic, device_addr, 0x5806, 0x29);
    cam_i2c_write(axi_iic, device_addr, 0x5807, 0x38);
    cam_i2c_write(axi_iic, device_addr, 0x5808, 0x26);
    cam_i2c_write(axi_iic, device_addr, 0x5809, 0x17);
    cam_i2c_write(axi_iic, device_addr, 0x580a, 0x11);
    cam_i2c_write(axi_iic, device_addr, 0x580b, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x580c, 0xd);
    cam_i2c_write(axi_iic, device_addr, 0x580d, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x580e, 0x13);
    cam_i2c_write(axi_iic, device_addr, 0x580f, 0x1a);
    cam_i2c_write(axi_iic, device_addr, 0x5810, 0x15);
    cam_i2c_write(axi_iic, device_addr, 0x5811, 0xd);
    cam_i2c_write(axi_iic, device_addr, 0x5812, 0x8);
    cam_i2c_write(axi_iic, device_addr, 0x5813, 0x5);
    cam_i2c_write(axi_iic, device_addr, 0x5814, 0x4);
    cam_i2c_write(axi_iic, device_addr, 0x5815, 0x5);
    cam_i2c_write(axi_iic, device_addr, 0x5816, 0x9);
    cam_i2c_write(axi_iic, device_addr, 0x5817, 0xd);
    cam_i2c_write(axi_iic, device_addr, 0x5818, 0x11);
    cam_i2c_write(axi_iic, device_addr, 0x5819, 0xa);
    cam_i2c_write(axi_iic, device_addr, 0x581a, 0x4);
    cam_i2c_write(axi_iic, device_addr, 0x581b, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x581c, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x581d, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x581e, 0x6);
    cam_i2c_write(axi_iic, device_addr, 0x581f, 0x9);
    cam_i2c_write(axi_iic, device_addr, 0x5820, 0x12);
    cam_i2c_write(axi_iic, device_addr, 0x5821, 0xb);
    cam_i2c_write(axi_iic, device_addr, 0x5822, 0x4);
    cam_i2c_write(axi_iic, device_addr, 0x5823, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x5824, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x5825, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x5826, 0x6);
    cam_i2c_write(axi_iic, device_addr, 0x5827, 0xa);
    cam_i2c_write(axi_iic, device_addr, 0x5828, 0x17);
    cam_i2c_write(axi_iic, device_addr, 0x5829, 0xf);
    cam_i2c_write(axi_iic, device_addr, 0x582a, 0x9);
    cam_i2c_write(axi_iic, device_addr, 0x582b, 0x6);
    cam_i2c_write(axi_iic, device_addr, 0x582c, 0x5);
    cam_i2c_write(axi_iic, device_addr, 0x582d, 0x6);
    cam_i2c_write(axi_iic, device_addr, 0x582e, 0xa);
    cam_i2c_write(axi_iic, device_addr, 0x582f, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x5830, 0x28);
    cam_i2c_write(axi_iic, device_addr, 0x5831, 0x1a);
    cam_i2c_write(axi_iic, device_addr, 0x5832, 0x11);
    cam_i2c_write(axi_iic, device_addr, 0x5833, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x5834, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x5835, 0xf);
    cam_i2c_write(axi_iic, device_addr, 0x5836, 0x15);
    cam_i2c_write(axi_iic, device_addr, 0x5837, 0x1d);
    cam_i2c_write(axi_iic, device_addr, 0x5838, 0x6e);
    cam_i2c_write(axi_iic, device_addr, 0x5839, 0x39);
    cam_i2c_write(axi_iic, device_addr, 0x583a, 0x27);
    cam_i2c_write(axi_iic, device_addr, 0x583b, 0x1f);
    cam_i2c_write(axi_iic, device_addr, 0x583c, 0x1e);
    cam_i2c_write(axi_iic, device_addr, 0x583d, 0x23);
    cam_i2c_write(axi_iic, device_addr, 0x583e, 0x2f);
    cam_i2c_write(axi_iic, device_addr, 0x583f, 0x41);
    cam_i2c_write(axi_iic, device_addr, 0x5840, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x5841, 0xc);
    cam_i2c_write(axi_iic, device_addr, 0x5842, 0xd);
    cam_i2c_write(axi_iic, device_addr, 0x5843, 0xc);
    cam_i2c_write(axi_iic, device_addr, 0x5844, 0xc);
    cam_i2c_write(axi_iic, device_addr, 0x5845, 0xc);
    cam_i2c_write(axi_iic, device_addr, 0x5846, 0xc);
    cam_i2c_write(axi_iic, device_addr, 0x5847, 0xc);
    cam_i2c_write(axi_iic, device_addr, 0x5848, 0xd);
    cam_i2c_write(axi_iic, device_addr, 0x5849, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x584a, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x584b, 0xa);
    cam_i2c_write(axi_iic, device_addr, 0x584c, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x584d, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x584e, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x584f, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5850, 0x11);
    cam_i2c_write(axi_iic, device_addr, 0x5851, 0xa);
    cam_i2c_write(axi_iic, device_addr, 0x5852, 0xf);
    cam_i2c_write(axi_iic, device_addr, 0x5853, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x5854, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5855, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5856, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5857, 0xa);
    cam_i2c_write(axi_iic, device_addr, 0x5858, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x5859, 0xe);
    cam_i2c_write(axi_iic, device_addr, 0x585a, 0xf);
    cam_i2c_write(axi_iic, device_addr, 0x585b, 0xf);
    cam_i2c_write(axi_iic, device_addr, 0x585c, 0xf);
    cam_i2c_write(axi_iic, device_addr, 0x585d, 0xa);
    cam_i2c_write(axi_iic, device_addr, 0x585e, 0x9);
    cam_i2c_write(axi_iic, device_addr, 0x585f, 0xd);
    cam_i2c_write(axi_iic, device_addr, 0x5860, 0xc);
    cam_i2c_write(axi_iic, device_addr, 0x5861, 0xb);
    cam_i2c_write(axi_iic, device_addr, 0x5862, 0xd);
    cam_i2c_write(axi_iic, device_addr, 0x5863, 0x7);
    cam_i2c_write(axi_iic, device_addr, 0x5864, 0x17);
    cam_i2c_write(axi_iic, device_addr, 0x5865, 0x14);
    cam_i2c_write(axi_iic, device_addr, 0x5866, 0x18);
    cam_i2c_write(axi_iic, device_addr, 0x5867, 0x18);
    cam_i2c_write(axi_iic, device_addr, 0x5868, 0x16);
    cam_i2c_write(axi_iic, device_addr, 0x5869, 0x12);
    cam_i2c_write(axi_iic, device_addr, 0x586a, 0x1b);
    cam_i2c_write(axi_iic, device_addr, 0x586b, 0x1a);
    cam_i2c_write(axi_iic, device_addr, 0x586c, 0x16);
    cam_i2c_write(axi_iic, device_addr, 0x586d, 0x16);
    cam_i2c_write(axi_iic, device_addr, 0x586e, 0x18);
    cam_i2c_write(axi_iic, device_addr, 0x586f, 0x1f);
    cam_i2c_write(axi_iic, device_addr, 0x5870, 0x1c);
    cam_i2c_write(axi_iic, device_addr, 0x5871, 0x16);
    cam_i2c_write(axi_iic, device_addr, 0x5872, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x5873, 0xf);
    cam_i2c_write(axi_iic, device_addr, 0x5874, 0x13);
    cam_i2c_write(axi_iic, device_addr, 0x5875, 0x1c);
    cam_i2c_write(axi_iic, device_addr, 0x5876, 0x1e);
    cam_i2c_write(axi_iic, device_addr, 0x5877, 0x17);
    cam_i2c_write(axi_iic, device_addr, 0x5878, 0x11);
    cam_i2c_write(axi_iic, device_addr, 0x5879, 0x11);
    cam_i2c_write(axi_iic, device_addr, 0x587a, 0x14);
    cam_i2c_write(axi_iic, device_addr, 0x587b, 0x1e);
    cam_i2c_write(axi_iic, device_addr, 0x587c, 0x1c);
    cam_i2c_write(axi_iic, device_addr, 0x587d, 0x1c);
    cam_i2c_write(axi_iic, device_addr, 0x587e, 0x1a);
    cam_i2c_write(axi_iic, device_addr, 0x587f, 0x1a);
    cam_i2c_write(axi_iic, device_addr, 0x5880, 0x1b);
    cam_i2c_write(axi_iic, device_addr, 0x5881, 0x1f);
    cam_i2c_write(axi_iic, device_addr, 0x5882, 0x14);
    cam_i2c_write(axi_iic, device_addr, 0x5883, 0x1a);
    cam_i2c_write(axi_iic, device_addr, 0x5884, 0x1d);
    cam_i2c_write(axi_iic, device_addr, 0x5885, 0x1e);
    cam_i2c_write(axi_iic, device_addr, 0x5886, 0x1a);
    cam_i2c_write(axi_iic, device_addr, 0x5887, 0x1a);
    cam_i2c_write(axi_iic, device_addr, 0x528a, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x528b, 0x06);
    cam_i2c_write(axi_iic, device_addr, 0x528c, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x528d, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x528e, 0x40);
    cam_i2c_write(axi_iic, device_addr, 0x528f, 0x50);
    cam_i2c_write(axi_iic, device_addr, 0x5290, 0x60);
    cam_i2c_write(axi_iic, device_addr, 0x5292, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5293, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x5294, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5295, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x5296, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5297, 0x08);
    cam_i2c_write(axi_iic, device_addr, 0x5298, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5299, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x529a, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x529b, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x529c, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x529d, 0x28);
    cam_i2c_write(axi_iic, device_addr, 0x529e, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x529f, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x5282, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5680, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5681, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5682, 0x05);
    cam_i2c_write(axi_iic, device_addr, 0x5683, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5684, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5685, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x5686, 0x03);
    cam_i2c_write(axi_iic, device_addr, 0x5687, 0xc0);
    cam_i2c_write(axi_iic, device_addr, 0x5180, 0xff);
    cam_i2c_write(axi_iic, device_addr, 0x5181, 0x52);
    cam_i2c_write(axi_iic, device_addr, 0x5182, 0x11);
    cam_i2c_write(axi_iic, device_addr, 0x5183, 0x14);
    cam_i2c_write(axi_iic, device_addr, 0x5184, 0x25);
    cam_i2c_write(axi_iic, device_addr, 0x5185, 0x24);
    cam_i2c_write(axi_iic, device_addr, 0x5186, 0x14);
    cam_i2c_write(axi_iic, device_addr, 0x5187, 0x14);
    cam_i2c_write(axi_iic, device_addr, 0x5188, 0x14);
    cam_i2c_write(axi_iic, device_addr, 0x5189, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x518a, 0x60);
    cam_i2c_write(axi_iic, device_addr, 0x518b, 0xa2);
    cam_i2c_write(axi_iic, device_addr, 0x518c, 0x9c);
    cam_i2c_write(axi_iic, device_addr, 0x518d, 0x36);
    cam_i2c_write(axi_iic, device_addr, 0x518e, 0x34);
    cam_i2c_write(axi_iic, device_addr, 0x518f, 0x54);
    cam_i2c_write(axi_iic, device_addr, 0x5190, 0x4c);
    cam_i2c_write(axi_iic, device_addr, 0x5191, 0xf8);
    cam_i2c_write(axi_iic, device_addr, 0x5192, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x5193, 0x70);
    cam_i2c_write(axi_iic, device_addr, 0x5194, 0xf0);
    cam_i2c_write(axi_iic, device_addr, 0x5195, 0xf0);
    cam_i2c_write(axi_iic, device_addr, 0x5196, 0x03);
    cam_i2c_write(axi_iic, device_addr, 0x5197, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x5198, 0x05);
    cam_i2c_write(axi_iic, device_addr, 0x5199, 0x2f);
    cam_i2c_write(axi_iic, device_addr, 0x519a, 0x04);
    cam_i2c_write(axi_iic, device_addr, 0x519b, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x519c, 0x06);
    cam_i2c_write(axi_iic, device_addr, 0x519d, 0xa0);
    cam_i2c_write(axi_iic, device_addr, 0x519e, 0xa0);
    cam_i2c_write(axi_iic, device_addr, 0x3a0f, 0x3c);
    cam_i2c_write(axi_iic, device_addr, 0x3a10, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x3a1b, 0x3c);
    cam_i2c_write(axi_iic, device_addr, 0x3a1e, 0x30);
    cam_i2c_write(axi_iic, device_addr, 0x3a11, 0x70);
    cam_i2c_write(axi_iic, device_addr, 0x3a1f, 0x10);
    cam_i2c_write(axi_iic, device_addr, 0x3800, 0x1);
    cam_i2c_write(axi_iic, device_addr, 0x3801, 0x50);
    cam_i2c_write(axi_iic, device_addr, 0x3802, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x3803, 0x8);
    cam_i2c_write(axi_iic, device_addr, 0x3804, 0x5);
    cam_i2c_write(axi_iic, device_addr, 0x3805, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x3806, 0x3);
    cam_i2c_write(axi_iic, device_addr, 0x3807, 0xc0);
    cam_i2c_write(axi_iic, device_addr, 0x3808, 0x3);
    cam_i2c_write(axi_iic, device_addr, 0x3809, 0x20);
    cam_i2c_write(axi_iic, device_addr, 0x380a, 0x2);
    cam_i2c_write(axi_iic, device_addr, 0x380b, 0x58);
    cam_i2c_write(axi_iic, device_addr, 0x380c, 0xc);
    cam_i2c_write(axi_iic, device_addr, 0x380d, 0x80);
    cam_i2c_write(axi_iic, device_addr, 0x380e, 0x3);
    cam_i2c_write(axi_iic, device_addr, 0x380f, 0xe8);
    cam_i2c_write(axi_iic, device_addr, 0x5001, 0x7f);
    cam_i2c_write(axi_iic, device_addr, 0x5680, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x5681, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x5682, 0x5);
    cam_i2c_write(axi_iic, device_addr, 0x5683, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x5684, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x5685, 0x0);
    cam_i2c_write(axi_iic, device_addr, 0x5686, 0x3);
    cam_i2c_write(axi_iic, device_addr, 0x5687, 0xc0);
    cam_i2c_write(axi_iic, device_addr, 0x5687, 0xc0);
    cam_i2c_write(axi_iic, device_addr, 0x3815, 0x02);
    cam_i2c_write(axi_iic, device_addr, 0x3503, 0x00);
    cam_i2c_write(axi_iic, device_addr, 0x3818, 0x81); // No Mirror
    cam_i2c_write(axi_iic, device_addr, 0x3621, 0xa7);

    cam_i2c_write(axi_iic, device_addr, 0x4740, 0x21);

    cam_i2c_write(axi_iic, device_addr, 0x501e, 0x2a);
    cam_i2c_write(axi_iic, device_addr, 0x5002, 0x78);
    cam_i2c_write(axi_iic, device_addr, 0x501f, 0x01);
    cam_i2c_write(axi_iic, device_addr, 0x4300, 0x61);

    return(0);
}

  1. 2019年01月05日 08:23 |
  2. ZYBO Z7
  3. | トラックバック:0
  4. | コメント:0

アカデミック向けに送付したUltra96用PMOD拡張ボード

今日、アカデミック向けにUltra96用PMOD拡張基板とパーツを送付しました。

送付した内容は、1セットに付き

Ultra96用PMOD拡張基板 1枚
レベル変換IC、LSF0108,LSF0108QPWRQ1 2個
1608 パッケージの 1KΩ 16個 + 1 個予備
1608 パッケージの100 KΩ 4個 + 1 個予備

です。
後は、秋月電子で下記の物品を購入ください。下のリストはCSVファイルです。切り取って、CSVファイルとしてセーブするとExcel で読めます。

Ultra96用PMOD拡張ボード・パーツリスト,,,,,,,,
,,,,,,,,
番号,パーツ名,規格,メーカー,販売店,販売店品番,数量,URL,備考
1,DC-DCコンバータ(3.3V0.5A),M78AR033-0.5,MINMAX,秋月電子,M-07178,1,http://akizukidenshi.com/catalog/g/gM-07178/,
2,L型ピンソケット 2×10(20P),,,秋月電子,C-05755,2,http://akizukidenshi.com/catalog/g/gC-05755/,2x6に割って使用する
3,チップ積層セラミックコンデンサー 0.1ΜF50V (1608),,,秋月電子,P-13374,5,http://akizukidenshi.com/catalog/g/gP-13374/,40個入りなのでその内の5個
4,チップ積層セラミックコンデンサー 10μF35V (1608),,,秋月電子,P-13161,1,http://akizukidenshi.com/catalog/g/gP-13161/,10個入りなのでその内の1個
5,2mmピッチピンヘッダ 2×40 (80P),,,秋月電子,C-06075,0.5,http://akizukidenshi.com/catalog/g/gC-06075/,2x20Pが必要なので半分に割る


こちらが全パーツリストです。
Kicad_BOM_12_181227.png

パーツの実装方法
チップ部品の実装方法はお分かりか?と思います。
R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R5 R6 R7 R8 R9 が 1KΩで、 R1 R2 R3 R4 が 100KΩです。

レベル変換IC、LSF0108 の実装向きは、1 番ピンがUltra96 の低速コネクタの方向に来るように実装してください。
DC-DCコンバータ(3.3V0.5A),M78AR033-0.5は、基板のシルクに部品のシルクを合わせてください。
OV5642_35_181215.jpg

OV5642_36_181215.jpg

L型ピンソケット 2×10(20P)は 2X6 に割って使用してください。私は、カッターで筋を付けて、ワイヤーストリッパーの元の両刃の部分で切り取るように割っています。比較的簡単に綺麗に割れています。その後はヤスリがけしてください。できたら、Pmod VGAなどの2つのPMODを使用するボードをコネクタに入れながらはんだ付けした方がベターかと思います。スルーホールに余裕があるため、ずれるとコネクタとPMODボードの勘合が悪くなることが考えられます。

2mmピッチピンヘッダ 2×40 (80P)は、半分に切り取って使用してください。これもワイヤーストリッパーの元の両刃の部分を使うと綺麗に割れます。

Ultra96用PMOD拡張ボードの実装方法
2 mmピッチピンヘッダ 2 X 40 をUltra96 のJ7 に挿入して使用します。その際にPMOD がUltra96 ボードの外に来るように実装してください。反対に実装するとレベル変換ICが飛びますので、ご注意ください。
OV5642_64_181220.jpg

(追記)
抵抗とコンデンサはすべて1608のチップ部品です。1kΩのプルアップ抵抗に集合抵抗を使わなかったのは、特定のピンだけプルアップを外すまたは抵抗値を変えられるようにです。
プルアップ抵抗がPMODのどのピンに対応するのか?はシルクで表示しています。

(2019/01/11:追記)
PMODのピン番号とFPGAのピン番号の表を載せておきます。MT9D111 の信号線も書かれています。
OV5642_71_190111.png

PMOD VGA のXDC 制約ファイルを載せておきます。

set_property PACKAGE_PIN D7 [get_ports {red[0]}]
set_property PACKAGE_PIN F8 [get_ports {red[1]}]
set_property PACKAGE_PIN F7 [get_ports {red[2]}]
set_property PACKAGE_PIN G7 [get_ports {red[3]}]
set_property PACKAGE_PIN F6 [get_ports {blue[0]}]
set_property PACKAGE_PIN G5 [get_ports {blue[1]}]
set_property PACKAGE_PIN A6 [get_ports {blue[2]}]
set_property PACKAGE_PIN A7 [get_ports {blue[3]}]
set_property PACKAGE_PIN G6 [get_ports {green[0]}]
set_property PACKAGE_PIN E6 [get_ports {green[1]}]
set_property PACKAGE_PIN E5 [get_ports {green[2]}]
set_property PACKAGE_PIN D6 [get_ports {green[3]}]
set_property PACKAGE_PIN D5 [get_ports {hsyncx[0]}]
set_property PACKAGE_PIN C7 [get_ports {vsyncx[0]}]

set_property IOSTANDARD LVCMOS18 [get_ports {blue[3]}]
set_property IOSTANDARD LVCMOS18 [get_ports {blue[2]}]
set_property IOSTANDARD LVCMOS18 [get_ports {blue[1]}]
set_property IOSTANDARD LVCMOS18 [get_ports {blue[0]}]
set_property IOSTANDARD LVCMOS18 [get_ports {hsyncx[0]}]
set_property IOSTANDARD LVCMOS18 [get_ports {vsyncx[0]}]
set_property IOSTANDARD LVCMOS18 [get_ports {green[3]}]
set_property IOSTANDARD LVCMOS18 [get_ports {green[2]}]
set_property IOSTANDARD LVCMOS18 [get_ports {green[1]}]
set_property IOSTANDARD LVCMOS18 [get_ports {green[0]}]
set_property IOSTANDARD LVCMOS18 [get_ports {red[3]}]
set_property IOSTANDARD LVCMOS18 [get_ports {red[2]}]
set_property IOSTANDARD LVCMOS18 [get_ports {red[1]}]
set_property IOSTANDARD LVCMOS18 [get_ports {red[0]}]


  1. 2019年01月04日 20:41 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

垂直方向が反転している画像のDMA(vflip_dma_write)

垂直方向、水平方向が反転している画像のDMA(vhflip_dma_write)”の続き。

前回は、垂直方向はフリップし、水平方向をミラーありにして、アドレスが減りながらDMAを行う方式をVivado HLS で実装して、検証した。その結果、性能が出ないことがわかった。今回は、垂直方向はフリップしているが、水平方向はミラーなしの場合のDMA を実装して性能を確認しよう。

まずは、ソースを貼っておく。
vflip_dma_write.h から貼っておく。

// vflip_dma_write.h
// 2019/01/01 by marsee
//

#ifndef __VFLIP_DMA_WRITE_H__
#define __VFLIP_DMA_WRITE_H__

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

#define MAX_WIDTH 800
#define MAX_HEIGHT 600

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<MAX_HEIGHT, MAX_WIDTH, HLS_8UC3> RGB_IMAGE;
typedef hls::Mat<MAX_HEIGHT, MAX_WIDTH, HLS_8UC1> GRAY_IMAGE;

#endif


vflip_dma_write.cpp を貼っておく。

// vflip_dma_write.cpp
// 2019/01/01 by marsee
//

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

#include "vflip_dma_write.h"

int vflip_dma_write(AXI_STREAM & ins,
    volatile ap_int<32> *fb0, volatile ap_int<32> *fb1, volatile ap_int<32> *fb2,
    volatile ap_uint<2> &active_frame){
#pragma HLS INTERFACE s_axilite port=active_frame
#pragma HLS INTERFACE m_axi depth=480000 port=fb0 offset=slave
#pragma HLS INTERFACE m_axi depth=480000 port=fb1 offset=slave
#pragma HLS INTERFACE m_axi depth=480000 port=fb2 offset=slave
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE s_axilite port=return

    AP_AXIU32 pix;
    int max_fb_chk;

    active_frame = 0;

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

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

            fb0[(y*MAX_WIDTH)+x] = pix.data;
        }
    }

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

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

            fb1[(y*MAX_WIDTH)+x] = pix.data;

        }
    }

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

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

            fb2[(y*MAX_WIDTH)+x] = pix.data;
        }
    }
end:
    return(0);
}


vflip_dma_write_tb_tb.cpp を貼っておく。

// vflip_dma_write_tb_tb.cpp
// 2019/01/01 by marsee
//

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

#include "vflip_dma_write.h"

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

#define NUM_FRAME_BUFFER 3

int main()
{
    using namespace cv;

    AXI_STREAM ins;
    AP_AXIU32 pix;

    ap_uint<2> active_frame;
    int *frame_buffer;

    // OpenCV で 画像を読み込む
    Mat src = imread("bmp_file0_vflip.bmp");
    AXI_STREAM src_axi, dst_axi;

    // Mat フォーマットから AXI4 Stream へ変換、3画面分
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        cvMat2AXIvideo(src, src_axi);
        for (int y=0; y<MAX_HEIGHT; y++){
            for (int x=0; x<MAX_WIDTH; x++){
                src_axi >> pix;
                ins << pix;
            }
        }
    }

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

    vflip_dma_write(ins, (volatile ap_int<32> *)frame_buffer,
        (volatile ap_int<32> *)&frame_buffer[MAX_WIDTH * MAX_HEIGHT],
        (volatile ap_int<32> *)&frame_buffer[2 * (MAX_WIDTH * MAX_HEIGHT)],
        active_frame);

    // AXI4 Stream から Mat フォーマットへ変換
    // dst は宣言時にサイズとカラー・フォーマットを定義する必要がある
    Mat dst[3];
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        dst[i] = Mat(src.rows, src.cols, CV_8UC3);
    }

    // dst[i] にframe_bufferから画像データをロード
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        Mat_<Vec3b> dst_vec3b = Mat_<Vec3b>(dst[i]);
        for(int y=0; y<dst[i].rows; y++){
            for(int x=0; x<dst[i].cols; x++){
                Vec3b pixel;
                int rgb = frame_buffer[i*(MAX_WIDTH * MAX_HEIGHT)+y*MAX_WIDTH+x];
                pixel[0] = (rgb & 0xff); // blue
                pixel[1] = (rgb & 0xff00) >> 8; // green
                pixel[2] = (rgb & 0xff0000) >> 16; // red
                dst_vec3b(y,x) = pixel;
            }
        }
    }

    // DMAされたデータをBMPフィルに書き込む
    char output_file[] = "dma_result0.bmp";
    for (int i=0; i<NUM_FRAME_BUFFER; i++){
        switch (i){
            case 0:
                strcpy(output_file,"dma_result0.bmp");
                break;
            case 1:
                strcpy(output_file,"dma_result1.bmp");
                break;
            case 2:
                strcpy(output_file,"dma_result2.bmp");
                break;
        }
        // Mat フォーマットからファイルに書き込み
        imwrite(output_file, dst[i]);
    }

    //free(frame_buffer);
    return 0;
}


Vivado HLS 2018.3 で作成した vflip_dma_write プロジェクトを示す。
vflip_dma_write_11_190102.png

C シミュレーションを行った。結果を示す。
vflip_dma_write_12_190102.png

ZYBO-Z7-20/vflip_dma_write/soulution1/csim/build ディレクトリを示す。
vflip_dma_write_13_190102.png

bmp_file0_vflip.bmp を示す。垂直方向のみフリップされている。
vflip_dma_write_14_190102.jpg

bmp_file0_vflip.bmp をDMA した結果、元に戻ったBMP ファイルの dma_result0.bmp を示す。なお、dma_result1.bmp 、dma_result2.bmp もdma_result0.bmp と同様だ。
vflip_dma_write_15_190102.jpg

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

レイテンシはは 1440093 クロックで、ほとんど 1 クロックで 1 ピクセル転送できているのが分かる。

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

レイテンシは 1448260 クロックで合成時のレイテンシと大幅に変わってはいないことが分かる。

C/RTL 協調シミュレーションの波形を見てみよう。
vflip_dma_write_20_190102.png

AWLEN が 0f で 16 バースト転送だということが分かる。

波形を拡大してみよう。
vflip_dma_write_21_190102.png

きちんとバースト転送していることが分かる。これだと性能が出るはずだ。

最後にExport RTL を行った。結果を示す。
vflip_dma_write_19_190102.png

DMA 3個入っているので、リソースは多めに使用するようだ。CP achieved post-implementation は 8.965 ns で大丈夫そうだ。
  1. 2019年01月03日 06:51 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

垂直方向、水平方向が反転している画像のDMA(vhflip_dma_write)

2019年最初の記事はやはりVivado HLS の記事を書こうと思う。

ZYBO Z7-20でのMNISTの実装にOV5642を使用する2”で垂直方向はフリップしたのだが、水平方向はミラー無しにした。これは、Vivado HLS でカメラ画像をDMA するIP を作成する際に性能を出すためだったのだが、最初に水平方向をミラーありにして、アドレスが減りながらDMA する場合を見ていこう。

Ultra96用PMOD拡張ボードでカメラ入力9(Vivado 2018.2のcam_test_182プロジェクト6)”のDMA_Write_sFB を少し改造して、vhflip_dma_write.cpp を作成した。

最初にvhflip_dma_write.h を示す。

// vhflip_dma_write.h
// 2019/01/01 by marsee
//

#ifndef __VHFLIP_DMA_WRITE_H__
#define __VHFLIP_DMA_WRITE_H__

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

#define MAX_WIDTH 800
#define MAX_HEIGHT 600

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<MAX_HEIGHT, MAX_WIDTH, HLS_8UC3> RGB_IMAGE;
typedef hls::Mat<MAX_HEIGHT, MAX_WIDTH, HLS_8UC1> GRAY_IMAGE;

#endif


vhflip_dma_write.cpp を示す。

// vhflip_dma_write.cpp
// 2019/01/01 by marsee
//

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

#include "vhflip_dma_write.h"

int vhflip_dma_write(AXI_STREAM & ins,
    volatile ap_int<32> *fb0, volatile ap_int<32> *fb1, volatile ap_int<32> *fb2,
    volatile ap_uint<2> &active_frame){
#pragma HLS INTERFACE s_axilite port=active_frame
#pragma HLS INTERFACE m_axi depth=480000 port=fb0 offset=slave
#pragma HLS INTERFACE m_axi depth=480000 port=fb1 offset=slave
#pragma HLS INTERFACE m_axi depth=480000 port=fb2 offset=slave
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE s_axilite port=return

    AP_AXIU32 pix;
    int max_fb_chk;

    active_frame = 0;

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

    LOOP_Y0: for (int y=MAX_HEIGHT-1; y>=0; y--){ // vflip
        LOOP_X0: for (int x=MAX_WIDTH-1; x>=0; x--){ // hflip
#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix;    // AXI4-Stream からの入力

            fb0[(y*MAX_WIDTH)+x] = pix.data;
        }
    }

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

    LOOP_Y1: for (int y=MAX_HEIGHT-1; y>=0; y--){ // vflip
        LOOP_X1: for (int x=MAX_WIDTH-1; x>=0; x--){ // hflip
#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix;    // AXI4-Stream からの入力

            fb1[(y*MAX_WIDTH)+x] = pix.data;

        }
    }

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

    LOOP_Y2: for (int y=MAX_HEIGHT-1; y>=0; y--){ // vflip
        LOOP_X2: for (int x=MAX_WIDTH-1; x>=0; x--){ // hflip
#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix;    // AXI4-Stream からの入力

            fb2[(y*MAX_WIDTH)+x] = pix.data;
        }
    }
end:
    return(0);
}


vhflip_dma_write_tb.cpp を示す。

// vhflip_dma_write_tb_tb.cpp
// 2019/01/01 by marsee
//

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

#include "vhflip_dma_write.h"

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

#define NUM_FRAME_BUFFER 3

int main()
{
    using namespace cv;

    AXI_STREAM ins;
    AP_AXIU32 pix;

    ap_uint<2> active_frame;
    int *frame_buffer;

    // OpenCV で 画像を読み込む
    Mat src = imread("bmp_file0_vhflip.bmp");
    AXI_STREAM src_axi, dst_axi;

    // Mat フォーマットから AXI4 Stream へ変換、3画面分
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        cvMat2AXIvideo(src, src_axi);
        for (int y=0; y<MAX_HEIGHT; y++){
            for (int x=0; x<MAX_WIDTH; x++){
                src_axi >> pix;
                ins << pix;
            }
        }
    }

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

    vhflip_dma_write(ins, (volatile ap_int<32> *)frame_buffer,
        (volatile ap_int<32> *)&frame_buffer[MAX_WIDTH * MAX_HEIGHT],
        (volatile ap_int<32> *)&frame_buffer[2 * (MAX_WIDTH * MAX_HEIGHT)],
        active_frame);

    // AXI4 Stream から Mat フォーマットへ変換
    // dst は宣言時にサイズとカラー・フォーマットを定義する必要がある
    Mat dst[3];
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        dst[i] = Mat(src.rows, src.cols, CV_8UC3);
    }

    // dst[i] にframe_bufferから画像データをロード
    for(int i=0; i<NUM_FRAME_BUFFER; i++){
        Mat_<Vec3b> dst_vec3b = Mat_<Vec3b>(dst[i]);
        for(int y=0; y<dst[i].rows; y++){
            for(int x=0; x<dst[i].cols; x++){
                Vec3b pixel;
                int rgb = frame_buffer[i*(MAX_WIDTH * MAX_HEIGHT)+y*MAX_WIDTH+x];
                pixel[0] = (rgb & 0xff); // blue
                pixel[1] = (rgb & 0xff00) >> 8; // green
                pixel[2] = (rgb & 0xff0000) >> 16; // red
                dst_vec3b(y,x) = pixel;
            }
        }
    }

    // DMAされたデータをBMPフィルに書き込む
    char output_file[] = "dma_result0.bmp";
    for (int i=0; i<NUM_FRAME_BUFFER; i++){
        switch (i){
            case 0:
                strcpy(output_file,"dma_result0.bmp");
                break;
            case 1:
                strcpy(output_file,"dma_result1.bmp");
                break;
            case 2:
                strcpy(output_file,"dma_result2.bmp");
                break;
        }
        // Mat フォーマットからファイルに書き込み
        imwrite(output_file, dst[i]);
    }

    //free(frame_buffer);
    return 0;
}


Vivado HLS 2018.3 のvhflip_dma_write プロジェクトを示す。
vflip_dma_write_1_190102.png

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

C シミュレーションでは、垂直方向にフリップして、水平方向もミラーした画像の bmp_file0_vhflip.bmp を垂直方向、水平方向共に反転して3回DMA する。
bmp_file0_vhflip.bmp を示す。
vflip_dma_write_3_190102.jpg

csim ディレクトリを示す。
vflip_dma_write_5_190102.png

3つのDMA されたBMP ファイルの1つの dma_result0.bmp を示す。dma_result1.bmp 、dma_result2.bmp も同じ画像だった。
vflip_dma_write_4_190102.jpg

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

合成結果は問題なく 1 クロックで 1 ピクセルをDMA できている。この結果は問題ない。

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

レイテンシは 3600102 クロックだった。これは、合成のときのレイテンシの約 2.5 倍となった。
その理由を探るためにC/RTL 協調シミュレーションの波形を見ていこう。
vflip_dma_write_9_190102.png

まずは全体の波形を示す。AWLEN が 00 で単一転送だということが分かる。バースト転送になっていない。これは、AXI インターフェースのバースト転送の際のアドレスは単調増加のみで単調減少のモードは無いので、バースト転送にならないということである。
波形を拡大してみよう。
vflip_dma_write_10_190102.png

アドレスの発行1つ当たり、1つのデータ転送が行われているのが分かる。非常に効率悪い。性能が出ないパターンだ。
この状況は避けたいということで、”ZYBO Z7-20でのMNISTの実装にOV5642を使用する2”で垂直方向はフリップしたのだが、水平方向はミラー無しにしたのだった。

次回は、垂直方向はフリップしたのだが、水平方向はミラー無しのときのVivado HLS の実装を見ていこう。
  1. 2019年01月02日 07:51 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

あけましておめでとうございます。今年もよろしくお願いします。

あけましておめでとうございます。今年もよろしくお願いします。

今年は、3月31日に定年退職を迎えて、たぶんシニアスタッフとなります。そして、給料1/3 になるので、副業をしたいと思っておりますので、皆様よろしくお願いいたします。
とりあえず、やりたいことは、ZYBO Z7-20 でOV5642を使用してMNIST のCNN をFPGA 実装し、手書き数字を判別するシステムを作るのと、Ultra96 をロボットカーに乗せて、白線認識、物体認識させたいと思っております。

昨年のFPGAの部屋のブログのアクセス数のグラフです。やはり、一昨年と比べてアクセス数は落ちていますが、いろいろな状況があって仕方ないですね。今年もブログを書いていきたいと思いますので、よろしくお願いします。
access_190101.png
  1. 2019年01月01日 04:13 |
  2. 日記
  3. | トラックバック:0
  4. | コメント:0