トップ 一覧 Farm 検索 ヘルプ RSS ログイン

Diary/2022-12-4の変更点

  • 追加された行はこのように表示されます。
  • 削除された行はこのように表示されます。
!PYNQ/KV260のビルド
GitHubで公開されてているKria-PYNQのbaseをビルドしてみる.
ライセンスは https://github.com/Xilinx/Kria-PYNQ/blob/main/LICENSE によると
BSD 3-Clause License なので,オリジナル作るときはこれをベースにすすめればよさそう.

 git clone https://github.com/Xilinx/Kria-PYNQ.git
 cd Kria-PYNQ
 git checkout v3.0
 git submodule update --init --recursive
 source /tools/Xilinx/Vitis/2020.2/settings64.sh
 kv260/base
 make

Vivado/Vitis HLSの日付のバグを踏むので
https://support.xilinx.com/s/article/76960
をちゃんと当てとかないとダメ

また,2020.2.2のアップデートをインストールしとくのと
KV260のボードファイルを用意しておく必要があるのは注意.
ボードファイルは,
https://github.com/Xilinx/XilinxBoardStore/tree/2020.2.2/boards/Xilinx/kv260
を /tools/Xilinx/Vivado/2020.2/data/boards/board_files にコピーしとく.

ビルドがおわると,PYNQでPLをオーバーレイするのに必要な
* base.bit
* base.hwh
* base.dtbo
が生成される.


base/base.xprを開いてブロックデザインをみてみると↓のような感じ.
ロゴが入っているのがZYNQ UltraSCALE+.
左側の濃いブロックがiop_pmod0でPMODにつながっていて,
右側の濃いブロックはmipi.
{{ref_image kv260_pynq_base_s.png}}

{{ref_image kv260_pynq_base_iop_s.png}}
iop_pmod0の中身はこんな感じ.中にMicroBlazeがいる.
	    
mipiの中身はこんな感じ.Vitis HLSで作ったモジュール達が入っている.
入力はmipi_phy_ifで外から入っていて,
計算した結果はAXI Video Direct Memory Accessを介して
Zynq UltraSCALE+のS_AXI_HPI_FPDに入力されている.
{{ref_image kv260_pynq_base_mipi_s.png}}

!オリジナルIPの組み込み
Kria-PYNQ/baseにオリジナルのIPを組み込んでみる.

 mkdir myipcore
 cd myipcore
 source /tools/Xilinx/Vitis/2020.2/settings64.sh
 vitis_hls

とかして,Vitis HLS開いて,プロジェクトを作る.

プロジェクトを作ったら,たとえば,こんな感じの関数を vector_add.c に実装して,

 #include "vector_add.h"
 
 void vector_add(int a[128], int b[128], int c[128]){
 #pragma HLS INTERFACE s_axilite port=return
 #pragma HLS INTERFACE s_axilite port=a
 #pragma HLS INTERFACE s_axilite port=b
 #pragma HLS INTERFACE s_axilite port=c
   for(int i = 0; i < 128; i++){
     c[i] = a[i] + b[i];
   }
 }

こんな感じのテストベンチを vector_add_tb.c に実装.

 #include <stdio.h>
 #include "vector_add.h"
 
 int main(int argc, char **argv){
   int a[128];
   int b[128];
   int c[128];
   int c_sw[128];
 
   int i;
 
   for(i = 0; i < 128; i++){
     a[i] = i;
     b[i] = i;
     c[i] = 0;
     c_sw[i] = a[i] + b[i];
   }
 
   vector_add(a, b, c);
 
   for(i = 0; i < 128; i++){
     if(c_sw[i] != c[i]){
       printf("%d: expected %d, but the actual %d\n", i, c_sw[i], c[i]);
       return 1;
     }
   }
   return 0;
 }

C Simulation -> C Synthesis -> Co-Simulation -> Export RTLする.
仮引数のa, b, cは,それぞれs_axi_controlのオフセット512, 1024, 1536にマッピングされた.

関数の制御/ステータス信号および仮引数はすべて一つのAXI4-LiteなSlaveにまとめられる.
メモリマップは
 //------------------------Address Info-------------------
 // 0x000 : Control signals
 //         bit 0  - ap_start (Read/Write/COH)
 //         bit 1  - ap_done (Read/COR)
 //         bit 2  - ap_idle (Read)
 //         bit 3  - ap_ready (Read)
 //         bit 7  - auto_restart (Read/Write)
 //         others - reserved
 // 0x004 : Global Interrupt Enable Register
 //         bit 0  - Global Interrupt Enable (Read/Write)
 //         others - reserved
 // 0x008 : IP Interrupt Enable Register (Read/Write)
 //         bit 0  - enable ap_done interrupt (Read/Write)
 //         bit 1  - enable ap_ready interrupt (Read/Write)
 //         others - reserved
 // 0x00c : IP Interrupt Status Register (Read/TOW)
 //         bit 0  - ap_done (COR/TOW)
 //         bit 1  - ap_ready (COR/TOW)
 //         others - reserved
 // 0x200 ~
 // 0x3ff : Memory 'a' (128 * 32b)
 //         Word n : bit [31:0] - a[n]
 // 0x400 ~
 // 0x5ff : Memory 'b' (128 * 32b)
 //         Word n : bit [31:0] - b[n]
 // 0x600 ~
 // 0x7ff : Memory 'c' (128 * 32b)
 //         Word n : bit [31:0] - c[n]
 // (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)
という感じ.

生成したIPコアをIPIに組み込むために,
IPカタログのリポジトリにmyipcoreディレクトリを追加してコアのインスタンスを生成して,
M_AXI_HPM0_LPDから制御されるps8_0_axi_periphなるAXI Interconnectの先にぶらさげる.

{{ref_image kv260_pynq_mycore_s.png}}

で,Generate Bitstreamで,しばらく待つと,
* kv260/base/base/base.runs/impl_1/base_wrapper.bit
* kv260/base/base/base.gen/sources_1/bd/base/hw_handoff/base.hwh
ができる.

両方ともKriaに転送して,
たとえば,/home/root/jupyter_notebooksの下にmyipcoreとか作って置いておく.

あとは,Juypter環境で,同じフォルダにPython3ノートを作って,

 from pynq import Overlay
 base = Overlay("./myipcore.bit")
 from pynq import MMIO
 mmio = MMIO(base_addr=base.ip_dict['vector_add_0']['phys_addr'], length=0x1000, debug=True)

として追加したvector_add_0へのハンドラ(メモリ領域へのアクセサ)を取得する.
 mmio.read(0)
とかすると,ap_idleに相当する4が返ってくる.
 for i in range(128):
     mmio.write(512+4*i, i)
     mmio.write(1024+4*i, i)
     mmio.write(1536+4*i, 0)
として,仮引数のa, b, cに相当する領域を初期化した後,
 mmio.write(0, 1)
で,処理の実行開始.処理はすぐに終わるはずなので,
 mmio.read(0)
とすると,今度はap_readyとap_idleが立った状態の6が返ってくる.
 for i in range(128):
     print(mmio.read(1536+4*i))
で,cの先のメモリを読むと,
 0
 2
 4
 6
 8
 10
 12
 14
 16
 ...
と,aとbを足した値が格納されていて,正しく処理が実行できたことがわかる.