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

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

FPGAの部屋

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

調歩同期式シリアル通信の送信 IP (uart_tx)を Vitis HLS で作成する2

調歩同期式シリアル通信の送信 IP (uart_tx)を Vitis HLS で作成する1”の続き。

調歩同期式シリアル通信の受信 IP とその後の 3 軸加速度センサー・データの処理 IP を作成した。今回は、調歩同期式シリアル通信の送信 IP (uart_tx)を Vitis HLS で作成しようとうことで、前回は、uart_tx のソースコードとテストベンチ・コードを貼って、Vitis HLS 2020.2 の uart_tx プロジェクトを作成した。今回は、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行っていこう。

まずは、C シミュレーションからやってみるが、txst にシリアルデータを出力するだけなので、このテストベンチでは、芸がない。。。
uart_tx_2_210227.png

C コードの合成をやってみた。結果を示す。
uart_tx_3_210227.png
uart_tx_4_210227.png

Latency は 1923 クロックだった。
12 分周 x 16 サンプル x ( 1 スタートビット + 8 キャラクタ + 1 ストップビット) = 1920 クロック
良いところだと思う。

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

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

ピンクの楕円の部分を拡大した。
uart_tx_7_210227.png

txst_V_din が X の部分があるので、これは良くない。。。

txst_V_din を出力する uart_tx.v のコードを見てみると、 else の場合が X に指定されていた。
uart_tx_8_210227.png

C 言語ベースで修正できないようなので、Verilog HDL コードを書き換えてしまった。(禁断の術だ。。。)
uart_tx_9_210227.png

これで、C/RTL 協調シミュレーションをして、波形を表示した。
uart_tx_10_210227.png

先ほどと同じ部分を拡大すると、txst_V_din は 1 のままなので、これで OK だ。
uart_tx_11_210227.png

しかし、txst_V_din が組み合わせ回路出力なのはいただけないので、もう一度、 FF 出力に変更した。
uart_tx_12_210227.png

