トップ 差分 一覧 Farm ソース 検索 ヘルプ PDF 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.


iop_pmod0の中身はこんな感じ.中にMicroBlazeがいる.

    

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


オリジナル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の先にぶらさげる.


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