!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 #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を足した値が格納されていて,正しく処理が実行できたことがわかる.