always @ (posedge ap_clk) begin
    if (ap_rst_n_inv == 1'b1) begin
        txst_V_din = 1'd1;
    end else if ((txst_V_full_n == 1'b1)) begin
        if ((1'b1 == ap_CS_fsm_state6)) begin
            txst_V_din = 1'd1;
        end else if ((1'b1 == ap_CS_fsm_state4)) begin
            txst_V_din = trunc_ln213_fu_307_p1;
        end else if ((1'b1 == ap_CS_fsm_state2)) begin
            txst_V_din = 1'd0;
        end else begin
            txst_V_din = 1'd1;
        end
    end else begin
        txst_V_din = 1'd1;
    end
end


これで、C/RTL 協調シミュレーションを行った。
uart_tx_13_210227.png

C/RTL 協調シミュレーションの波形を表示した。
uart_tx_14_210227.png

2 クロック分ロスしているだけのようだ。問題無さそう。

uart_tx.v は修正したファイルになっている。
uart_tx_15_210227.png

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

uart_tx IP も確認したが、修正済みの Verilog HDL ファイルになっている。

内部で使用されていない出力ポートのレベルを変更することや、レジスタ化はやっても良いと思っている。ただ、ソースコードを修正した時は、Verilog HDL の再修正を忘れないようにする必要がある。
  1. 2021年02月28日 04:53 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

調歩同期式シリアル通信の送信 IP (uart_tx)を Vitis HLS で作成する1

調歩同期式シリアル通信の受信 IP とその後の 3 軸加速度センサー・データの処理 IP を作成した。残りっている調歩同期式シリアル通信の送信 IP (uart_tx)を Vitis HLS で作成しよう。
今回は、 uart_tx のソースコードとテストベンチ・コードを貼っておく。

ソースコードの uart_tx.h を貼っておく。

/// uart_tx.h
// 2021/02/18 by marsee
//

#ifndef __UART_TX_H__
#define __UART_TX_H__

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

#define DIV_8M  12
#define DIV_500k 16

#endif


uart_tx.cpp を貼っておく。

// uart_tx.cpp
// 2021/02/18 by marsee
//

#include "uart_tx.h"

bool send_zero_bit(hls::stream<ap_uint<1> >& txst){
    const ap_uint<1> tx=0;

    LOOP_DZB1: for(int i=0; i<DIV_8M; i++){
        LOOP_DZB2: for(int j=0; j<DIV_500k; j++){
#pragma HLS PIPELINE II=1 rewind
            txst << tx;
        }
    }
    return(true);
}

bool send_data_byte(ap_uint<8> tx_data, hls::stream<ap_uint<1> >& txst){
    ap_uint<1> tx;

    LOOP_DDB1: for(int k=0; k<8; k++){
        LOOP_DDB2: for(int i=0; i<DIV_8M; i++){
            LOOP_DDB3: for(int j=0; j<DIV_500k; j++){
#pragma HLS PIPELINE II=1 rewind
                tx = tx_data & 1;
                txst << tx;
                if(i==(DIV_8M-1) && j==(DIV_500k-1)){
                    tx_data >>= 1;
                }
            }
        }
    }
    return(true);

}

bool send_stop_bit(hls::stream<ap_uint<1> >& txst){
    const ap_uint<1> tx = 1;

    LOOP_CSB1: for(int i=0; i<DIV_8M; i++){
        LOOP_CSB2: for(int j=0; j<DIV_500k; j++){
#pragma HLS PIPELINE II=1 rewind
            txst << tx;
        }
    }
    return(true);
}

int uart_tx(ap_uint<8> tx_data, hls::stream<ap_uint<1> >& txst){
#pragma HLS INTERFACE s_axilite port=tx_data
#pragma HLS INTERFACE ap_ctrl_hs port=return
    send_zero_bit(txst);
    send_data_byte(tx_data, txst);
    send_stop_bit(txst);

    return(0);
}


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

// uart_tx_tb.cpp
// 2021/02/18 by marsee
//

#include "uart_tx.h"

int uart_tx(ap_uint<8> tx_data, hls::stream<ap_uint<1> >& txst);

int main(){
    ap_uint<8> tx_data;
    hls::stream<ap_uint<1> > txst;

    tx_data = 0x55;
    uart_tx(tx_data, txst);

    tx_data = 0xaa;
    uart_tx(tx_data, txst);

    return(0);
}


Vitis HLS 2020.2 で ZYBO Z7-20 用の uart_tx プロジェクトを作成した。
uart_tx_1_210226.png
  1. 2021年02月27日 03:24 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

uart_rx と uart_rx_axi4ls を連結してシミュレーションする2

uart_rx と uart_rx_axi4ls を連結してシミュレーションする1”の続き。

3 軸加速度センサーの RS-422 インターフェースのデータを受けるために uart_rx とその後段の uart_rx_axi4ls を Vitis HLS で作ってきたが、それらを連結してシミュレーションしてみようということで、前回は、Vivado 2020.2 の uart_rx_axi4ls_top プロジェクトを作成し、 uart_rx IP と uart_rx_axi4ls IP を IP Catalog に追加した。そてひ、 uart_rx_axi4ls_top_tb.sv のソースコードを貼った。今回は、Viado でブロックデザインを作成し、ラッパー Verilog HDL ファイルを生成して、論理シミュレーションを行う。

uart_rx_axi4ls_top ブロックデザインを作成し、 uart_rx IP と uart_rx_axi4ls IP を Add IP して追加し、配線を行った。
uart_rx_33_210224.png

uart_rx_axi4ls_top ブロックデザインを拡大する。
uart_rx_34_210224.png

Address Editor を示す。アドレスマップは実行されていない。
uart_rx_35_210224.png

Source ウインドウで uart_rx_axi4ls_top_i を右クリックし右クリックメニューから Create HDL Wrapper... を選択して、Verilog HDL のラッパーファイルを生成した。
uart_rx_36_210224.png

Add Sources で”uart_rx と uart_rx_axi4ls を連結してシミュレーションする1”の uart_rx_axi4ls_top_tb.sv ファイルを追加した。
そして、SIMULATION から Run Simulation -> Run Behavioral Simulation を選択して、論理シミュレーションを行った。
uart_rx_37_210224.png

シミュレーションの画面が表示された。
Run All ボタンをクリックして、シミュレーションを終了させる。
uart_rx_38_210224.png

$STOP 行までシミュレーションを行って、停止している。
uart_rx_39_210224.png

シミュレーション波形を見てみる。
uart_rx_40_210224.png

x 軸データ、 y 軸データ、 z 軸データが受かっているか確認するために波形の終わりの部分を拡大した。
uart_rx_41_210224.png

キーコード + x 軸データは 0x5a112233 、 y 軸データは 0x00445566 、 z 軸データは 0x00778899 が受かっていることが分かる。これで正解で、成功だ。
  1. 2021年02月26日 04:45 |
  2. FPGAを使用したシステム
  3. | トラックバック:0
  4. | コメント:0

uart_rx と uart_rx_axi4ls を連結してシミュレーションする1

3 軸加速度センサーの RS-422 インターフェースのデータを受けるために uart_rx とその後段の uart_rx_axi4ls を Vitis HLS で作ってきたが、それらを連結してシミュレーションしてみよう。
今回は、Vivado 2020.2 の uart_rx_axi4ls_top プロジェクトを作成し、 uart_rx IP と uart_rx_axi4ls IP を IP Catalog に追加した。そてひ、 uart_rx_axi4ls_top_tb.sv のソースコードを貼った。

まずは、Viavdo 2020.2 で uart_rx_axi4ls_top プロジェクトを作成した。
uart_rx_32_210224.png

uart_rx_axi4ls_top プロジェクトのディレクトリに uart_rx と uart_rx_axi4ls ディレクトリを新規作成した。

uart_rx ディレクトリには、uart_rx/solution1/impl/export.zip を展開して、コピーした。
uart_rx_30_210224.png

uart_rx_axi4ls ディレクトリには、uart_rx_axi4ls/solution1/impl/export.zip を展開して、コピーした。
uart_rx_31_210224.png

uart_rx IP と uart_rx_axi4ls IP を IP catalog に追加した。
uart_rx_32_210224.png

最後に、次回使用する SystemVerilog のテストベンチの uart_rx_axi4ls_top_tb.sv を貼っておく。
uart_rx にシリアル・データを入力して、

task automatic axi_master_rd_reg(input logic [5:0] araddr)

で AXI4 Lite インターフェースでキーコード + x 軸、 y 軸、 z軸のデータを読み出す。

`timescale 100ps / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 02/17/2021
// Design Name: 
// Module Name: uart_rx_axi4ls_top_tb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_rx_axi4ls_top_tb;
    parameter DELAY = 10; // delay = 1 ns
    
    logic ap_clk;
    logic ap_rst;
    logic ap_start;
    logic ap_done;
    logic ap_idle;
    logic ap_ready;
    logic ap_start_0;
    logic ap_done_0;
    logic ap_idle_0;
    logic ap_ready_0;
    logic rxst_V_dout;
    logic [5:0] s_axi_control_araddr;
    logic s_axi_control_arready;
    logic s_axi_control_arvalid;
    logic [5:0] s_axi_control_awaddr;
    logic s_axi_control_awready;
    logic s_axi_control_awvalid;
    logic s_axi_control_bready;
    logic [1:0] s_axi_control_bresp;
    logic s_axi_control_bvalid;
    logic [31:0] s_axi_control_rdata;
    logic s_axi_control_rready;
    logic [1:0] s_axi_control_rresp;
    logic s_axi_control_rvalid;
    logic [31:0] s_axi_control_wdata;
    logic s_axi_control_wready;
    logic [3:0] s_axi_control_wstrb;
    logic s_axi_control_wvalid;
   
    uart_rx_axi4ls_top_wrapper uart_rx_axi4ls_top_i(.*);
    
    task gen_ap_clk;
        forever begin
            #52 ap_clk <= ~ap_clk; // 10.4 ns = 96.15 MHz
        end
    endtask
    
    task start_bit_gen;
        begin
            @(posedge ap_clk);
            #DELAY
            rxst_V_dout <= 1'b0;
            repeat(12*16)@(posedge ap_clk);
        end
    endtask
    
    task rx_data_gen(input logic [7:0] rxd);
        begin
            for(int i=0; i<8; i++) begin
                #DELAY
                rxst_V_dout <= rxd[i];
                repeat(12*16)@(posedge ap_clk);
             end
         end
     endtask
     
    task stop_bit_gen;
        begin
            #DELAY
            rxst_V_dout <= 1'b1;
            repeat(12*16)@(posedge ap_clk);
        end
    endtask
    
    task automatic axi_master_rd_reg(input logic [5:0] araddr);
        @(posedge ap_clk);
        #DELAY
        s_axi_control_araddr <= araddr;
        s_axi_control_arvalid <= 1'b1;
        do begin
            @(posedge ap_clk);
        end while (s_axi_control_arready == 1'b0);

        #DELAY
        s_axi_control_arvalid <= 1'b0;
        s_axi_control_rvalid <= 1'b1;
        do begin
            @(posedge ap_clk);
        end while (s_axi_control_rready == 1'b0);
        #DELAY
        s_axi_control_rvalid <= 1'b0;
    endtask //automatic

    initial begin
        ap_clk <= 1'b0;
        ap_rst <= 1'b1;
        ap_start <= 1'b0;
        ap_start_0 <= 1'b0;
        rxst_V_dout <= 1'b1;
        s_axi_control_araddr <= 0;
        s_axi_control_arvalid <= 1'b0;
        s_axi_control_awaddr <= 0;
        s_axi_control_awvalid <= 1'b0;
        s_axi_control_bready <= 1'b1;
        s_axi_control_rready <= 1'b1;
        s_axi_control_wdata <= 0;
        s_axi_control_wstrb <= 0;
        s_axi_control_wvalid <= 1'b0;
        
        #300 fork
            gen_ap_clk;
        join_none
        
        #300 ap_rst <= 1'b0;
        #300 ap_start <= 1'b1;
        ap_start_0 <= 1'b1;
        repeat(12*16)@(posedge ap_clk);
        
        #300 // X key
        start_bit_gen;
        rx_data_gen(0'h5A);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);
        
        #300 // x_data_0
        start_bit_gen;
        rx_data_gen(0'h11);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);

        #300 // x_data_1
        start_bit_gen;
        rx_data_gen(0'h22);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);

        #300 // x_data_2
        start_bit_gen;
        rx_data_gen(0'h33);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);

        #300 // y_data_0
        start_bit_gen;
        rx_data_gen(0'h44);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);

        #300 // y_data_1
        start_bit_gen;
        rx_data_gen(0'h55);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);

        #300 // y_data_2
        start_bit_gen;
        rx_data_gen(0'h66);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);

        #300 // z_data_0
        start_bit_gen;
        rx_data_gen(0'h77);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);

        #300 // z_data_1
        start_bit_gen;
        rx_data_gen(0'h88);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);

        #300 // y_data_2
        start_bit_gen;
        rx_data_gen(0'h99);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);

        #300
        axi_master_rd_reg(6'b01_0000); // 0x10 x_data
        #300
        axi_master_rd_reg(6'b10_0000); // 0x20 y_data
        #300
        axi_master_rd_reg(6'b11_0000); // 0x30 z_data
        repeat(12*16)@(posedge ap_clk);

        $stop;
    end
endmodule


uart_rx_axi4ls IP の AXI4 Lite のアドレスマップを貼っておく。

//------------------------Address Info-------------------
// 0x00 : reserved
// 0x04 : reserved
// 0x08 : reserved
// 0x0c : reserved
// 0x10 : Data signal of x_data
//        bit 31~0 - x_data[31:0] (Read)
// 0x14 : Control signal of x_data
//        bit 0  - x_data_ap_vld (Read/COR)
//        others - reserved
// 0x20 : Data signal of y_data
//        bit 31~0 - y_data[31:0] (Read)
// 0x24 : Control signal of y_data
//        bit 0  - y_data_ap_vld (Read/COR)
//        others - reserved
// 0x30 : Data signal of z_data
//        bit 31~0 - z_data[31:0] (Read)
// 0x34 : Control signal of z_data
//        bit 0  - z_data_ap_vld (Read/COR)
//        others - reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)

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

uart_rx を使用して 3 軸加速度センサー用のデータを作成する uart_rx_axi4ls IP の作成2

uart_rx を使用して 3 軸加速度センサー用のデータを作成する uart_rx_axi4ls IP の作成1”の続き。

uart_rx の後段で 3 軸加速度センサー用のデータを作成する IP である uart_rx_axi4ls IP を作成していこうということで、前回は、ソースコードとテストベンチのコードを貼って、 Vitis HLS 2020.2 の uart_rx_axi4ls プロジェクトを作成した。今回は、C シミュレーション、C コードの合成、 C/RTL 協調シミュレーション、Export RTL を行う。

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

x_data = 55112233, y_data = 445566, z_data = 778899

と表示されている。これで正しい。

次に、C コードの合成を行った。結果を示す。
uart_rx_23_210223.png
uart_rx_24_210223.png

Latency は 13 クロックだった。
合成された Verilog HDL ファイルを示す。
uart_rx_axi4ls.v、 uart_rx_axi4ls_control_s_axi.v、 uart_rx_axi4ls_regslice_both.v の 3 個だった。
uart_rx_27_210223.png

uart_rx_axi4ls_control_s_axi.v のレジスタ・マップ部分を示す。

//------------------------Address Info-------------------
// 0x00 : reserved
// 0x04 : reserved
// 0x08 : reserved
// 0x0c : reserved
// 0x10 : Data signal of x_data
//        bit 31~0 - x_data[31:0] (Read)
// 0x14 : Control signal of x_data
//        bit 0  - x_data_ap_vld (Read/COR)
//        others - reserved
// 0x20 : Data signal of y_data
//        bit 31~0 - y_data[31:0] (Read)
// 0x24 : Control signal of y_data
//        bit 0  - y_data_ap_vld (Read/COR)
//        others - reserved
// 0x30 : Data signal of z_data
//        bit 31~0 - z_data[31:0] (Read)
// 0x34 : Control signal of z_data
//        bit 0  - z_data_ap_vld (Read/COR)
//        others - reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)



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

Latency は 14 クロックで、 Initiation Interval は 47 クロックだった。

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

x_data, y_data, z_data を AXI4 Lite インターフェース経由で取得できている。

Export RTL の結果を示す。
uart_rx_28_210223.png

問題ない。

次は、 uart_rx と uart_rx_axi4ls をつないで Vivado でシミュレーションを行う。
  1. 2021年02月24日 03:33 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

uart_rx を使用して 3 軸加速度センサー用のデータを作成する uart_rx_axi4ls IP の作成1

調歩同期式シリアル通信の受信 IP (uart_rx)を Vitis HLS で作成する3”の続き。

調歩同期方式シリアル通信の受信 IP を Vitis HLS で作成することにしたということで、前回は、Vivado 2020.2 の uart_rx プロジェクトを作成し、合成された uart_rx の Verilog HDL ファイルをコピーし、SystemVerilog のテストベンチファイルを作成して、Vivado 上でシミュレーションして、問題なく動作しそうだということが分かった。今度は、その uart_rx の後段で 3 軸加速度センサー用のデータを作成する IP である uart_rx_axi4ls IP を作成していこう。
どういうことか?というと 3 軸加速度センサーからのシリアルデータは、キーコード、 x 軸データ 1, 2, 3 と 3 個のデータをまとめて 32 ビット幅データにする。次の 3 個のデータは y 軸データなので、 32 ビット幅データにする。次の 3 個のデータは z 軸データなので、同様に 32 ビット幅データにする。そして、 x 軸、 y 軸、 z 軸のデータを AXI4 Lite のレジスタとしてマップして、 Zynq の ARM プロセッサから見えるようにするのがこの IP の目的となる。

さて、ソースコードを貼っておく。
まずは、ソースコードの uart_rx_axi4ls.cpp から貼っておく。
AXI4 Lite インターフェースを指示子で指定できるので、Vitis HLS で書くと行数が少なくなり楽に書けると思う。

// uart_rx_axi4ls.cpp
// 2021/02/17 by marsee
//

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

int uart_rx_axi4ls(hls::stream<ap_uint<8> >&rx_data, ap_uint<32> &x_data,
        ap_uint<32> &y_data, ap_uint<32> &z_data){
#pragma HLS INTERFACE axis port=rx_data register_mode=both register
#pragma HLS INTERFACE s_axilite port=z_data
#pragma HLS INTERFACE s_axilite port=y_data
#pragma HLS INTERFACE s_axilite port=x_data
#pragma HLS INTERFACE ap_ctrl_hs port=return
    ap_uint<32> xd=0, yd=0, zd=0;
    ap_uint<8> rd;

    LOOP_X: for(int i=0; i<4; i++){ // key + x_data
#pragma HLS PIPELINE II=1 rewind
        xd <<= 8;
        rx_data >> rd;
        xd = xd | rd;
    }
    x_data = xd;

    LOOP_Y: for(int i=0; i<3; i++){ // y_data
#pragma HLS PIPELINE II=1 rewind
        yd <<= 8;
        rx_data >> rd;
        yd = yd | rd;
    }
    y_data = yd;

    LOOP_Z: for(int i=0; i<3; i++){ // Z_data
#pragma HLS PIPELINE II=1 rewind
        zd <<= 8;
        rx_data >> rd;
        zd = zd | rd;
    }
    z_data = zd;

    return(0);
}


次にテストベンチの uart_rx_axi4ls_tb.cpp を貼っておく。

// uart_rx_axi4ls_tb.cpp
// 2021/02/18 by marsee
//

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

int uart_rx_axi4ls(hls::stream<ap_uint<8> >&rx_data, ap_uint<32> &x_data,
        ap_uint<32> &y_data, ap_uint<32> &z_data);

int main(){
    hls::stream<ap_uint<8> > rx_data;
    ap_uint<32> x_data, y_data, z_data;

    ap_uint<8> rxd[10] = {0x55, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99};
    // key-code + x_data 3 bytes, y_data 3 bytes, z_data 3 bytes

    for(int i=0; i<10; i++){
        rx_data << rxd[i];
    }

    uart_rx_axi4ls(rx_data, x_data, y_data, z_data);

    printf("x_data = %x, y_data = %x, z_data = %x\n",
        (unsigned int)x_data, (unsigned int)y_data, (unsigned int)z_data);

    return(0);
}


Vitis HLS 2020.2 で ZYBO Z7-20 用の uart_rx_axi4ls プロジェクトを作成した。
uart_rx_21_210222.png
  1. 2021年02月23日 04:32 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

調歩同期式シリアル通信の受信 IP (uart_rx)を Vitis HLS で作成する3

調歩同期式シリアル通信の受信 IP (uart_rx)を Vitis HLS で作成する2”の続き。

調歩同期方式シリアル通信の受信 IP を Vitis HLS で作成することにしたということで、前回は、uart_rx プロジェクトで C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行って、uart_rx IP を作成した。今回は、Vivado 2020.2 の uart_rx プロジェクトを作成し、合成された uart_rx の Verilog HDL ファイルをコピーし、SystemVerilog のテストベンチファイルを作成して、Vivado 上でシミュレーションしてみよう。

まずは、”SystemVerilogで遊ぼう! - 03. テストベンチトップ”と”SystemVerilog 入門”を参照して SystemVerilog のテストベンチを書いてみよう。とても SystemVerilog テストベンチ書くの初めてなので、Verilog HDL っぽくなるのは許して欲しい。
uart_rx_tb.sv を貼っておく。

`timescale 100ps / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: marsee
// 
// Create Date: 02/16/2021
// Design Name: 
// Module Name: uart_rx_tb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_rx_tb;
    parameter DELAY = 10; // delay = 1 ns
    
    logic ap_clk;
    logic ap_rst;
    logic ap_start;
    logic ap_done;
    logic ap_idle;
    logic ap_ready;
    logic rxst_V_dout;
    logic rxst_V_empty_n;
    logic rxst_V_read;
    logic [7:0] rx_data;
    logic rx_data_ap_vld;
    logic rx_data_ap_ack;
    logic [31:0] ap_return;
    
    uart_rx uart_rx_i(.*);
    
    task gen_ap_clk;
        forever begin
            #52 ap_clk <= ~ap_clk; // 10.4 ns = 96.15 MHz
        end
    endtask
    
    task start_bit_gen;
        begin
            @(posedge ap_clk);
            #DELAY
            rxst_V_dout <= 1'b0;
            rxst_V_empty_n <= 1'b1;
            repeat(12*16)@(posedge ap_clk);
        end
    endtask
    
    task rx_data_gen(input logic [7:0] rxd);
        begin
            for(int i=0; i<8; i++) begin
                #DELAY
                rxst_V_dout <= rxd[i];
                rxst_V_empty_n <= 1'b1;
                repeat(12*16)@(posedge ap_clk);
             end
         end
     endtask
     
    task stop_bit_gen;
        begin
            #DELAY
            rxst_V_dout <= 1'b1;
            rxst_V_empty_n <= 1'b1;
            repeat(12*16)@(posedge ap_clk);
        end
    endtask
    
    initial begin
        ap_clk <= 1'b0;
        ap_rst <= 1'b1;
        ap_start <= 1'b0;
        rxst_V_dout <= 1'b1;
        rx_data_ap_ack <= 1'b0;
        
        #300 fork
            gen_ap_clk;
        join_none
        
        #300 ap_rst <= 1'b0;
        #300 ap_start <= 1'b1;
        rxst_V_empty_n <= 1'b1;
        rx_data_ap_ack <= 1'b1;
        repeat(12*16)@(posedge ap_clk);
        
        #300 // First charactor
        start_bit_gen;
        rx_data_gen(0'h55);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);
        
        #300 // Second charactor
         start_bit_gen;
        rx_data_gen(0'haa);
        stop_bit_gen;
        repeat(12*16)@(posedge ap_clk);
        $stop;
    end
endmodule



さて、Vivado 2020.2 で uart_rx プロジェクトを作成した。
uart_rx_13_210221.png

前回、Vitis HLS 2020.2 で合成した Verilog HDL ファイル が 2 個ある。
uart_rx_14_210221.png

uart_rx.v と uart_rx_regslice_forward.v を Vivado の uart_rx プロジェクトのディレクトリにコピーした。
uart_rx_15_210221.png

uart_rx.v と uart_rx_regslice_forward.v を Vivado の uart_rx プロジェクトに Add Sources... を行った。
uart_rx_16_210221.png

uart_rx_tb.sv を新規作成し、SystemVerilog コードを書いて、右クリックし右クリックメニューから Set as Top を行った。
uart_rx_17_210221.png

Vivado IDE の左のウインドウの Flow Navigator から SIMULATION -> Run Simulation -> Run Behavioral Simulation を選択して、論理シミュレーションを行う。

Socpe ウインドウから ap_CS_fsm[8:0] を波形ウインドウにドラック&ドロップして追加した。
uart_rx_18_210221.png

Restart ボタンをクリックして、シミュレーションをリスタートする。
Run All ボタンをクリックして、シミュレーションを最後まで実行した。
uart_rx_19_210221.png

波形ウインドウを示す。
uart_rx_20_210221.png

問題無さそうだ。
  1. 2021年02月22日 03:46 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

調歩同期式シリアル通信の受信 IP (uart_rx)を Vitis HLS で作成する2

調歩同期式シリアル通信の受信 IP (uart_rx)を Vitis HLS で作成する1”の続き。

調歩同期方式シリアル通信の受信 IP を Vitis HLS で作成することにしたということで、前回は、ソースコードをブログに貼って、Vitis HLS 2020.2 の uart_rx プロジェクトを作成した。今回は、uart_rx プロジェクトで C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行って、uart_rx IP を作成しよう。

前回、ソースコードを貼ったのだが、このソースコードで、どの程度、TREADY が 0 の Wait が発生するか?を C/RTL 協調シミュレーションで見ていきたい。それで、使い物になるかどうか?が決まることだろう。

まずは、C シミュレーションからやってみよう。結果を示す。
uart_rx_4_210219.png

0x55 と 0xaa が受かっている。

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

12×(16÷2−1)+12×16×8+12×16 = 1812 クロックのはずなのだが、1820 クロックになっている。 1820 / 1812 ≒ 1.0044 で 0.44 % の誤差なので、大丈夫そうだね。

さて、お待ちかねの C/RTL 協調シミュレーションをやってみる。
uart_rx_7_210219.png

2202 クロックだった。

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

いい感じだと思う。

波形の前の部分、rxst_V_dout[0:0] が 0 になった時のレイテンシを拡大して見てみよう。
uart_rx_8_210219.png

rxst_V_dout[0:0] が 0 になるタイミングを見ると、rxst_V_dout[0:0] が1 の時には、 rxst_V_read が 0 , 1 を繰り返している。つまり、rxst_V_dout[0:0] が 0 になるのを検出するのに 1 クロックのレイテンシが発生する可能性があるということが分かる。

次に rxst_V_read が 0 になるタイミングは、 12 x 7 クロック後に 0 (スタートビット)を検出するタイミングとなる。そこでも 1 クロック間 rxst_V_read が 0 になる。
uart_rx_10_210219.png

最後に rxst_V_read が 0 になるタイミングは、ストップビットを検出するタイミングだ。ここでは、TVALID が 1 クロック間 0 になっていることもあるだろうが 2 クロック間 0 になっている。
uart_rx_12_210219.png

つまり、全体のクロック数 12 x 16 + 12 x 16 x 8 + 12 x 16 = 1920 クロックの内の 3 クロックだけ伸びているので、 3 / 1920 x 100 ≒ 0.16 % ずれていることになるが、問題無さそうだ。

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

問題無さそうだ。

次に、ものが物だけに心配なので、Vivado でプロジェクトを作って、RTL シミュレーションをやってみようと思う。
  1. 2021年02月21日 04:30 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

調歩同期式シリアル通信の受信 IP (uart_rx)を Vitis HLS で作成する1

調歩同期方式シリアル通信の受信 IP を Vitis HLS で作成することにした。
Xilinx 社のUART IP として AXI Uartlite があるが、これはボーレートを自分で自由に決めることができない。
PIC マイコンで生成された調歩同期方式シリアル通信データをなるべく高速に受けたいというモチベーションがあるのだが、どうもいつも使っているボーレートにすることが難しいので、任意にボーレートを決定したいためだ。今回は、500kbps のボーレートで通信したい。
今回は、96 MHz のクロックを使用して、それを 12 分周すると 8 MHz になって、ちょうど 500 kHz x 16 となる。つまり、12 分周 x 16 分周でやってみよう。

調歩同期方式シリアル通信については、以前のブログ記事”シリアルインターフェース(RS-232C)の説明”を見て欲しい。ここで、サンプリングするクロックが 500kHz の 16 倍になっている場合が今回の事例となる。そう 12 分周 x 16 分周の 16 分周の部分がサンプリング・クロック周期だ。

さて、ソースコードを貼っておく。
まずは、 uart_rx.h から。

// uart_rx.h
// 2021/02/16 by marsee
//

#ifndef __UART_RX_H__
#define __UART_RX_H__

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

#define DIV_8M  12
#define DIV_500k 16

int uart_rx(hls::stream<ap_uint<1> >& rxst, ap_uint<8>& rx_data);

#endif


ソースコードの uart_rx.cpp を貼っておく。

// uart_rx.cpp
// 2021/02/12 by marsee
// 動作周波数は 96 MHz, 8 MHz x 12
// 32000000/(16*(3+1)) = 500kbps, サンプル周波数 500k x 16 = 8 MHz

#include "uart_rx.h"

bool det_zero_bit(hls::stream<ap_uint<1> >& rxst){
    ap_uint<1> rx;

    LOOP_DZB1: for(int i=0; i<DIV_8M; i++){
        LOOP_DZB2: for(int j=0; j<(DIV_500k/2-1); j++){
#pragma HLS PIPELINE II=1 rewind
            rxst >> rx;
        }
    }
    if(rx == 0)
        return(false); // 0 検出
    else
        return(true);
}

ap_uint<8> det_data_byte(hls::stream<ap_uint<1> >& rxst){
    ap_uint<8> data = 0;
    ap_uint<1> rx;

    LOOP_DDB1: for(int k=0; k<8; k++){
        LOOP_DDB2: for(int i=0; i<DIV_8M; i++){
            LOOP_DDB3: for(int j=0; j<DIV_500k; j++){
#pragma HLS PIPELINE II=1 rewind
                rxst >> rx;
                if(i==(DIV_8M-1) && j==(DIV_500k-1)){
                    data >>= 1;
                    if(rx == 1)
                        data |= (ap_uint<8>)128;
                }
            }
        }
    }
    return(data);
}

int check_stop_bit(hls::stream<ap_uint<1> >& rxst){
    int ret_val;
    ap_uint<1> rx;

    LOOP_CSB1: for(int i=0; i<DIV_8M; i++){
        LOOP_CSB2: for(int j=0; j<DIV_500k; j++){
#pragma HLS PIPELINE II=1 rewind
            rxst >> rx;
            if(i==(DIV_8M-1) && j==(DIV_500k-1)){
                if(rx == 0)
                    ret_val = 1;
                else
                    ret_val = 0;
            }
        }
    }
    return(ret_val);
}

int uart_rx(hls::stream<ap_uint<1> >& rxst, ap_uint<8>& rx_data){
#pragma HLS INTERFACE ap_hs port=rx_data
#pragma HLS INTERFACE ap_ctrl_hs port=return

    int result;
    ap_uint<1> rx;

    LOOP_WAIT: do{
#pragma HLS LOOP_TRIPCOUNT avg=1 max=1 min=1
        rxst >> rx;
        if(rx == 0)
            result = det_zero_bit(rxst);
        else
            result = true;
    }while(result);

    rx_data = det_data_byte(rxst);

    return(check_stop_bit(rxst));
}


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

// uart_rx_tb.cpp
// 2021/02/16 by marsee
// 動作周波数は 96 MHz, 8 MHz x 12
// 32000000/(16*(3+1)) = 500kbps, サンプル周波数 500k x 16 = 8 MHz

#include "uart_rx.h"

int rx_data_gen(ap_uint<8> rxdata, hls::stream<ap_uint<1> >& rxst){
    // start bit
    for(int i=0; i<DIV_8M; i++){
        for(int j=0; j<DIV_500k; j++){
            rxst << 0;
        }
    }

    // data bit
    for(int k=0; k<8; k++){
        for(int i=0; i<DIV_8M; i++){
            for(int j=0; j<DIV_500k; j++){
                rxst << (rxdata & 1);
                if(i==DIV_8M-1 && j==DIV_500k-1){
                    rxdata >>= 1;
                }
            }
        }
    }

    // stop bit
    for(int i=0; i<DIV_8M; i++){
        for(int j=0; j<DIV_500k; j++){
            rxst << 1;
        }
    }
    return(0);
}

int main(){
    hls::stream<ap_uint<1> > rxst;
    ap_uint<8> rxd0, rxd1;

    for(int i=0; i<DIV_8M; i++){
        for(int j=0; j<DIV_500k; j++){
            rxst << 1;
        }
    }
    rx_data_gen(0x55, rxst);
    rx_data_gen(0xAA, rxst);

    uart_rx(rxst, rxd0);
    uart_rx(rxst, rxd1);
    printf("Receive Data = %x, %x\n", (unsigned int)rxd0, (unsigned int)rxd1);

    return(0);
}


Vitis HLS 2020.2 で uart_rx プロジェクトを作成した。

uart_rx プロジェクトを作成する途中の New Vitis HLS Project ダイアログの Project Configuration 画面を示す。
uart_rx_1_210219.png

Solution Configuration 画面を示す。 Clock Period に 96MHz を設定し、Part は Zybo Z7-20 に設定した。
uart_rx_2_210219.png

ソースコードやテストベンチを設定した後のuart_rx プロジェクトを示す。
uart_rx_3_210219.png
  1. 2021年02月20日 03:28 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

Zynq で PL から PS に割り込みを掛ける3

Zynq で PL から PS に割り込みを掛ける2”の続き。

FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”の second プロジェクトを改造して、プッシュボタンスイッチを押した時に割り込みを掛けて RGB LED のON/OFF 制御をしてみようということで、前回は、 second プロジェクトのブロックデザインに押しボタンスイッチによって PL から PS への割り込みを追加して、動作を確認することができた。今回は、”Zynq で PL から PS に割り込みを掛ける1”で、 IRQ_F2P 割り込みはレベル割り込みとエッジ割り込みの 2 種類から選べるはずだが、どちらになっているか?を確認してみよう。

まずは、”Zynq-7000 SoC テクニカル リファレンス マニュアル UG585 (v1.10) 2015 年 2 月 23 日”の219ページの図 7-4のICD ICFR 2~ICD ICFR 5を引用する。
second_58_210218.png

ピンクの枠で囲ったところが、 IRQ_F2P 割り込みに対応する部分だ。
ICD ICFR レジスタの該当する割り込みの番号に割り当てる割り込みの種類は

01: High-level active
11: Rising-edge active


ということだ。

また、embeddedsw/XilinxProcessorIPLib/drivers/scugic/src/xscugic_hw.h にも Interrupt Configuration Register 0xC00-0xCFC の記述があるので、引用する。
second_59_210218.png

embeddedsw/XilinxProcessorIPLib/drivers/scugic/src/xscugic.c には、

void XScuGic_SetPriorityTriggerType(XScuGic *InstancePtr, u32 Int_Id, u8 Priority, u8 Trigger)

void XScuGic_GetPriorityTriggerType(XScuGic *InstancePtr, u32 Int_Id, u8 *Priority, u8 *Trigger)

がある。これでプライオリティと割り込みのトリガーを指定することができる。

プライオリティと割り込みのトリガーを指定する方法は、武内先生の”Xilinx の XAxiDma ドライバのサンプルを読む”を参照のこと。

さて、それでは、XScuGic_GetPriorityTriggerType() を second_lef_off.c に追加して、プライオリティと割り込みのトリガーが何になっているか?を見てみよう。(second_led_off.c については”Zynq で PL から PS に割り込みを掛ける2”を参照のこと)
追加したコードを示す。

    XScuGic_GetPriorityTriggerType(&IntcInstance,XPS_FPGA0_INT_ID,
            &Priority, &Trigger);
    xil_printf("XPS_FPGA0_INT_ID Priority = %x, Trigger = %x\n", (int)Priority, (int)Trigger);

    XScuGic_GetPriorityTriggerType(&IntcInstance,XPS_FPGA1_INT_ID,
            &Priority, &Trigger);
    xil_printf("XPS_FPGA1_INT_ID Priority = %x, Trigger = %x\n", (int)Priority, (int)Trigger);


second_60_210218.png

ビルドして Run するとプライオリティと割り込みのトリガーの種類が表示された。
second_61_210218.png

XPS_FPGA0_INT_ID Priority = A0, Trigger = 1
XPS_FPGA1_INT_ID Priority = A0, Trigger = 1


よって、現在の割り込みのプライオリティは 0xA0 で、割り込みのトリガーの種類はハイ・レベルによる割り込みであるということが確認できた。
  1. 2021年02月19日 03:27 |
  2. Zynq
  3. | トラックバック:0
  4. | コメント:0

Zynq で PL から PS に割り込みを掛ける2

Zynq で PL から PS に割り込みを掛ける1”の続き。

FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”の second プロジェクトを改造して、プッシュボタンスイッチを押した時に割り込みを掛けて RGB LED のON/OFF 制御をしてみようということで、前回は、前振りとして、PL から PS への割り込み方法について調査した。今回は、 second プロジェクトのブロックデザインに押しボタンスイッチによって PL から PS への割り込みを追加する。

まずは second プロジェクトのブロックデザインで processing_system7_0 をダブルクリックして設定画面を開く。

Page Navigator で Interrupt をクリックする。
Fabric Interrupt -> PL-PS Interrupt Ports を展開する。
IRG_F2P[15:0] にチェックを入れる。
second_53_210214.png

processing_system7_0 に IRQ_F2P[0:0] が増えた。
BTN[1:0] を IRQ_F2P[0:0] に接続するのだが、”Zynq で PL から PS に割り込みを掛ける1”に書いたとおりに、 Slice で BTN[1] と BTN{0] に分けてから Concat でまとめて IRQ_F2P[0:0] に入れる。まだ IRQ_F2P[0:0] のままになっている。(なお、このブロックデザインでは、 Slice を接続する Concat のポートが間違っている。Sletected_bit1 の Slice が Concat の In1 に接続されるはずだ)
second_54_210214.png

論理合成、インプリメンテーション、ビットストリームの生成後のブロックデザインを示す。(この時は、Sletected_bit1 の Slice が Concat の In1 に接続されていて、バグが修正されている)
second_55_210214.png

IRQ_F2P[1:0] に修正されていて、正常なビット長になっている。

Project Summary を示す。
second_56_210214.png

ハードウェアをエクスポートして、 XSA ファイルを更新した。

Vitis で design_1_wrapper プラットフォームを右クリックし右クリックメニューから Update Hardware Specification を選択する。
Update Hardware Specification ダイアログが表示される。更新した XSA ファイルを指定して、プラットフォームをアップデートする。
プラットフォームを再度ビルドする。
second_lef_off アプリケーション・プロジェクトを新規作成した。
second_led_off_system -> second_led_off -> src を右クリックし右クリックメニューから New -> File を選択して、 second_led_off.c を新規作成した。
ソースコードは ”FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”の second.c を改造して使用している。
second_57_210215.png

これで、アプリケーション・ソフトウェアを Run すると、 second プロジェクトと同様に RGB LED が色を変えながら点灯する。
そして、BTN0 で RGB LED の点灯が停止して、 BTN1 で RGB LED の点灯が再開した。成功だ。

最後に、小林様のご厚意で、 second_lef_off.c の全文を掲載させていただく。

/* Copyright(C) 2020 Cobac.Net All Rights Reserved. */
/* chapter: 第5章                  */
/* Vivado : second                 */
/* Vitis  : second                 */
/* outline: タイマー割り込みテスト */

// second_led_off.c 2021/02/14 by marsee
// second.c にPLの割り込みを追加して BTN0 でLED OFF、BTN1で LED ON するようにコードを変更した

#include "xparameters.h"
#include "xgpio.h"
#include "xscutimer.h"
#include "xscugic.h"
#include "xil_exception.h"
#include "xil_printf.h"

#define TIMER_LOAD_VALUE 333333333  /* CPU周波数667MHzの1/2 */
#define LED_CHANNEL      1          /* ch1:LED ch2:BTN      */

/* 各周辺回路のインスタンス変数 */
XGpio Gpio;
XScuTimer TimerInstance;
XScuGic IntcInstance;

int led_stat = 1;

/* LED表示パターン作成 */
int led_rgb(int cnt)
{
    int led;
    switch ( cnt%5 ) {
        case 0: led = 0x4; break;
        case 1: led = 0x2; break;
        case 2: led = 0x1; break;
        case 3: led = 0x7; break;
        case 4: led = 0x0; break;
        default:led = 0x0;
    }
    return led;
}

/* タイマー割り込み関数 */
void TimerCounterHandler(void *CallBackRef)
{
    volatile static int cnt;
    XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;

    if (XScuTimer_IsExpired(TimerInstancePtr)) {
        XScuTimer_ClearInterruptStatus(TimerInstancePtr);
        if (led_stat == 1){
            if ( ++cnt>9 ) cnt = 0;
        }else{
            cnt = 4;
        }
        xil_printf("cnt=%d\n", cnt);
        XGpio_DiscreteWrite(&Gpio, LED_CHANNEL, led_rgb(cnt));
    }
}

// LED_on 割り込み関数
void LED_ON_Handler(void *CallBackRef){
    led_stat = 1;
}

// LED_off 割り込み関数
void LED_OFF_Handler(void *CallBackRef){
    led_stat = 0;
}

/* 割り込みコントローラのドライバ初期化 */
int ScuGicInt_Init( void )
{
    int Status;
    XScuGic_Config *ConfigPtr;
    ConfigPtr = XScuGic_LookupConfig(XPAR_PS7_SCUGIC_0_DEVICE_ID);
    Status = XScuGic_CfgInitialize(&IntcInstance, ConfigPtr,
            ConfigPtr->CpuBaseAddress);
    if (Status != XST_SUCCESS) return XST_FAILURE;
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
            (Xil_ExceptionHandler) XScuGic_InterruptHandler,
            &IntcInstance);
    Xil_ExceptionEnable();
    return XST_SUCCESS;
}

/* 割り込み処理関数の登録 */
int ScuGicInt_Reg(u32 Int_Id, void *InstancePtr, void *IntHandler)
{
    int Status;
    Status = XScuGic_Connect(&IntcInstance,
            Int_Id,
            (Xil_ExceptionHandler)IntHandler,
            (void *)InstancePtr);
    if (Status != XST_SUCCESS) return XST_FAILURE;
    XScuGic_Enable(&IntcInstance, Int_Id);
    return XST_SUCCESS;
}

int main()
{
    int Status;
    XScuTimer_Config *ConfigPtr;

    xil_printf("Timer Interrupt Test.\n\n");

    /* GPIOの初期化 */
    Status = XGpio_Initialize(&Gpio, XPAR_GPIO_0_DEVICE_ID);
    if (Status != XST_SUCCESS) return XST_FAILURE;
    XGpio_SetDataDirection(&Gpio, LED_CHANNEL, 0);
    XGpio_DiscreteWrite(&Gpio, LED_CHANNEL, led_rgb(0));

    /* タイマーのドライバ初期化 */
    ConfigPtr = XScuTimer_LookupConfig(XPAR_XSCUTIMER_0_DEVICE_ID);
    Status = XScuTimer_CfgInitialize(&TimerInstance, ConfigPtr,
                    ConfigPtr->BaseAddr);
    if (Status != XST_SUCCESS) return XST_FAILURE;

    /* 割り込み関連初期化と割り込み処理関数の登録 */
    Status = ScuGicInt_Init();
    if (Status != XST_SUCCESS) return XST_FAILURE;
    Status = ScuGicInt_Reg(XPAR_SCUTIMER_INTR, &TimerInstance,
                           TimerCounterHandler);
    if (Status != XST_SUCCESS) return XST_FAILURE;

    // BTN 1 , LED ON
    Status = XScuGic_Connect(&IntcInstance,
            XPS_FPGA1_INT_ID,
            (Xil_ExceptionHandler)LED_ON_Handler,
            (void *)NULL);
    if (Status != XST_SUCCESS) return XST_FAILURE;
    XScuGic_Enable(&IntcInstance, XPS_FPGA1_INT_ID);

    // BTN 0 , LED OFF
    Status = XScuGic_Connect(&IntcInstance,
            XPS_FPGA0_INT_ID,
            (Xil_ExceptionHandler)LED_OFF_Handler,
            (void *)NULL);
    if (Status != XST_SUCCESS) return XST_FAILURE;
    XScuGic_Enable(&IntcInstance, XPS_FPGA0_INT_ID);

    /* タイマーの初期設定と開始 */
    XScuTimer_EnableAutoReload(&TimerInstance);
    XScuTimer_LoadTimer(&TimerInstance, TIMER_LOAD_VALUE);
    XScuTimer_EnableInterrupt(&TimerInstance);
    XScuTimer_Start(&TimerInstance);

    while(1);
    return 0;
}


  1. 2021年02月18日 03:35 |
  2. Zynq
  3. | トラックバック:0
  4. | コメント:0

Zynq で PL から PS に割り込みを掛ける1

FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”の second プロジェクトを改造して、プッシュボタンスイッチを押した時に割り込みを掛けて RGB LED のON/OFF 制御をしてみよう。
今回は、Zynq 割り込みコントローラを構造を確認して、 Xilinx のライブラリを使用した設定方法を見ていこう。

まずは、”Zynq-7000 SoC テクニカル リファレンス マニュアル UG585 (v1.10) 2015 年 2 月 23 日”を参照する。

Zynq-7000 SoC テクニカル リファレンス マニュアル UG585 (v1.10) 2015 年 2 月 23 日”の”図 1-1 : Zynq-7000 SoC の概要”を引用する。
zynq_1_210215.png

汎用割り込みコントローラー (GIC) があるのが分かる。

GIC のブロック図の”図 7-2 : 割り込みコントローラーのブロック図”を引用する。
zynq_2_210215.png

ピンクの四角で囲ったのが、 PL からの割り込みを受け付ける SPI だ。

”表 7-4 : PS と PL の共有ペリフェラル割り込み (SPI)”を引用する。
zynq_3_210215.png

PL[7:0] までの割り込みが書かれている。

”表 7-4 : PS と PL の共有ペリフェラル割り込み (SPI) (続き)”を引用する。
zynq_4_210215.png

PL[15:8] の割り込みが書かれている。

これは、IP では何処かと言うと、Zynq Processing System7 の設定画面の Interrupt をクリックし、 Fabric Interrupt の IRQ_F2P[15:0] に相当する。
zynq_5_210215.png

複数の割り込みを実装する場合に、 Fabric Interrupt の IRQ_F2P[15:0] にチェックを入れても、当初は IRQ_F2P[0:0] となってしまい 1 ビット幅のままである。これを適切なビット長にするためには、 Concat IP を使用して、 IRQ_F2P[0:0] に接続すれば良いそうだ。
How to use more than one IRQ_F2P interrupt?”を参照。
なお、Concat IP を接続しても、 IRQ_F2P[0:0] のままだが、論理合成を始めれば、ビット長が変更されるようだ。

ブロックデザインでは、BTN0, BTN1 を Zynq Processing System7 の IRQ_F2P[1:0] に接続する。
Vitis のソフトウェア・プロジェクトでは、 XScuGic_Connect() 関数を利用して、 BTN0 を押した時に RGB LED の点滅が中止するようにプログラムする。BTN1 を押すと RGB LED の点滅が再開する。

    // BTN 1 , LED ON
    Status = XScuGic_Connect(&IntcInstance,
            XPS_FPGA1_INT_ID,
            (Xil_ExceptionHandler)LED_ON_Handler,
            (void *)NULL);
    if (Status != XST_SUCCESS) return XST_FAILURE;
    XScuGic_Enable(&IntcInstance, XPS_FPGA1_INT_ID);

    // BTN 0 , LED OFF
    Status = XScuGic_Connect(&IntcInstance,
            XPS_FPGA0_INT_ID,
            (Xil_ExceptionHandler)LED_OFF_Handler,
            (void *)NULL);
    if (Status != XST_SUCCESS) return XST_FAILURE;
    XScuGic_Enable(&IntcInstance, XPS_FPGA0_INT_ID);


XScuGic_Connect() 関数は、 xscugic.c にある。
zynq_6_210215.png

最初の”XScuGic *InstancePtr”は、GIC のインスタンスへのポインタ、
2番目の”u32 Int_Id”は割り込み番号(後で説明する)
3番目の”Xil_InterruptHandler Handler”が割り込みのコールバック関数を登録する
4番目が割り込みのコールバック関数に渡すポインタ(ScuTimer の場合は、 XScuTimer 構造体へのポインタを渡していたが、LED ON / OFF の場合は必要ないため NULL ポインタを渡している)

2番目の”u32 Int_Id”の番号は、 xparameters_ps.h に定義されている。
zynq_7_210215.png

この場合は、 IRQ_F2P[1] が XPS_FPGA1_INT_ID に、 IRQ_F2P[0] が XPS_FPGA0_INT_ID に対応している。

割り込みのコールバック関数を示す。

// LED_on 割り込み関数
void LED_ON_Handler(void *CallBackRef){
    led_stat = 1;
}

// LED_off 割り込み関数
void LED_OFF_Handler(void *CallBackRef){
    led_stat = 0;
}


void *CallBackRef は使用していない。

最後に、 BTN1, BTN0 はチャタリングはケアしていない。この場合は、チャタリングをケアしなくても大丈夫だ。
それは、BTN1 には LED を ON する機能だけを割り当てて、 BTN0 には LED を OFF する機能だけを割り当てているので、何度も割り込みが入ってもその機能が何度も呼ばれるだけという理由だ。
今のところうまく行っている。もし、チャタリングにより IRQ_F2P 自体が誤動作するようだったら対策を考えることにする。
  1. 2021年02月17日 03:43 |
  2. Zynq
  3. | トラックバック:0
  4. | コメント:0

”FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”をやってみる3

”FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”をやってみる2”の続き。

FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”をやってみるということで、前回は、ラッパー Verilog HDL ファイルのトップファイルを生成し、制約ファイルを追加して、論理合成、インプリメンテーション、ビットストリームの生成を行う。そして、ハードウェアをエクスポートし、 XSA ファイルを生成した。今回は、Vitis 2020.2 を立ち上げて、プラットフォームとアプリケーション・プロジェクトを作成して、ZYBO Z7-10 で実機検証を行う。

Vivado の Tools メニューから Launch Vitis IDE を選択して、 Vitis 2020.2 を起動する。
Vitis IDE Launcher ダイアログが起動する。
Browse... ボタンをクリックして、 second ディレクトリの下に vitis_work ディレクトリを新規作成する。
Vitis IDE Launcher ダイアログに戻って、 Launch ボタンをクリックする。
second_40_210214.png

Vitis IDE が起動した。
Create Application Project をクリックする。
second_41_210214.png

New Application Project ダイアログが表示される。
Next > ボタンをクリックする。
second_42_210214.png

Platform 画面が表示された。
Create a new platform from hardware (XSA) タブをクリックする。
Browse... ボタンをクリックして、 XSA File に second/design_1_wrapper.xsa を指定する。
Next > ボタンをクリックする。
second_43_210214.png

Application Project Details 画面が表示された。
Application Project name に second を入力する。
Next > ボタンをクリックする。
second_44_210214.png

Domain 画面が表示された。
デフォルトのまま、Next > ボタンをクリックする。
second_45_210214.png

Templates 画面では、 Empty Application を選択する。
Finish ボタンをクリックする。
second_46_210214.png

design_1_wrapper プラットフォームと second プロジェクトが生成された。
second_47_210214.png

second.c をインポートする。
Vitis の Explorer の second_system -> second -> src を右クリックし右クリックメニューから Import Sources... を選択する。
サポートページからダウンロードした XilinxFPGA_SE.zip を展開したディレクトリの Zybo_Z7-10/dai5sho/second/VITIS ディレクトリを指定する。
Import Sources ダイアログで second.c にチェックを入れてインポートする。
second_48_210214.png

second.c の日本語コードは Shift JIS だったので、 UTF-8 に変更する。
second/vitis_work/second/src ディレクトリに行って、次のコマンドを実行した。
nkf -Lw -S --overwrite second.c
これで、 second.c の日本語コードは UTF-8 に変更された。
second_49_210214.png

second.c の一部を示す。
second_50_210214.png

トンカチボタンをクリックして、ビルドを行う。
ビルドが成功して、 second.elf ファイルが生成された。

Assistant ウインドウの second_system -> second -> Debug を右クリックし、右クリックメニューから Run -> Launch on Hardware (Single Application Debug) を選択して起動する。
second_51_210214.png

RGB LED が点灯して色が変わって行くのが確認できた。
gtkterm を確認した。
second_52_210214.png
  1. 2021年02月16日 04:12 |
  2. Zynq
  3. | トラックバック:0
  4. | コメント:0

”FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”をやってみる2

FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”をやってみるということで、前回は、Vivado 2020.2 の second プロジェクトを作成して、ブロックデザインを完成させた。今回は、ラッパー Verilog HDL ファイルのトップファイルを生成し、制約ファイルを追加して、論理合成、インプリメンテーション、ビットストリームの生成を行う。そして、ハードウェアをエクスポートし、 XSA ファイルを生成する。

まずはブロックデザインをセーブする。
Source タブをクリックし、 design_1 を右クリックし右クリックメニューから Create HDL Wrapper... を選択して、 HDL Wrapper file を作成する。
second_23_210213.png

desgin_1_wrapper.v が生成された。
second_24_210213.png

制約ファイルを生成する。
Source ウインドウ内で右クリックし右クリックメニューから Add Sources... を選択する。
Add Sources ダイアログが開く。
Add or create constraints のラジオボタンをクリックする。
Next > ボタンをクリックする。
second_25_210214.png

Add or Create Constraints 画面で、 Create File ボタンをクリックする。
second_26_210214.png

Create Constraints File ダイアログが開く。
File name に second を入力する。
OK ボタンをクリックする。
second_27_210214.png

Add or Create Constraints 画面に second.xdc が入った。
Finish ボタンをクリックする。
second_28_210214.png

空の second.xdc が表示された。
サポートページからダウンロードした XilinxFPGA_SE.zip を展開してできた Zybo_Z7-10/dai5sho/second ディレクトリの下の second.xdc から使用する部分だけを引用した。

#RGB LED
set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { LED_RGB[2] }]; # Red
set_property -dict { PACKAGE_PIN F17 IOSTANDARD LVCMOS33 } [get_ports { LED_RGB[1] }]; # Green
set_property -dict { PACKAGE_PIN M17 IOSTANDARD LVCMOS33 } [get_ports { LED_RGB[0] }]; # Blue

#Buttons
set_property -dict { PACKAGE_PIN K18 IOSTANDARD LVCMOS33 } [get_ports { BTN[0] }];
set_property -dict { PACKAGE_PIN P16 IOSTANDARD LVCMOS33 } [get_ports { BTN[1] }];


セーブボタンでセーブした。
second_29_210214.png

左の Flow Navigator 画面から Gererate Bitstream を選択して、論理合成、インプリメンテーション、ビットストリームの生成を行う。
すると、 Launch Runs ダイアログが表示される。
デフォルトのまま OK ボタンをクリックする。
second_30_210214.png

論理合成、インプリメンテーション、ビットストリームの生成を行って終了すると、 Bitstream Generation Completed ダイアログが表示された。
Cancel ボタンをクリックする。
second_31_210214.png

Feedback Request ダイアログが表示された。
Remind me Later をクリックした。
second_32_210214.png

Project Summary を表示するために Project Summary ボタンをクリックする。
second_33_210214.png

Project Summary が表示された。問題ないようだ。
second_34_210214.png

ハードウェアをエクスポートする。
Vivado の File メニューから Export -> Export Hardware... を選択する。

Export Hardware Platform ダイアログが表示された。
Next > ボタンをクリックする。
second_35_210214.png

Output 画面で Include bitstream のラジオボタンをクリックする。
Next > ボタンをクリックする。
second_36_210214.png

Files 画面が表示された。
デフォルトのまま Next > ボタンをクリックする。
second_37_210214.png

Exporting Hardware Platform の Summary が表示された。
Finish ボタンをクリックする。
second_38_210214.png

Vivado 2020.2 の second プロジェクトのディレクトリの下に、 design_1_wrapper.xsa ファイルが生成された。
second_39_210214.png
  1. 2021年02月15日 04:58 |
  2. Zynq
  3. | トラックバック:0
  4. | コメント:0

”FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”をやってみる1

小林さんから”FPGAプログラミング大全 Xilinx編 第2版”をいただいた。
大変ボリュームがある本で、

Xilinx のツールの使い方
Zynq の使用方法
MicroBlaze の使い方
IP の作り方
AXI バス
Vitis HLS 高位合成ツール


について書かれている凄い本だと思った。本をいただいておいて言うのも何だけど 3,800 円は安い。
その中から私が意図的に使ってこなかった Zynq の割り込みをやってみようと思う。具体的には”5-4 タイマー割り込みとAPIの利用”をやってみようと思う。
割り込みを使ってこなかった理由は、別に割り込みを使わなくても、コードが書けるし、割り込み使うとレジスタの待避に時間がかかるし、複雑に割り込み使うと時間に間に合うか計算するのが大変だし、デバックが面倒だからだ。usleep() などを使って、プログラムすればポーリングでも消費電力を減らせる。割り込み使うようなところはハードウェアにしちゃえば良いし、プログラム負荷が少なくなってポーリングでの実装が楽になる。

使用する FPGA ボードは ZYBO Z7-10 にしようと思う。
サポートページから XilinxFPGA_SE.zip をダウンロードして展開した。ダウンロードに時間がかかるようなので、余裕がある時にダウンロードすることをお勧めする。まだダウンロードが終了していないが Vivado のプロジェクトを作成していこう。

Ubuntu 18.04 LTS の Vivado 2020.2 で ZYBO Z7-10 用の second プロジェクトを作成する。

File メニューから Projcet -> New... を選択して新しいプロジェクトを作成する。
FPGAプログラミング大全 Xilinx編 第2版”の”5-4 タイマー割り込みとAPIの利用”に従ってプロジェクトを作成していく。

Project Name ダイアログを示す。
second_1_210213.png

間のダイアログは飛ばして、 Default Part で Boards をクリックし、Vendor を deiglentinc.com にして、 Zybo Z7-10 を選択する。
second_2_210213.png

New Project Summary を示す。 Finish ボタンをクリックする。
second_3_210213.png

second プロジェクトが生成された。
second_4_210213.png

Create Block Design をクリックして、新しいブロックデザインを生成する。

Create Block Design ダイアログが表示される。デフォルトのまま OK ボタンをクリックする。
second_5_210213.png

ブロックデザインが生成されて、Diagram ウインドウが開く。
真ん中の + ボタンをクリックして、Add IP しよう。
second_6_210213.png

Diagram ウインドウを Float した。
IP をリストするダイアログが開くので、 Zynq と入力すると Zynq Processing System が見つかるので、それをダブルクリックする。
second_7_210213.png

Zynq Processing System が Add IP された。
second_8_210213.png

Run Block Automation をクリックする。

Run Block Automation が表示される。これで ZYBO Z7-10 の設定値が入力される。
OK ボタンをクリックする。
second_9_210213.png

DDR と FIXED_IO が外部に出力され、 M_AXI_GP0 ポートなどが追加されている。
second_10_210213.png

Add IP ボタンをクリックして、 AXI_GPIO を追加する。

Search に GPIO と入れると AXI GPIO がリストされた。ダブルクリックして、 Add IP する。
second_11_210213.png

axi_gpio_0 が追加された。
axi_gpio_0 をダブルクリックして設定画面を表示する。
second_12_210213.png

GPIO の All Outputs にチェックを入れて、 GPIO Width を 3 に設定した。
GPIO 2 の All Inputs にチェックを入れて、 GPIO Width を 2 に設定した。
second_13_210213.png

Run Connection Automation をクリックする。
なお、下図は GPIO と GPIO2 を展開してある。
second_14_210213.png

Run Connection Automation ダイアログが表示された。
axi_gpio_0 の S_AXI にチェックを入れて自動配線する。
second_15_210213.png

Processing System Reset と AXI Interconnect IP が追加されて、自動配線された。
second_16_210213.png

axi_gpio_0 の gpio_io_o[2:0] と gpio2_io_i[1:0] を選択して、右クリックし右クリックメニューから Make External を選択する。

gpio_io_o[2:0] と gpio2_io_i[1:0] ポートができた。
second_17_210213.png

gpio2_io_i[1:0] ポートの名前を BTN に変更する。
second_18_210213.png

gpio_io_o[2:0] の名前を LED_RGB[2:0] に変更した。

Validate Design をクリックして、ブロックデザインが正しいかどうか?調べた。
second_19_210213.png

Critical Messages ダイアログがでたが、DDR の設定値に関することなので、OK だ。
second_20_210213.png

Address Editor 画面を示す。
second_21_210213.png

Address Map 画面を示す。
second_22_210213.png

これでブロックデザインは完成だ。
  1. 2021年02月13日 12:15 |
  2. Zynq
  3. | トラックバック:0
  4. | コメント:0

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にソーベル・フィルタを追加する3

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にソーベル・フィルタを追加する2”の続き。

IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”のUltra96V2のPicam2 - DisplayPort 表示システムにソーベル・フィルタを追加するということで、前回は、sobel_filter_axis_RBG10 プロジェクトで C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行った。今回は、Vivado で sobel_filter_axis_RBG10 IP をブロックデザインに追加して、論理合成、インプリメンテーション、ビットストリームの生成を行って、Vitis でアプリケーション・ソフトウェアを修正し、ソーベル・フィルタ画像をディスプレイに表示する。

Vivado 2020.2 の ultra96v2_picam2_dp_sobel_202 プロジェクトを作成した。
sobel_filter_RBG10_14_210211.png

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

Address Editor 画面を示す。
sobel_filter_RBG10_15_210211.png

Address Map 画面を示す。
sobel_filter_RBG10_16_210211.png

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

ハードウェアをエクスポートして、Vitis を立ち上げ、プラットフォームとアプリケーション・プロジェクトを作成した。
sobel_filter_RBG10_18_210211.png

アプリケーション・ソフトウェアを起動した。
gtkterm 画面を示す。
sobel_filter_RBG10_19_210211.png

ソーベル・フィルタ画像が表示された。
写真に取るとよく見えないかも知れない?
sobel_filter_RBG10_20_210212.jpg

しかし、ラプラシアン・フィルタの時もそうだったが、アプリケーション・ソフトウェアで下に sscanf() 関数を使用して元画像とソーベル・フィルタ画像を切り替えようとすると、カメラ画像が表示されないのはなぜだろうか?
  1. 2021年02月12日 06:09 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

MIPI D-PHY v4.2 のお勉強

MIPI CSI-2 Receiver Subsystem IP のお勉強”の続き。

MIPI D-PHY v4.2 LogiCORE IP Product Guide Vivado Design Suite PG202 (v4.2) September 7, 2020”に書いてあることをGoogle 翻訳で翻訳して図と一緒に引用してまとめていく。

Overview
MIPI D-PHYコントローラーはフル機能のIPコアであり、この高速I / Oインターフェイス規格で適切に通信するために必要なすべてのロジックが組み込まれている。 コアは、高速SelectIO™インターフェースを使用した標準フォーマットのPHYProtocolインターフェース(PPI)との間のカメラセンサーおよびビデオデータの送受信をサポートする。
次の図は、MIPI D-PHYとそのすべてのコンポーネントの概要を示している。
Figure 1: D-PHY IP Overview を引用する。
MIPI_CSI-2_Receiver_6_210211.png

MIPI D-PHY RX (Slave) Core Architecture
RX PCS Logic: PHYとインターフェイスし、高速およびエスケープモードの低電力データ転送(LPDT)パケットなどのPHYプロトコルインターフェイス(PPI)準拠のトランザクションを配信する。 また、レーンの初期化、送信開始(SoT)の検出、およびエスケープモードでのクロックリカバリも担当する。

RX PHY Logic: 高速モードおよび逆シリアル化でクロックリカバリを実行する。 BITSLICE_CONTROLとRX_BITSLICEをネイティブモードとD-PHY互換のI / Oブロックに統合する。

Register Interface: タイマーとレジスタの制御用プロトコルのためのAXI4-Lite レジスタ・インターフェース(オプション)

Figure 5: MIPI D-PHY RX (Slave) Core Architecture for UltraScale+ Families を引用する。
MIPI_CSI-2_Receiver_7_210211.png

アドレスマップ
Table 20: MIPI D-PHY Core Register Space を引用する。
MIPI_CSI-2_Receiver_8_210211.png
  1. 2021年02月11日 04:17 |
  2. IP
  3. | トラックバック:0
  4. | コメント:0

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にソーベル・フィルタを追加する2

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にソーベル・フィルタを追加する1”の続き。

IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”のUltra96V2のPicam2 - DisplayPort 表示システムにソーベル・フィルタを追加するということで、前回は、Vitis HLS 2020.2 で実装した RBG 10 ビットのソーベル・フィルタのコードと Vitis HLS 2020.2 の sobel_filter_axis_RBG10 プロジェクトを示した。今回は、sobel_filter_axis_RBG10 プロジェクトで C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行っていこう。

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

sobel_filter_axis_RBG10/solution/csim/build ディレクトリを示す。
sobel.bmp と org.bmp が生成されている。
元画像のサイズは 800 x 600 ピクセルだ。
sobel_filter_RBG10_3_210208.png

sobel.bmp を示す。
sobel_filter_RBG10_4_210208.jpg

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

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

Export RTL 結果を示す。
sobel_filter_RBG10_8_210208.png

800 x 600 ピクセルでやってきたが、実機は HD 解像度なので、 1920 x 1080 ピクセルに変更した。
sobel_filter_RBG10_10_210209.png

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

Export RTL の結果を示す。
sobel_filter_RBG10_13_210209.png

CLB, LUT, FF は 800 x 600 の方がリソース使用量が多いのはなぜだろう?
BRAM は 1920 x 1080 の方が 2 倍使用している。SRL は同じリソース使用量だ。
  1. 2021年02月10日 05:07 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にソーベル・フィルタを追加する1

前回は、”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”のUltra96V2のPicam2 - DisplayPort 表示システムにラプラシアン・フィルタを追加したが、よくエッジが見えなかった。画像がぼやけているのか?と思うが、Vitis HLS の C/RTL 協調シミュレーションでも余りエッジが出ていなかったので、今度はソーベル・フィルタを実装することにした。
今回は、Vitis HLS 2020.2 で実装した RBG 10 ビットのソーベル・フィルタのコードと Vitis HLS 2020.2 の sobel_filter_axis_RBG10 を示す。

最初に sobel_filter_axis_RBG10.h を示す。

// sobel_filter_axis_RBG10.h
// 2021/02/07 by marsee
//

#ifndef __SOBEL_FILTER_AXIS_RBG10_H__
#define __SOBEL_FILTER_AXIS_RBG10_H__

#define HORIZONTAL 0
#define VERTICAL 1

#define HD_RES

#ifdef HD_RES
#define DISPLAY_WIDTH 1920
#define DISPLAY_HIGHT 1080
#endif

#ifdef SVGA_RES
#define DISPLAY_WIDTH 800
#define DISPLAY_HIGHT 600
#endif

#ifdef SMALL_RES
#define DISPLAY_WIDTH 64
#define DISPLAY_HIGHT 48
#endif

#define ALL_PIXEL_VALUE (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define ORIGINAL_IMAGE 0
#define SOBEL_FILTER 1

#endif


なお、今は HD_RES を定義しているが、C/RTL 協調シミュレーションの途中まで、速度が遅くなるので、 SVGA_RES を定義しておくことにする。

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

// sobel_filter_RBG10.cpp
// 2021/02/07 by marsee
//

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

#include "sobel_filter_axis_RBG10.h"

ap_int<32> sobel_fil(ap_int<32> h_or_v, ap_int<32> x0y0, ap_int<32> x1y0, ap_int<32> x2y0, ap_int<32> x0y1,
        ap_int<32> x1y1, ap_int<32> x2y1, ap_int<32> x0y2, ap_int<32> x1y2, ap_int<32> x2y2);
ap_int<32> conv_rbg2y(ap_int<32> rbg);
ap_int<32> square_root10(ap_int<32> val);

int sobel_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins,
        hls::stream<ap_axis<32,1,1,1> >& outs, int function){
#pragma HLS INTERFACE s_axilite port=function
#pragma HLS INTERFACE axis register_mode=both register port=outs
#pragma HLS INTERFACE axis register_mode=both register port=ins
#pragma HLS INTERFACE s_axilite port=return

    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> sobel;
    ap_int<32> sobel_val, sobel_h_val, sobel_v_val;

    ap_int<32> line_buf[2][DISPLAY_WIDTH];
#pragma HLS array_partition variable=line_buf block factor=2 dim=1
#pragma HLS resource variable=line_buf core=RAM_2P

    ap_int<32> pix_mat[3][3];
#pragma HLS array_partition variable=pix_mat complete

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

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

            LOOP_PIX_MAT_K: for(int k=0; k<3; k++){
                LOOP_PIX_MAT_M: for(int m=0; m<2; m++){
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];
            ap_int<32> y_val = conv_rbg2y(pix.data);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            sobel_h_val = sobel_fil(HORIZONTAL, pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                                pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                                pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            sobel_v_val = sobel_fil(VERTICAL,   pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                                pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                                pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            sobel_val = square_root10(sobel_h_val*sobel_h_val + sobel_v_val*sobel_v_val);
            sobel.data = (sobel_val<<20)+(sobel_val<<10)+sobel_val;

            if(x<2 || y<2)
                sobel.data = 0;

            if(x==0 && y==0) // 最初のピクセル
                sobel.user = 1;
            else
                sobel.user = 0;
            if(x == (DISPLAY_WIDTH-1)) // 行の最後
                sobel.last = 1;
            else
                sobel.last = 0;

            if(function == SOBEL_FILTER)
                outs << sobel;
            else
                outs << pix;
        }
    }
    return(0);
}

// RBGからYへの変換
// RBGのフォーマットは、{2'd0, R(10bits), B(10bits), G(10bits)}, 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 にした
ap_int<32> conv_rbg2y(ap_int<32> rbg){
    ap_int<32> r, g, b, y_f;
    ap_int<32> y;

    b = (rbg>>10) & 0x3ff;
    g = rbg & 0x3ff;
    r = (rbg>>20) & 0x3ff;

    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で割る

    return(y);
}

// sobel filter
// HORZONTAL
// x0y0 x1y0 x2y0  1  2  1
// x0y1 x1y1 x2y1  0  0  0
// x0y2 x1y2 x2y2 -1 -2 -1
// VERTICAL
// x0y0 x1y0 x2y0  1  0 -1
// x0y1 x1y1 x2y1  2  0 -2
// x0y2 x1y2 x2y2  1  0 -1
ap_int<32> sobel_fil(ap_int<32> h_or_v, ap_int<32> x0y0, ap_int<32> x1y0, ap_int<32> x2y0, ap_int<32> x0y1,
        ap_int<32> x1y1, ap_int<32> x2y1, ap_int<32> x0y2, ap_int<32> x1y2, ap_int<32> x2y2){
    ap_int<32> y;

    if(h_or_v == HORIZONTAL){
        y = x0y0 + 2*x1y0 + x2y0 - x0y2 - 2*x1y2 - x2y2;
    } else {
        y = x0y0 - x2y0 + 2*x0y1 - 2*x2y1 + x0y2 - x2y2;
    }
    if(y<0)
        y = -y;
        //y = 0;
    else if(y>1023) // 10 bits
        y = 1023;
    return(y);
}

// square_root8
// 10 bit幅のsquare_rootを求める
ap_int<32> square_root10(ap_int<32> val){
    ap_int<32> temp = 0;
    ap_int<32> square;

    for(int i=9; i>=0; --i){
        temp += (1 << i);
        square = temp * temp;

        if(square > val){
            temp -= (1 << i);
        }
    }

    return(temp);
}


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

// sobel_filter_axis_RBG10_tb.cpp
// 2021/02/07 by marsee
//

#include <stdio.h>
#include <stdint.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgcodecs/imgcodecs.hpp"

#include "sobel_filter_axis_RBG10.h"

int sobel_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int function);
int sobel_filter_soft(int32_t *cam_fb, int32_t *sobel_fb,
        int32_t x_size, int32_t y_size);
int32_t square_root10_soft(int32_t val);

const char INPUT_BMP_FILE[] = "test2.bmp";
const char OUTPUT_BMP_FILE[] = "sobel.bmp";
const char ORG_OUT_BMP_FILE[] = "org.bmp";

int main(){
    hls::stream<ap_axis<32,1,1,1> > ins, ins2;
    hls::stream<ap_axis<32,1,1,1> > ins_soft;
    hls::stream<ap_axis<32,1,1,1> > outs, outs2;
    hls::stream<ap_axis<32,1,1,1> > outs_soft;
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> vals;

    // BMPファイルをMat に読み込む
    cv::Mat img = cv::imread(INPUT_BMP_FILE);

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

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

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

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

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

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

            ins << pix;
            ins2 << pix;
        }
    }

    sobel_filter_axis(ins, outs, SOBEL_FILTER); // ハードウェアのソーベルフィルタ
    sobel_filter_soft(rd_bmp.data(), sw_sobel.data(), img.cols, img.rows);  // ソフトウェアのソーベルフィルタ

    // ハードウェアとソフトウェアのソーベルフィルタの値のチェック
    for (int y=0; y<img.rows; y++){ // 結果の画像サイズはx-2, y-2
        for (int x=0; x<img.cols; x++){
            outs >> vals;
            ap_int<32> val = vals.data;
            hw_sobel[y*img.cols+x] = (int32_t)val;
            if (val != sw_sobel[y*img.cols+x]){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, HW = %d, SW = %d\n",
                        x, y, val, sw_sobel[y*(img.cols-2)+x]);
                return(1);
            }
        }
    }
    printf("Success HW and SW results match\n");

    const int sobel_row = img.rows;
    const int sobel_cols = img.cols;
    cv::Mat wbmpf(sobel_row, sobel_cols, CV_8UC3);
    // wbmpf にsobel フィルタ処理後の画像を入力
    cv::Mat_<cv::Vec3b> sob_vec3b = cv::Mat_<cv::Vec3b>(wbmpf);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = sob_vec3b(y,x);
            int32_t rbg = hw_sobel[y*wbmpf.cols+x];
            pixel[0] = ((rbg >> 12) & 0xff); // blue
            pixel[1] = ((rbg >> 2) & 0xff); // green
            pixel[2] = ((rbg >> 22) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // ハードウェアのソーベルフィルタの結果を bmp ファイルへ出力する
    cv::imwrite(OUTPUT_BMP_FILE, wbmpf);

    sobel_filter_axis(ins2, outs2, ORIGINAL_IMAGE); // 元画像出力

    cv::Mat wbmpf2(sobel_row, sobel_cols, CV_8UC3);
    // wbmpf2 に元画像を入力
    sob_vec3b = cv::Mat_<cv::Vec3b>(wbmpf2);
    for (int y=0; y<wbmpf.rows; y++){
        for (int x=0; x<wbmpf.cols; x++){
            cv::Vec3b pixel;
            pixel = sob_vec3b(y,x);
            outs2 >> vals;
            int32_t val = vals.data;
            pixel[0] = ((val >> 12) & 0xff); // blue
            pixel[1] = ((val >> 2) & 0xff); // green
            pixel[2] = ((val >> 22) & 0xff); // red
            sob_vec3b(y,x) = pixel;
        }
    }

    // 元画像を bmp ファイルへ出力する
    cv::imwrite(ORG_OUT_BMP_FILE, wbmpf2);

    return(0);
}

int32_t sobel_fil_soft(int32_t h_or_v, int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1,
        int32_t x1y1, int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2);
int32_t conv_rbg2y_soft(int32_t rbg);

int sobel_filter_soft(int32_t *cam_fb, int32_t *sobel_fb,
    int32_t x_size, int32_t y_size){
    int32_t sobel_val, sobel_h_val, sobel_v_val;
    int32_t pix[3][3];

    for(int y=0; y<y_size; y++){
        for(int x=0; x<x_size; x++){
            for(int i=2; i>=0; --i){
                for(int j=2; j>=0; --j){
                    if(x>=2 && y>=2)
                        pix[i][j] = conv_rbg2y_soft(cam_fb[(y-i)*x_size+(x-j)]);
                    else
                        pix[i][j] = 0;
                }
            }
            sobel_h_val = sobel_fil_soft(HORIZONTAL,pix[0][0], pix[0][1], pix[0][2],
                                                    pix[1][0], pix[1][1], pix[1][2],
                                                    pix[2][0], pix[2][1], pix[2][2]);
            sobel_v_val = sobel_fil_soft(VERTICAL,  pix[0][0], pix[0][1], pix[0][2],
                                                    pix[1][0], pix[1][1], pix[1][2],
                                                    pix[2][0], pix[2][1], pix[2][2]);
            sobel_val = square_root10_soft(sobel_h_val*sobel_h_val + sobel_v_val*sobel_v_val);
            sobel_fb[y*x_size+x] = (sobel_val<<20)+(sobel_val<<10)+sobel_val;
        }
    }
    return(0);
}

// RBGからYへの変換
// RBGのフォーマットは、{2'd0, R(10bits), B(10bits), G(10bits)}, 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 にした
int32_t conv_rbg2y_soft(int32_t rbg){
    int32_t r, g, b, y_f;
    int32_t y;

    b = (rbg>>10) & 0x3ff;
    g = rbg & 0x3ff;
    r = (rbg>>20) & 0x3ff;

    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で割る

    return(y);
}

// sobel filter
// HORZONTAL
// x0y0 x1y0 x2y0  1  2  1
// x0y1 x1y1 x2y1  0  0  0
// x0y2 x1y2 x2y2 -1 -2 -1
// VERTICAL
// x0y0 x1y0 x2y0  1  0 -1
// x0y1 x1y1 x2y1  2  0 -2
// x0y2 x1y2 x2y2  1  0 -1
int32_t sobel_fil_soft(int32_t h_or_v, int32_t x0y0, int32_t x1y0, int32_t x2y0, int32_t x0y1,
        int32_t x1y1, int32_t x2y1, int32_t x0y2, int32_t x1y2, int32_t x2y2){
    int32_t y;

    if(h_or_v == HORIZONTAL){
        y = x0y0 + 2*x1y0 + x2y0 - x0y2 - 2*x1y2 - x2y2;
    } else {
        y = x0y0 - x2y0 + 2*x0y1 - 2*x2y1 + x0y2 - x2y2;
    }
    if(y<0)
        y = -y;
        //y = 0;
    else if(y>1023)
        y = 1023;
    return(y);
}

// square_root10_soft
// 10 bit幅のsquare_rootを求める
int32_t square_root10_soft(int32_t val){
    int32_t temp = 0;
    int32_t square;

    for(int i=9; i>=0; --i){
        temp += (1 << i);
        square = temp * temp;

        if(square > val){
            temp -= (1 << i);
        }
    }

    return(temp);
}


Vitis HLS 2020.2 の sobel_filter_axis_RBG10 を示す。
sobel_filter_RBG10_1_210208.png

なお、今回はパソコンのUbuntu 18.04 LTS にインストール済みの OpenCV ライブラリを使用する。
そのため、Vitis HLS の Project メニューから Project Settings... を選択して、Project Settings ダイアログを開いた。
Simulation タブを開いて、sobel_filter_axis_RBG10_tb.cpp の CFLAGS に

-I/usr/local/include

を設定した。
Linker Flags に

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

を設定した。
  1. 2021年02月09日 04:49 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する6

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する5”の続き。

IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”のUltra96V2のPicam2 - DisplayPort 表示システムに lap_filter_axis IP を追加してビットストリームを生成し、XSA ファイルを生成した。今回は、Vitis でプラットフォームを XSA ファイルでアップデートし、アプリケーション・プロジェクトを再度ビルドしたが、やはり同様にラプラシアン・フィルタ処理結果は表示されなかった。
そこで、原因を追求するために Vivado Analyzer で波形を確認した。ラプラシアン・フィルタの出力AXI4-Stream 波形は最初は出力されているが、途中で止まってしまう。
次に、AXI VDMA のS2MM 側ではなく、MM2S 側に入れるとラプラシアン・フィルタ処理波形を表示することができたが、とっても薄くよく見えなかった。

現在は、AXI VDMA の S2MM 側 gumma_lut の後に lap_filter_axis を入れてある。
lap_filter_axis_RBG10_34_210205.png

Vitis 画面。
アプリケーション・ソフトウェアを走らせる。
lap_filter_axis_RBG10_39_210205.png

XLap_filter_axis_Set_function_r(&xlf_axis_ap, 1);

でラプラシアン・フィルタ処理モードにしてある。
lap_filter_axis の TLAST でトリガーしてある。この後、トリガーかけるとトリガーかからない。AXI4-Stream のトランザクションが消失している。
lap_filter_axis_RBG10_41_210206.png

v_gamma_lut の TLAST のはるか後ろで lap_filter_axis の TLAST があるのはなぜだろうか?

lap_filter_axis の TLAST を拡大する。
lap_filter_axis_RBG10_40_210206.png

v_gamma_lut の TVALID が出てから、 lap_filter_axis のデータ出力しているのか?
lap_filter_axis の TVALID が出力されてから、7 クロック後に TLAST が 1 になっている。

今度は、

XLap_filter_axis_Set_function_r(&xlf_axis_ap, 0);

で画像をスルー状態にしてみた。
同様に、lap_filter_axis の TLAST でトリガーしてある。この後、トリガーかけるとトリガーかかって、画像も正常に表示されている。
lap_filter_axis_RBG10_42_210206.png

やはり、v_gamma_lut の TLAST のはるか後ろで lap_filter_axis の TLAST が出るのは変わらない。

lap_filter_axis の TLAST を拡大する。
lap_filter_axis_RBG10_43_210206.png

TLAST はその前からずーと 1 になっているが、遥か後で、 TVALID が 1 になってから 0 に落ちている。つまり最後のデータだけ残っていたのか?
これもおかしい気がするけどな???

何処でおかしくなっているのかな?ということで、lap_filter_axis の TDATA が 0 じゃない時、つまり 2 行より後が出力されているのかな?ということで、lap_filter_axis の TDATA が 0 じゃなくて、 TVALID と TREADY が 1 の時をトリガーしてみるとトリガーかかった。
lap_filter_axis_RBG10_44_210206.png

拡大した。
lap_filter_axis_RBG10_45_210206.png

値は出ている。

Vitis HLS の C/RTL 協調シミュレーションでは、TLAST が出ているが、入力の TVALID は 1 になっている。入力の TVALID が 1 になっていると動かいないのか? RTL シミュレーションするしかないか?
lap_filter_axis_RBG10_46_210206.png

そうだ。カメラ側でなくビデオ側に入れてみよう。
AXI VDMA の MM2S と v_tpg の間に入れてみようということで入れてみた。
lap_filter_axis_RBG10_48_210207.png

それに、クロックを 100 MHz から 148.5 MHz に変更した。
lap_filter_axis_RBG10_52_210207.png

Address Editor
lap_filter_axis_RBG10_49_210207.png

Address Map
lap_filter_axis_RBG10_50_210207.png

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

Vitis 画面。
lap_filter_axis_RBG10_53_210207.png

XLap_filter_axis_Set_function_r(&xlf_axis_ap, 1);

でラプラシアン・フィルタ処理モードにしたところ、どうもエッジが表示されているようだが、エッジがあまり良く見えなかった。

XLap_filter_axis_Set_function_r(&xlf_axis_ap, 0);

で元画像も表示されている。

これでうまく行ったようだが、エッジが弱すぎるので、ソーベル・フィルタを実装してみようか?
  1. 2021年02月08日 03:35 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

AXI4-Stream インターフェース用 INTERFACE 指示子の register_mode オプション

Vivado HLS で使ってきた AXI4-Stream インターフェース用の指示子のオプションが Vitis HLS で変更になったので、覚書を書いておく。

Vivado HLS は AXI4-Stream インターフェースを指定する場合には、INTERFACE 指示子の axis オプションを付ける。その際にディレクティブ・エディタを使用するとデフォルトで register both オプションが追加された。このオプションは、すべての信号 (TDATA、TREADY、TVALID) にレジスタが追加された。(UG902 (v2019.2) 2020 年 1 月 13 日 高位合成 87 ページ”AXI4 インターフェイスの使用 AXI4-Stream インターフェイス”参照)

int lap_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int function){
#pragma HLS INTERFACE s_axilite port=function
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE s_axilite port=return


Vitis HLS でも同じ記述を使っていたのだが、ネイティブにディレクティブ・エディタを使っているとオプションが違っている。 register_mode both に変更になっていた。(”UG1399 (v2020.1) 2020 年 6 月 24 日 Vitis HLS ユーザーガイド”の 305 ページに記載あり)
Vitis_HLS_2020_2_41_210207.png

int lap_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int function){
#pragma HLS INTERFACE axis register_mode=both register port=outs
#pragma HLS INTERFACE axis register_mode=both register port=ins
#pragma HLS INTERFACE s_axilite port=function
#pragma HLS INTERFACE s_axilite port=return


UG1391 (v2020.1) 2020 年 7 月 28 日 Vitis HLS 移行ガイド”には、INTERFACE 指示子の axis オプションの register_mode オプションについては記載されていないようだ。
  1. 2021年02月07日 08:01 |
  2. Vitis HLS
  3. | トラックバック:0
  4. | コメント:0

MIPI CSI-2 Receiver Subsystem IP のお勉強

MIPI CSI-2 Receiver Subsystem IP について勉強したことを書いておこう。

MIPI CSI-2 Receiver Subsystem v5.1 Product Guide Vivado Design Suite PG232 (v5.1) January 8, 2021”に書いてあることをGoogle 翻訳で翻訳して図と一緒に引用してまとめていく。

Figure 1 - 1: Sybsystem Architecture を引用する。
MIPI_CSI-2_Receiver_1_210206.png

構成サブモジュールは以下の4つだそうだ。
・MIPI D-PHY
・MIPI CSI-2 RX Controller
・AXI Crossbar/Smart Connect
・Video Format Bridge

MIPI D-PHYIP
MIPI D-PHYIPコアはD-PHYRXインターフェイスを実装し、CSI-2RXインターフェイスと互換性のあるPHYプロトコルレイヤーサポートを提供する。MIPI D-PHY IPコアは、1500 Mb / sを超えるラインレートのデスキューパターン検出もサポートする。MIPI D-PHYの実装は、I / Oに関してUltraScale +™デバイスと7シリーズデバイスで異なる。

MIPI CSI-2 RX Controller
MIPI CSI-2 RXコントローラーコアは、レーン管理レイヤー、低レベルプロトコル、バイトからピクセルへの変換など、MIPI CSI-2 RX 1.1仕様(Versal ACAPの場合は2.0)で定義された複数のレイヤーで構成される。
MIPI CSI-2 RXコントローラーコアは、MIPI D-PHYコアからPPIを介して、最大4レーンをサポートするレーンごとに8ビットデータを受信する。 図1-1に示すように、PPIで受信したバイトデータは、低レベルのプロトコルモジュールによって処理され、実際の画像情報が抽出されます。 最終的に抽出された画像は、AXI4-Streamプロトコルを使用してユーザー/プロセッサインターフェイスで利用できるようになります。 レーン管理ブロック(lane management block)は、レーン数に関係なく、常にPPIから受信した32ビットデータで動作する。

Video Format Bridge
Video Format Bridgeコアは、ユーザーが選択したVCおよびデータタイプ情報を使用して、必要なAXI4-Streamデータビートのみをフィルタリングする。 このAXI4-Streamデータは、データタイプ情報に基づいてさらに処理され、出力はクロックあたりの要求されたピクセル数に基づいている。

video_outインターフェイスのデータポートの幅は、選択したデータタイプと選択したクロックあたりのピクセル数によって異る。RAW8とVivadoIDEで選択されたデータタイプの最大値にクロックあたりのピクセル数を掛けたデータ幅になる。 これはAXI4-Streamプロトコルに従って最も近いバイト境界に丸められる。
Single pixel width of RAW10 =10 だと 16ビットになっている。

Register Space
MIPI CSI-2 RX Controller のレジスタとMIPI D-PHY のレジスタがある。それをTable 2-6: Sub-Core Address Offsets に示す。
Table 2-6: Sub-Core Address Offsets を引用する。
MIPI_CSI-2_Receiver_2_210206.png

Table 2-7: MIPI CSI-2 RX Controller Core Registers を引用する。
MIPI_CSI-2_Receiver_3_210206.png
MIPI_CSI-2_Receiver_4_210206.png
MIPI_CSI-2_Receiver_5_210206.png
  1. 2021年02月06日 11:19 |
  2. IP
  3. | トラックバック:0
  4. | コメント:0

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する5

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する4”の続き。

IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”をやってきたが、 Vivado 2020.2 でカメラ画像をディスプレイに表示することができた。そこで、そのカメラ画像表示システムにラプラシアン・フィルタを追加してみようということで実装を続けていたが、Vitis HLS プロジェクトで画像の大きさが 64 X 48 ピクセルで 1920 X 1080 ピクセルに変更していないことを気がついたので、Vitis HLS からやり直すことにした。
しかし、すでにVivado のブロックデザインを lap_filter_axis IP だけにして、 axis_switch IP を消してしまったので、 lap_filter_axis IP に元画像とラプラシアン・フィルタ処理の切り替え機能を付けることになった。
前回は、新しいソースコードとテストベンチなどのファイルを貼った。今回は、Vitis HLS での C シミュレーション結果、C コードの合成、Export RTL を行って、Vivado の lap_filter_axis IP を更新し、論理合成、インプリメンテーション、ビットストリームの生成を行って、XSA ファイルをエクスポートしよう。

まずは、Vitis HLS 2020.2 の lap_filter_axis_RBG10 プロジェクトで C シミュレーションを行った。
lap_filter_axis_RBG10_28_210205.png

lap_filter_axis_RBG10/solution1/csim/build ディレクトリを示す。
temp_lap.bmp にラプラシアン・フィルタ画像がセーブされ、temp_org.bmp に元画像がセーブされているのが分かる。
lap_filter_axis_RBG10_29_210205.png

C コードの合成を行った。結果を示す。
1920 x 1080 = 2073600 ピクセルで、レイテンシが 2073611 クロックなので優秀だ。
lap_filter_axis_RBG10_30_210205.png
lap_filter_axis_RBG10_31_210205.png

Export RTL の結果を示す。
CP achieved post-implementation が 5.502 ns なので、全く問題無さそうだ。
lap_filter_axis_RBG10_32_210205.png

lap_filter_axis_RBG10/solution1/impl/export.zip を展開して、Viavdo 2020.2 の ultra96v2_picam2_dp_202_3 プロジェクトの lap_filter_axis ディレクトリの内容と入れ替えた。
lap_filter_axis_RBG10_33_210205.png

現在の ultra96v2_picam2_dp_202_3 プロジェクトのブロックデザインを示す。
axis_switch を削除した。
lap_filter_axis_RBG10_34_210205.png

Address Editor 画面を示す。
lap_filter_axis_RBG10_35_210205.png

Address Map 画面を示す。
lap_filter_axis_RBG10_36_210205.png

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

Project Summary を示す。
lap_filter_axis_RBG10_38_210205.png

ハードウェアをエクスポートして、 XSA ファイルを作成した。
  1. 2021年02月05日 04:36 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する4

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する3”の続き。

IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”をやってきたが、 Vivado 2020.2 でカメラ画像をディスプレイに表示することができた。そこで、そのカメラ画像表示システムにラプラシアン・フィルタを追加してみようということで、前回は、生成されたラプラシアン・フィルタ IP をブロックデザインで使用して、ラプラシアン・フィルタ付きのカメラ画像システムを構築し、XSA ファイルを生成した。しかし、Vitis HLS プロジェクトで画像の大きさが 64 X 48 ピクセルで 1920 X 1080 ピクセルに変更していないことを気がついたので、Vitis HLS からやり直す。

前回で、 XSA ファイルが生成できたので、 Vitis のプラットフォームの更新とアプリケーション・プロジェクトのクリーン、再ビルドを行ってやってみたが、ラプラシアン・フィルタ処理を入れると画像が止まっていた。ラプラシアン・フィルタ IP を入れないと正常にカメラ画像が映る。
Vivado のブロックデザインで、lap_filter_axis IP だけにしてみたところ、ランダムが画像が表示された。

もしかして。。。ということで、ラプラシアン・フィルタを 64 X 48 ピクセルのままにしていたことが判明した。 800 X 600 ピクセルに変更する必要があったはず。。。
しまったとということで、 Vitis HLS 2020.2 からやり直すことにした。
その際に、Vivado のブロックデザインを lap_filter_axis IP だけにして、 axis_switch IP を消してしまったので、 lap_filter_axis IP に元画像とラプラシアン・フィルタ処理の切り替え機能を付けることにした。

lap_filter_axis_RBG10.h を示す。

// lap_filter_axis_RBG10.h
// 2021/01/31 by marsee
//

#ifndef __LAP_FILTER_AXIS_RBG10_H__
#define __LAP_FILTER_AXIS_RBG10_H__

#define HORIZONTAL_PIXEL_WIDTH 1920
#define VERTICAL_PIXEL_WIDTH 1080

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

//#define HORIZONTAL_PIXEL_WIDTH 64
//#define VERTICAL_PIXEL_WIDTH 48

#define ALL_PIXEL_VALUE (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#define ORIGINAL_IMAGE 0
#define LAPLACIAN_FILTER 1

#endif


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

// lap_filter_axis_RBG10.cpp
// 2021/01/31 by marsee
//

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

#include "lap_filter_axis_RBG10.h"

int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rbg2y10(int rgb);

int lap_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int function){
#pragma HLS INTERFACE s_axilite port=function
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE s_axilite port=return

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

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

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

    int lap_fil_val;

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

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

            Loop4 : for (int k=0; k<3; k++){
                Loop5 : for (int m=0; m<2; m++){
#pragma HLS UNROLL
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int y_val = conv_rbg2y10(pix.data);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap.data = (lap_fil_val<<20)+(lap_fil_val<<10)+lap_fil_val; // RGB同じ値を入れる

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

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

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

            if(function == LAPLACIAN_FILTER)
                outs << lap;    // ラプラシアン・フィルタ処理画像をAXI4-Stream へ出力
            else
                outs << pix;    // 元画像をAXI4-Stream へ出力
        }
    }

    return 0;
}

// RBG 10 ビット幅からYへの変換
// RBGのフォーマットは、{2'd0, R(10 bits), B(10 bits), G(10 bits)}, 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 にした
int conv_rbg2y10(int rgb){
    int r, g, b, y_f;
    int y;

    g = rgb & 0x3ff;
    b = (rgb>>10) & 0x3ff;
    r = (rgb>>20) & 0x3ff;

    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で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = -y;
        //y = 0;
    else if (y>1023)
        y = 1023;
    return(y);
}


テストベンチの lap_filter_axis_RBG10_tb.cpp を示す。
サブルーチンを使わないで長くなってしまった。

// lap_filter_axis_RBG10_tb.cpp
// 2021/01/31 by marsee
//

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

#include "lap_filter_axis_RBG10.h"
#include "bmp_header.h"

int lap_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int function);

int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rbg2y10_soft(int rgb);
int lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int width, int height);

#define CLOCK_PERIOD 10

int main()
{
    using namespace std;

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

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

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

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

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

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

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

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

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

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

    lap_filter_axis(ins, outs, LAPLACIAN_FILTER);
    lap_filter_axis_soft(ins_soft, outs_soft, bmpihr.biWidth, bmpihr.biHeight);

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

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

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

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

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

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);

    // 元画像を出力
    lap_filter_axis(ins2, outs, ORIGINAL_IMAGE);
    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            outs >> vals;
            ap_int<32> val = vals.data;

            hw_lapd[(j*bmpihr.biWidth)+i] = (int)val;
        }
    }
    // ハードウェアのラプラシアンフィルタの結果を temp_lap.bmp へ出力する
    if ((fbmpw=fopen("temp_org.bmp", "wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.bmp by binary write mode\n");
        exit(1);
    }
    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

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

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

    return 0;
}

int lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int width, int height){
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;
    unsigned int **line_buf;
    int pix_mat[3][3];
    int lap_fil_val;
    int i;

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

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

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

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

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

            int y_val = conv_rbg2y10_soft(pix.data);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil_soft(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap.data = (lap_fil_val<<20)+(lap_fil_val<<10)+lap_fil_val; // RGB同じ値を入れる

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

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

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

            outs << lap;    // AXI4-Stream へ出力
        }
    }

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

    return 0;
}

