**********************************************************************
セッションS2-a チュートリアル
テーマ:【SWEST/ACRi 共同企画セッション】PYNQでお手軽に始めるFPGAシステム開発
講師:藤枝 直輝 氏(愛知工業大学)
日時:2021/9/3 10:00~11:10
参加人数:15名(終了時)
**********************************************************************
・ACRiの紹介
本セッションはSWESTとACRiの共同セッション。
ACRiはAdaptive Computing Research Initiativeという
アダプティブなハードウェアデバイスの活用方法を模索・研究する団体である。
FPGAをみんなで使っていこう、盛り上げていこうとしている。
5大学と17社、エントリー企業として何社か参加して頂いている。
・本セッションの意図
FPGAの特にPYNQというボードシステムを使って
FPGAのシステムを気軽に開発しようという話をする。
・アウトライン
◆PYNQとは何か?
◆PYNQ-Z1ボードのセットアップ
◆LED点滅回路を含む設計例
◆グラフィックパターン送信回路を含む設計例
◆PYNQとは何か?
・FPGAの説明
PYNQというシステムはFPGAとプロセッサがワンチップに搭載されたSoC(System-on-a-chip)のことである。
Xilinx社のZynqシリーズやIntel社のSoC FPGAなどが売られている。
FPGAの開発にはハードウェア記述言語(HDL)がよく使われる。
最近だとC/C++を使って、高位合成という技術を使って開発することもできる。
・FPGAの開発を試すと起こること
FPGAを試してみようとするとFPGAの評価ボードを買い、FPGAの開発環境をマシンに入れて、LEDをチカチカさせてみてと、
その先が長すぎて挫折するということがありがちなパターンと思う。
手を動かすところまではチュートリアルがあるので、簡単にできる。その先どうやっていいのかがわかりにくい。
・その先やらないといけないこと
作った回路をプロセッサと一緒に動く周辺回路にパッケージする必要がある。
プロセッサと周辺回路が共同で動くシステムを合成する。
色々調べないといけないことが多い。
試さないといけないことも多い。
この2つの理由から検討を終わりにしてしまう事がありがちである。
・やらなければならないことが多いという欠点に対する対策
この問題意識はFPGAのベンダーもあるようです。
この問題を解決するために、ここ数年で出てきたプラットフォームがPYNQになる。
PYはPythonのこと。NQはXilinx社のFPGA搭載のZynqシリーズのことである。
・PYNQの概要
掲げているものはPythonの生産性をZynqに取り入れようという事。
Zynqを作っているXilinx社のオープンソースプロジェクトである。
ハードウェアやソフトウェアのライブラリ、Pythonの開発環境、それぞれのボード向けに用意されたディスクイメージ等が用意されている。
それらを書き込んでBootしてあげるとLinuxが起動して最低限試すことが出来る。
・PYNQの狙い
組込みのアーキテクト・エンジニア・デザイナがASICスタイルの設計ツールを使うことなく、Zynqを利用できるようにすること。
要約すると、ソフトウェア屋さんにもフレンドリーなプラットフォームを作ってあげましょうということ。
・PYNQの利点
利点は3つ存在する。
1.FPGAのロジックの部分の書き換えが容易になっている
PYNQというシステムではFPGA部分の情報一式をオーバーレイというファイル郡で管理している。
回路情報を表す.bitファイルとハードウェア構成ファイルをセットでオーバーレイと呼ぶ。
PythonにはOverlayクラスが用意されていて、このインスタンスを作成するとコンストラクタの所で、対応する回路がFPGA部分に自動的に書き込まれる。
このため、Overlayファイルを用意しておくと、pythonでそれを呼び出してあげるだけでロジックの書き換えが自動的に完了する。
2.プログラミングの容易性
Pythonの有難いところは記述の抽象度が高いというところ。
また、ソフトウェアのライブラリが非常に充実している。
やりたいことがあればpythonのライブラリで大体できる。
Ecoシステムが既にできているのが強み。
そのEcoシステムの中にPYNQという形でFPGAが加わった。
ハードウェアへのアクセスも容易である。
メモリマップされたIOにドライバのクラスのread、writeを使ってアクセスできる。
既定のデフォルトクラスが使いづらかったり、定型的の処理がある場合も、それに応じたカスタムのドライバクラスをPythonで作成できる。
3.使い始めが非常に簡単
Ethernetでケーブルとボードをつなぐ。
Lan経由でボードにアクセスできる。
PythonのプログラミングにはJupyter Notebookを使うので、Webブラウザーからアクセスできるようになっている。
SCPやSMB、Jupyter Notebookなどいろんな形でファイルのアップロードが出来る。
別途USBのケーブルでシリアルコンソールから叩くということもできる。
利点をまとめると、自作回路をプロセッサの周辺回路として含んだFPGAシステムをお手軽に開発できる。
・PYNQに対応するボード
PYNQに対応するボードは何種類かある。
本セッションの対象
PYNQ-Z1(Digilent社)
PYNQ-Z2(TUL社)でも大丈夫。
ローエンドからハイエンドまでたくさんボードがある。
AlveoというアクセラレータカードもPYNQに対応している。
◆PYNQ-Z1ボードのセットアップ
・必要な機材を用意する
ボード本体
シリアル通信のためのmicroUSBケーブル
LANに接続するためのEthernetケーブル
ディスクイメージを書き込むためのmicroSDカード
ホストPCだけに接続したい場合は、USB-Ethernetアダプタ
・イメージファイルをダウンロード
microSDのほうにボードに対応したSDカードイメージをダウンロードする。
Linuxが入っているので、圧縮時に2GB、展開後に6GBになる。
・イメージファイルを書き込む
ダウンロードしたイメージファイルをmicroSDに書き込む。
ラズパイと同じ要領である。
ディスクイメージを書き込むためのツールは任意のモノを使ってもらって大丈夫。
書き込み先を間違えて別のディスクを破壊しないように気を付けてください。
書き込み後はパーティションが2つ作成される。
2つ目のほうがLinuxのファイルになるので、Windowsからは認識されないが正常である。
・ボードの接続を確かめる
microSDをボードに差し込んで動かす。
ジャンパピンの設定を切り替えなければならない。
どの形でBootするかを設定するジャンパピン
こちらはジャンパ位置をSDにする。
PYNQ-Z1ボードだと左上に位置する。
給電を設定するジャンパピン
microUSB経由で行うので、ジャンパ位置をUSBにする。
PYNQ-Z1ボードだと下側に位置する。
・USB-Ethernetアダプタ設定
もし、USB-Ethernetアダプタを使う場合はこの時点で設定しなければならない。
IPアドレスは192.168.2.1に設定しておいてもらうとよい。
デフォルトだと192.168.2.99というIPで使用できるようになっている。
それにサブネットを合わせるかたち。
・ボードの電源を入れる
準備が整ったら、もろもろ接続したうえで、電源を入れると自動的にSDカードからディスクイメージを読みだして、OSのBootが始まる。
Tera TermなどでCOMポートを開いてもらうと、Linuxの起動の文字列が表示され、自動的にログインされてシェルの画面が表示される。
一通りBootが終わった時点でLEDが点滅して、点灯する。そうなるとボードの準備はOK。
・Jupyter Notebookにログイン
ここまで準備ができたら、ブラウザのほうで192.168.2.99にアクセスする。
既存のLANに接続する場合はDHCPで割り当てられたIPアドレスを確認して、そこにアクセスする。
ブラウザアクセスするとJupyter Notebookのログイン画面が表示されるので、パスワードのxilinxを入力してログインすると、Jupyter Notebookのトップ画面が表示される。
ここまで行くと、非力ながらpythonのプログラムを動かせる。
あらかじめハードウェアを作ってあれば、PYNQのオーバーレイ機能でハードウェアを扱うことも可能。
◆LED点滅回路を含む設計例
実際のPYNQのFPGAの部分を書き換えたうえで、自作回路を作ったうえでPYNQのJupyter Notebookのほうから動かすという例を見ていく。
・LED点滅回路を題材とする
LEDの点滅周期CVIDはソフトウェアのほうから入力できるようにする。
回路図で言うと、カウンタがあってカウンタが一定の値になったら、今のLEDの値を反転する。
・LED点滅回路のソースコード
n_count = n_count + 1
カウンタで点滅周期の分だけ数を数える部分。
あらかじめ決めた値になったらLEDの値を反転させる。
LEDの初期値は一部点灯、一部消灯をするために切り替わる。
入力は点滅周期、出力はLEDになる。
このうちの入力周期はソフトウェアのほうから書き込みを行う。
LEDの出力は外部出力になる。
FPGAの場合だと、外部の入出力は名前が付いているだけだと、どこに割り当てればよいかわからなくなるので、手動で割り当てる必要がある。
それをするための制約ファイル.xdcファイルもあわせて記述する必要がある。
今回のLED点滅回路では、制約ファイルにて回路のLED出力をPYNQ-Z1のLED出力ピン(R14,P14,N16,M14)へ割り当てる。
・Vivadoの準備
ハードウェアの部分、FPGAの部分を書き込むためのデータファイルにVivadoを使う。
PYNQのバージョンに合わせたVivadoを用意するとトラブルが少ない。
既存の周辺回路をバージョンアップしてしまうことがある。
PYNQのドキュメントにボードファイルがあるので、事前にVivadoのインストールフォルダにコピーしておくと、プロジェクトの作成がやりやすい。
・Vivadoの使い方
・新規プロジェクトの作成
File->Project->Newで新規プロジェクト作成のウィザードを立ち上げる。
2つ目の画面で、プロジェクトの名前、プロジェクトをどこに保存するかを設定する。
create project subdirectoryにチェックを入れてほしい。
プロジェクト名と同じ名前のフォルダが作成され、必要なファイルがそちらに格納される形になる。
プロジェクトの種別はRTL Projectを選び、その下のチェックボックスDo not specify sources at this timeにチェックをしておく。
次はパーツを選択する画面になる。
Boards->PYNQ-Z1を選んでNextを押す。
最終確認画面が出るのでFinishを押して、プロジェクトの作成を完了する。
プロジェクトを作成すると、Vivado左側画面がFlow Navigatorになる。
Flow Navigatorによく使う操作にショートカットでアクセスできる。
ソースファイルを追加するにはFlow NavigatorでProject Manager->Add Sourcesを押す。
どういうファイルを追加するのかというウィザードが表示される。
今回は設計用のファイルを加えるのでAdd or create design sourcesを選択する。
そのうえで、どのファイルを追加するかの画面が出る。
Add Filesを押して、先程作成した点滅回路のファイルを追加する。
同じ要領で制約ファイル.xdcも追加する。
制約ファイル追加時はAdd Sourcesの時にAdd or create constraintsを選択することに注意してほしい。
・ブロック図の新規作成
まず回路ブロックを追加する。
Zynq(プロセッサ込みのシステム)はブロック図を使ってどの回路が必要か、どう接続するのかを操作することで開発する。
IP Integrator->Create Block Designでブロック図を新規作成していく。
デフォルトのままOKを押すと作成できる。
右上画面がブロック図作成画面になる。
+ボタンを押してブロック図を作成していく。
最初に追加しなければならないのはプロセッサの部分になる。
ZYNQ7 Processing Systemを探して、ブロック図に貼り付ける。
上側にRun Block Automationというヒントが出るので、そこを押してProcessing Systemの初期設定を行う。
変更せずにOKを押してもらえば大丈夫。
次に点滅回路を加えたい。
点滅回路は入力をソフトウェアから管理したいので、ソフトウェアからの変更を実際の回路のほうに接続するインターフェースの回路が必要である。
インターフェース回路に汎用I/O(GPI/O)を使う。
+ボタン->AXI GPIOを探して追加する。
引数のみを使うので、GPIOにとっては出力のみあれば良いということになる。
GPIOをダブルクリックして設定画面を開き、IP Configuration->All Outputsにチェックを入れる。
GPIOがアウトプットだけが用意される状態になる。
そうしたら、GPIOをプロセッサに接続していく。
先程のBlock Automationと同じようにConnection Automationが出来るというヒントが表示される。
こちらをクリックして、S_AXIにだけチェックを入れていOKを押す。
この段階でブロック図作成、変更が加わり、リセット用の回路、インターコネクトの回路が自動的に生成される。
プロセッサ部分やGPIOと自動的に接続させる。
点滅回路をブロック図のほうに追加する。
ブロック図の何もないところを右クリックしてAdd Moduleを押す。
今プロジェクトにある回路からどの回路を使うか選択する。
点滅回路のブロック図が追加されるので、GPIOの出力と点滅回路の入力を接続する。
最後にLEDの出力部分を外部の出力ポートとして接続する。
LEDの出力部分を右クリックしてCreate Portを選択する。
ダイアログがでるけど、デフォルトのままOKを押すとLEDの出力が外部の出力になる。
回路図の作成は終了。
・ハードウェアの作成
ブロック図の検証する。
ブロック図の何もないところを右クリックして、Validate Designを選択する。
Validation Successfulと出たら検証OK。
次にブロック図のラッパを行う。
ブロック図は直接論理合成を行うことが出来ないので、ラッパの回路を作成してそれを対象に論理合成を行う。
先程作成したdesign_1を右クリックして、Create HDL Wrapperを選択する。
どういう形で管理するのかを聞かれるので、自動更新するLet Vivado manage~を選択する。
design_1_wrapperというモジュールが作成されればOK。
ブロック図からそれぞれファイルを作成し、CADのほうに論理合成をお願いする。
IP Integrator->Generate Block Designを押し、Generateボタンを押すと自動で論理合成が行われる。
しばらく待つと回路の生成、合成が終わる。
手元の環境で1~2分かかった。
ハードウェアの合成を行う。
Program and Debug->Generate Bitstreamを選択する。
一連の作業が行われるので2~3分かかる。
最終的にBitstreamが生成できたとダイアログが表示される。
ここまで行けば、Vivadoでの作業は終了。
cancelをおして、ダイアログを閉じる。
・PYNQにアップロード
Bitstreamが生成し終わると、オーバーレイに必要なファイルが生成されている。
.runs¥impl_1¥design_1_wrapper.bit
.srcs¥sources_1¥bd¥design_1¥hw_handoff¥design1.hwh
それぞれを適当なファイルにコピーしてから、名前をblink.bit、blink.hwhに変更しておく。
これをPYNQに適当な方法でアップロードする。
今回はWindowsのファイル共有で行っているが、自分の好きな方法で大丈夫です。
・pythonで動作確認
そのうえでpython上での動作確認に移る。
Jupyter Notebook上でスクリプトを作成する。
まずはオーバーレイというクラスを読み込むスクリプトを書く。
そのスクリプトが読み込まれた時点で先程作成したblink.bitがFPGAの部分に自動で書き込まれる。
実行した場合にはLEDが点滅を始める。
まだ点滅周期を設定しないので、超高速で点滅してうっすら点灯している形に見える。
blink.bitを作成するところに加え、GPIOの回路を経由して点滅周期を制御するコードを書いていく。
この後は、
0.5秒周期で点滅->2秒待つ->0.25秒周期で点滅->2秒待つ->0.125秒周期で点滅->2秒待つ->終了する
というスクリプトを書いて、スクリプトを実行させLEDが点滅する実例を示していた。
この実例からVivadoの操作手順に慣れる必要があるが、ソフトウェア側からの制御が簡単にできることがわかると思う。
・参考情報
点滅回路の設定例
https://www.acri.c.titech.ac.jp/wordpress/archives/11224
コードはgithubにある
https://github.com/nfproc/connect_with_pynq/
◆グラフィックパターン送信回路を含む設計例
複雑な設計例を見ていく
・HDLについて
ある程度実用的な回路を書くときにHDLで書かないといけないのか?
書けることに越したことはないが、最近だとC、C++から高位合成をする手段もある。
最近だと高位合成で何とかなる。
高位合成を使うとHDLで一から書く時と比べると、開発期間を短くできる。
プロトタイピングだけに使うだけでも良いと思う。
高位合成で性能が出るかというと、そういうわけではない。
C言語のソースコードを書き換えて、ハードウェア向けの書き方にしなければならない。
・HDLでグラフィックパターン送信回路を作成する
次にグラフィックパターン送信回路という画面の一部分にカラーパターンを生成、表示する回路を作る。
HDMIの信号の変換は既存の回路を使用する。
どういうグラフィックパターンを作るかというと、構造体pixel_tの2次元配列を使って表現する。
yのループとxのループがあり、pixel_t[y][x]にr,b,gの値を計算で求めて書き込んでいく。
・ブロック図
C言語のプログラムを元にグラフィックパターン送信回路を書いていく。
今回、既存の回路を利用していく形で開発する。
送信した画像、映像は既存のAXI Video DMAの回路で行っていく。
PYNQ上の画面バッファに適切な形でデータを書き込むのをやってくれる。
これを使うことで、AXI Streamが使える。
通常だと、何処のメモリアドレスに何を書くのか指定しないといけない。
AXI Streamだと左上の画素から右下の画素までがそのデータを順番に送り付けるだけでパターンの書き込みができる。
ただ、AXI Streamを使えるようにプログラムを書き換えなければならない。
・C言語に対応させる
プログラムの構造体pixel_tの2次元配列だったところを
ap_axiu<24,1,1,1>データ(data) 24 bit,補助データ(user) 1 bitのストリームデータ型
にしている。
ストリームを扱う形にプログラムを書き換えている。
また、プログラム本体で表記できない負荷情報はプラグマを使って表現する。
今回はaxi streamを使うといった負荷情報を表現する。
引数をどのような形で回路のインターフェースに変換するか、回路のパイプライン化を適応するかを表現できる。
一通り、プログラムを書いたらテストベンチを記述する。
CやC++から高位合成で書くこともできる。
今回は、送信されたパターンのチェックサムを求めて、表示するというテストベンチを作った。
・開発の流れ
ソースコードは以上。
ここから先はVItis HLSというXilinx社の高位合成ツールを使う。
作成した関数をハードウェア化すると先程のブロック図で使ったIPコアが使える。
そのあとは点滅回路と同じ要領になる。
・Vitis HLSのプロジェクト作成
まずはVitis HLSを起動して、プロジェクトを作成する。
File->New Project
プロジェクト名と作業ディレクトリを指定する。
回路記述のソースコードを指定する。
まずソースファイルとして先程作成したパターン送信回路のmain関数の含まれているC言語ファイルと、それからincludeされているヘッダファイルを追加する。
TopFunctionからどの関数高位合成の対象にするかを決める。
次にテストベンチ(先程のチェックサムを取る)のプログラムのソースコードを追加する。
最後に対象のボードを選択する。
今回はPYNQ-Z1になる。
Finishを押すことでプロジェクトの作成が完了する。
・高位合成の際に必要な作業は大きく4つ存在する
1.そのプログラムがC、C++としてしっかり動くのかを確認する
これはC Simulationで行う。
Cで実行した結果、今回はいくつかのチェックサムが表示される。
2.関数を高位合成して、ハードウェアに変換する
Solution->Run C Synthesis->Active Solutionを使用する。
1分程度待つと高位合成が完了する。
関数の実行にかかるサイクルが見積りできる。
今回は384,003サイクルかかる見積り。
3.高位合成で出来上がった回路が正しく動いているかをCとRTLの協調シミュレーションで確認をしていく
Solution->Run C/RTL Cosimulationを押す。
テストベンチはC/C++、ハードウェア化した関数は論理シミュレーションで実行される。
チェックサムが表示され、C/RTL Cosimulation PASSと表示されれば回路のチェックは終了する。
4.高位合成で出来上がったをVivadoで使える形でエクスポートする
Solution->Export RTLを選択する。
FormatがVivado IPであることを確認してOKすると、プロジェクトのディレクトリにexport.zipが作成される。
・リポジトリの追加
これが終わった後はVivadoのほうでブロック図を作成する。
今回はexport.zipのパターン送信回路の回路に加え、Digilent社のGithubからダウンロードできる。
既存のビデオエンコーダやインターフェースの定義を使用していく。
これらをまとめたディレクトリをIPリポジトリに追加する。
具体的にはVivadoのプロジェクトを作成したあとで、設定の所のIPー>Repositoryから+を押して先程作成したフォルダを追加する。
・ブロック図の作成
ブロック図の作成方法は基本的に同じ。
違うところもあるので
https://www.acri.c.titech.ac.jp/wordpress/archives/8512
のブログを参照してもらえればと思う。
ブログではパターン送信回路をRTLで記述しているが、今回はここをC言語の高位合成で置き換える。
一部必要な操作が異なる。
割り込みの信号線の接続が必要なので、そこは自分で行う。
・pythonプログラム
完了したらこちらでpythonのプログラムの記述を行う。
hdmi_sender.bitをオーバーレイとして読み込む。
そのうえでいくつかのフレームバッファの準備をしてmainループに入る。
mainではフレーム番号を経過時間×60で求める。
これが増加するまで待ち続ける。
フレーム番号が増加したら、パターン送信回路を起動する。
そのうえで終了を待つ。
mainループが終わった時点で処理したフレーム数を出力する。
20秒行っているので、20×60=1200フレームが表示される。
・実行
この後は動かした結果を見た。
一定時間パターンが送信されていることを確認した。
・参考情報
今回はオリジナルで高位合成を行った。
高位合成を利用する例を出しているので確認してください。
https://www.acri.c.titech.ac.jp/wordpress/archives/12551
ソースコードはgithubで公開している。
https://github.com/nfproc/connect_with_pynq/
質問
・shibakawaさん
Q Pythonからの高位合成は出来ないのか? できるのならPythonに閉じた設計ができるのではないか?
A 公式ではそういった環境をやっていない。
Xilinx社の外ではそういった環境はある。
Pythonで閉じるということはやろうと思えばできる。
・yamazakiさん(北九大)
Q 紹介して頂いたリンクをたどると、「他のZynqボードでの動かし方」を見ている。
これは他のZynqボードでの労力はどれくらいかかるのか、似ているのなら同じくらいの労力か?
A 自分で試したことないが、操作に従って有志で作ってみたかたがそのリンクにいる。
自分では試していないので、お答えしにくい。
Q Zynqの場合だとCPUが載っている ArmのCPUとFPGAで協調して動作するプログラムを書きたい場合の手順はどうすればよいですか?
A 今回は回路の終了待ち、FPGAの回路を動かしたら終了するまでbusyループで待つというプログラムで書いている。
非同期で動くので、FPGAで仕事を投げた後にPythonで他の処理をすることは可能。
・チャットからの質問 質問者不明
Q 高位合成で「無限に動き続ける回路」は作れますか?
CPUからkickすると、センサからのデータ入力をCPUに送り続けるイメージです。
A 可能です。
高位合成した回路をインターフェースとしてオートリスタート機能がある。
そちらを使うとソフトウェア側から何も言わなくても動き続ける事が可能。
Q C言語のプラグマで書けばいいのか?
A AXIのインターフェースのほうに自動的にオートリスタートというメモリマップのIOが生成されたはず。
■まとめ
PYNQを使ってFPGAシステム開発をお手軽に始めてみようというもの。
ボードのセットアップはラズパイと同じくらいの労力でできる。
ハードウェアのオーバーレイは手順が多めだが定型的な作業が多く、慣れるとサクサクできる。
高位合成の頭が良くなっているので、HDLを書かずともC/C++だけでも作成できる。
以上。