ACRiブログ PYNQ を使って Python で手軽に FPGA を活用 (4) に寄稿した文章では、PYNQ-Z1を使ってPYNQの使い方を紹介しました。その記事に対して、Ultra96だとどうなの?というコメントがフォーラムに寄せられていたので、簡単にフォローです。
準備
Pynqの準備
まずは、AvnetのGitHubリポジトリのリリースページからv2.5 PYNQ for Ultra96 を ダウンロードします。1.7GBなので、それなりに時間がかかるかもしれません。ダウンロードできたら展開してMicroSDカードに書き込みます。
$ unzip ultra96v1_v2.5.zip
$ sudo dd if=ultra96v1_v2.5.img of=/dev/sda
Vivadoの準備
PLに自分のロジックを実装して利用するためには Vivado のインストールが必要です。 Vivado 2019.1 をインストールします。また、Ultra96を簡単に利用するために Avnet が提供しているボード定義ファイルもインストールしておきましょう。https://codeload.github.com/Avnet/bdf/zip/master にアクセスして、 bdf-master.zip をダウンロードします。ダウンロードしたら展開し、bdf-master以下のディレクトリを、/tools/Xilinx/Vivado/2019.1/data/boards/board_files 以下にコピーします(パスは自分がVivadoをインストールしたときにあわせてください)。
電源投入とJupyter Notebookへのアクセス
USBケーブルと電源を接続してパワーオンします。Ultra96とはUSBポート接続越しでTCP/IP通信ができます.電源を投入すると
- IPアドレスは Ultra96 から振られる
- ホストPCには192.168.3.100が振られた
という動作をします。Ultra96は 192.168.3.1 というIPアドレスなので、ブラウザから 192.168.3.1 へアクセスします。アクセスしたら、
- http://192.168.3.1:9090/notebooks/sensors96b/sensors_mezzanine_examples.ipynb をやってみる(Groveモジュールもってなくても,sensors96b.bitによるオーバレイの動作確認として)
- http://192.168.3.1:9090/notebooks/common/overlay_download.ipynb でビットストリームのロード時間が測定できる
として、Jupyter Notebook経由でFPGAを操作できていることが確認できます。
適当なデザインを作ってみる
PLにオリジナルの回路をいれてみましょう。次のような手順です。
- Vivado 2019.1 でプロジェクトを作る。たとえば、ultra96_pynq_testという名前でプロジェクト作りましょう。プロジェクト設定では、ターゲットボードとして Ultra96v1を選択します(ターゲットボードに Ultra96v1がでてこない場合にはボード定義ファイルを正しくセットアップできてません)。
プロジェクト作成のサマリはこんな感じです(画像をクリックで拡大)。
Vivado IPIでブロックデザインを作る
- Zynq UltraScale+ MPSoCとGPIOのインスタンスを生成(画像をクリックで拡大)
- GPIOのインスタンス axi_gpio_0 を Enable Dual Channel にチェックをいれる
- GPIOを All Outputsに GPIO2 を All Inptusにセット
- Run Block Automationをクリック
- Run Connection Automation クリック。All Automation に チェックをいれて実行
- Create Portで pl_clk という名前のクロック出力ポートを生成し、pl_clk_0 に接続
- maxihpm1_fpd_aclk を pl_clk_0 に接続
最終的にこんな感じのブロックデザインが出来上がります(画像クリックで拡大)。M_AXI_HPM1_FPDがオープンなので、Run Connection Automationが表示されますが無視します。
- ブロックデザインを保存して Create HDL Wrapper… で,生成したブロックダイアグラムのHDLラッパーを作成
トップモジュールを用意して合成
- トップモジュール(top.v)を作って合成.(GPIOの0bit目を0->1にすると内部レジスタに100を加算するロジック)
//
`default_nettype none
module top();
wire [31:0]gpio_rtl_0_tri_i;
wire [31:0]gpio_rtl_o;
wire pl_clk;
design_1_wrapper design_1_wrapper_i(
.gpio_rtl_0_tri_i(gpio_rtl_0_tri_i), // PL -> PS
.gpio_rtl_tri_o(gpio_rtl_o), // PS -> PL
.pl_clk(pl_clk)
);
reg [31:0] data = 32'h0;
reg [31:0] gpio_rtl_o_reg = 32'h0;
always @(posedge pl_clk) begin
gpio_rtl_o_reg <= gpio_rtl_o;
if(gpio_rtl_o_reg[0] == 0 && gpio_rtl_o[0] == 1) begin
data <= data + 100;
end
end
assign gpio_rtl_0_tri_i = data;
endmodule
`default_nettype wire
//
合成がおわると,
- ./ultra96_pynq_test.srcs/sources_1/bd/design_1/hw_handoff/design_1.hwh
- ./ultra96_pynq_test.runs/impl_1/top.bit
ができあがります。
PYNQで動作確認
次の手順で実環境上で動作確認ができます。
- top.bitとdesign_1.hwhをJupyter Notebookにアップロード
- top.bitは,design_1.bitに名前を変更
- Python3スクリプトを新規作成.
- ビットストリームをFPGAにセット(以下のようなコード片をJupyter Notebookに入力して実行)
from pynq import Overlay
base = Overlay("./design_1.bit")
- GPIOのインスタンス axi_gpio_0 へのハンドラを取得(以下のようなコード片をJupyter Notebookに入力して実行)
from pynq import MMIO
mmio = MMIO(base_addr = base.ip_dict['axi_gpio_0']['phys_addr'], length = 0x1000, debug = True)
- axi_gpio_0 のポート0のビット0をパタパタしてaxi_gpio_0 のポート1から値を読み出(以下のようなコード片をJupyter Notebookに入力して実行)
mmio.write(0,0); mmio.write(0,1); mmio.write(0,0) # ビット0をパタパタする
mmio.read(8) # ポート1の値(PL->PS)を読み出す
mmio.read(8)で、PL内部で100ずつ加算された結果を読み出すことができ、正しくロジックが動作していること、Jupyter Notebookから制御できていることがわかります。