// RBG 10 ビット幅からYへの変換
// RBGのフォーマットは、{2'd0, R(10 bits), B(10 bits), G(10 bits)}, 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 にした
int conv_rbg2y10_soft(int rgb){
    int r, g, b, y_f;
    int y;

    g = rgb & 0x3ff;
    b = (rgb>>10) & 0x3ff;
    r = (rgb>>20) & 0x3ff;

    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で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = -y;
        //y = 0;
    else if (y>1023)
        y = 1023;
    return(y);
}


test2.bmp は 800 X 600 ピクセルで、test3.bmp は 1920 X 1080 ピクセルだ。
今回は、 test3.bmp を使用する。
lap_filter_axis_RBG10_27_210204.png

新しくなった Vitis HLS 2020.2 の lap_filter_axis_RBG10 プロジェクトを示す。
lap_filter_axis_RBG10_25_210204.png
  1. 2021年02月04日 05:21 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する3

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する2”の続き。

IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”をやってきたが、 Vivado 2020.2 でカメラ画像をディスプレイに表示することができた。そこで、そのカメラ画像表示システムにラプラシアン・フィルタを追加してみようということで、前回は、Vitis HLS 2020.2 プロジェクトで、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行った。今回は、生成されたラプラシアン・フィルタ IP をブロックデザインで使用して、ラプラシアン・フィルタ付きのカメラ画像システムを構築していく。

