- 追加された行はこのように表示されます。
- 削除された行は
このように表示されます。
!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を足した値が格納されていて,正しく処理が実行できたことがわかる.