ultra96v2_picam2_dp_202_3 ディレクトリのカメラ画像システムにラプラシアン・フィルタ IP を追加する。

前回作成した Vitis HLS 2020.2 の lap_filter_axis_RGB10/solution/impl ディレクトリの export.zip に IP が凍結されている。
lap_filter_axis_RBG10_12_210202.png

ultra96v2_picam2_dp_202_3 ディレクトリに lap_filter_axis ディレクトリを作成し、その中に export.zip を解凍した中身をコピーする。
lap_filter_axis_RBG10_13_210202.png

IP Catalog に lap_filter_axis IP を登録する。(右クリックし右クリックメニューから Add Repository... を選択する)
lap_filter_axis_RBG10_14_210202.png

ブロックデザインで、 v_gamma_lut_0 の後に、 axis_switch_0 を挿入して、その後に lap_filter_axis 、 axs_swtich_1 を挿入した。
なお、 axis_switch で元画像とラプラシアン・フィルタ処理画像をソフトウェアで切り替えられるようにしてある。
lap_filter_axis_RBG10_15_210202.png

Address Editor 画面を示す。
lap_filter_axis_RBG10_16_210202.png

Address Map 画面を示す。
lap_filter_axis_RBG10_17_210202.png

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

Export Hardware を行って、 design_1_wrapper.xsa ファイルを生成できた。
lap_filter_axis_RBG10_19_210202.png
  1. 2021年02月03日 05:14 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する2

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する1”の続き。

IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”をやってきたが、 Vivado 2020.2 でカメラ画像をディスプレイに表示することができた。そこで、そのカメラ画像表示システムにラプラシアン・フィルタを追加してみようということで、前回は、RBG 10 ビット幅用のラプラシアン・フィルタ IP のコードを紹介して、Vitis HLS 2020.2 プロジェクトを作成した。今回は、その Vitis HLS 2020.2 プロジェクトで、C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行う。

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

lap_filter_axis_RBG10/solution1/csim/build ディレクトリにラプラシアン・フィルタ処理された temp_lap.bmp が作成されていた。
lap_filter_axis_RBG10_6_210201.png

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

問題無さそうだ。

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

64 ピクセル X 48 行 = 3072 ピクセルで 3088 クロックなので、優秀だ。

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

TVALID と TREADY がほとんど 1 のままでスループットが高い。

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

問題無さそうだ。 IP として使用できそうだ。
  1. 2021年02月02日 04:01 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0

”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”にラプラシアン・フィルタを追加する1

今まで、”IMX219 MIPI sensor to Ultra96-V2 FPGA DisplayPort”をやってきたが、 Vivado 2020.2 でカメラ画像をディスプレイに表示することができたので、ラプラシアン・フィルタを追加してみようと思う。

まずは、 Vitis HLS 2020.2 を使用して、ラプラシアン・フィルタを作成する。今までもラプラシアン・フィルタを Vivado HLS や Vitis HLS で作成してきたが、今回は、”Gamma LUT v1.0 LogiCORE IP Product Guide PG285 December 6, 2019”の 11 ページの”Figure 2-2:Dual Pixels per Clock, 10 bits per Component Mapping for RGB”を見ると、 RBG のフォーマットのなので、それに従って作成しよう。

ラプラシアン・フィルタは Gamma LUT の後に AXI4-Stream 入出力として入れるの予定なので、まずは、 v_gamma_lut_0 の設定を確認する。
v_gamma_lut_0 をダブルクリックして設定ダイアログを開いた。
lap_filter_axis_RBG10_1_210201.png

次に、”Gamma LUT v1.0 LogiCORE IP Product Guide PG285 December 6, 2019”の 11 ページの”Figure 2-2:Dual Pixels per Clock, 10 bits per Component Mapping for RGB”と”Figure 2-3:Dual Pixels per Clock, 10 bits per Component Mapping for YUV 4:4:4”を引用する。
lap_filter_axis_RBG10_2_210201.png

2 つの図から画像フォーマットは RBG 10 ビット幅ずつになっているようだ。

そこで、ラプラシアン・フィルタのソースコードを修正した。
ソースコード・ファイルの lap_filter_axis_RBG10.cpp を示す。

// lap_filter_axis_RBG10.cpp
// 2021/01/31 by marsee
//

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

#include "lap_filter_axis_RBG10.h"

int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rbg2y10(int rgb);

int lap_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs){
#pragma HLS INTERFACE axis register both port=ins
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE s_axilite port=return

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

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

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

    int lap_fil_val;

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

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

            Loop4 : for (int k=0; k<3; k++){
                Loop5 : for (int m=0; m<2; m++){
#pragma HLS UNROLL
                    pix_mat[k][m] = pix_mat[k][m+1];
                }
            }
            pix_mat[0][2] = line_buf[0][x];
            pix_mat[1][2] = line_buf[1][x];

            int y_val = conv_rbg2y10(pix.data);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap.data = (lap_fil_val<<20)+(lap_fil_val<<10)+lap_fil_val; // RGB同じ値を入れる

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

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

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

            outs << lap;    // AXI4-Stream へ出力
        }
    }

    return 0;
}

// RBG 10 ビット幅からYへの変換
// RBGのフォーマットは、{2'd0, R(10 bits), B(10 bits), G(10 bits)}, 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 にした
int conv_rbg2y10(int rgb){
    int r, g, b, y_f;
    int y;

    g = rgb & 0x3ff;
    b = (rgb>>10) & 0x3ff;
    r = (rgb>>20) & 0x3ff;

    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で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = -y;
        //y = 0;
    else if (y>1023)
        y = 1023;
    return(y);
}


lap_filter_axis_RBG10.h を示す。

// lap_filter_axis_RBG10.h
// 2021/01/31 by marsee
//

#ifndef __LAP_FILTER_AXIS_RBG10_H__
#define __LAP_FILTER_AXIS_RBG10_H__

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

#define HORIZONTAL_PIXEL_WIDTH    64
#define VERTICAL_PIXEL_WIDTH    48

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#endif


テストベンチ・ファイルの lap_filter_axis_RBG10_tb.cpp を示す。

// lap_filter_axis_RBG10_tb.cpp
// 2021/01/31 by marsee
//

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

#include "lap_filter_axis_RBG10.h"
#include "bmp_header.h"

int lap_filter_axis(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs);

int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2);
int conv_rbg2y10_soft(int rgb);
int lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int width, int height);

#define CLOCK_PERIOD 10

int main()
{
    using namespace std;

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

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

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

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

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

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

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

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

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

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

    lap_filter_axis(ins, outs);
    lap_filter_axis_soft(ins_soft, outs_soft, bmpihr.biWidth, bmpihr.biHeight);

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

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

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

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

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

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

    return 0;
}

int lap_filter_axis_soft(hls::stream<ap_axis<32,1,1,1> >& ins, hls::stream<ap_axis<32,1,1,1> >& outs, int width, int height){
    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> lap;
    unsigned int **line_buf;
    int pix_mat[3][3];
    int lap_fil_val;
    int i;

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

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

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

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

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

            int y_val = conv_rbg2y10_soft(pix.data);
            pix_mat[2][2] = y_val;

            line_buf[0][x] = line_buf[1][x];    // 行の入れ替え
            line_buf[1][x] = y_val;

            lap_fil_val = laplacian_fil_soft(    pix_mat[0][0], pix_mat[0][1], pix_mat[0][2],
                                        pix_mat[1][0], pix_mat[1][1], pix_mat[1][2],
                                        pix_mat[2][0], pix_mat[2][1], pix_mat[2][2]);
            lap.data = (lap_fil_val<<20)+(lap_fil_val<<10)+lap_fil_val; // RGB同じ値を入れる

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

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

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

            outs << lap;    // AXI4-Stream へ出力
        }
    }

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

    return 0;
}

// RBG 10 ビット幅からYへの変換
// RBGのフォーマットは、{2'd0, R(10 bits), B(10 bits), G(10 bits)}, 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 にした
int conv_rbg2y10_soft(int rgb){
    int r, g, b, y_f;
    int y;

    g = rgb & 0x3ff;
    b = (rgb>>10) & 0x3ff;
    r = (rgb>>20) & 0x3ff;

    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で割る

    return(y);
}

// ラプラシアンフィルタ
// x0y0 x1y0 x2y0 -1 -1 -1
// x0y1 x1y1 x2y1 -1  8 -1
// x0y2 x1y2 x2y2 -1 -1 -1
int laplacian_fil_soft(int x0y0, int x1y0, int x2y0, int x0y1, int x1y1, int x2y1, int x0y2, int x1y2, int x2y2)
{
    int y;

    y = -x0y0 -x1y0 -x2y0 -x0y1 +8*x1y1 -x2y1 -x0y2 -x1y2 -x2y2;
    if (y<0)
        y = -y;
        //y = 0;
    else if (y>1023)
        y = 1023;
    return(y);
}


元画像の test.bmp だが、 Ubuntu 18.04 LTS の Pinta で作成した。ブラシの幅を 5 ピクセルに設定してある。サイズは 64 ピクセル X 48 行だ。
lap_filter_axis_RBG10_3_210201.png

Vitis HLS 2020.2 で lap_filter_axis_RBG10 プロジェクトを作成した。
lap_filter_axis_RBG10_4_210201.png
  1. 2021年02月01日 04:32 |
  2. Ultra96
  3. | トラックバック:0
  4. | コメント:0