1.記事一覧
【旧版】こちらの記事は旧版です。開発手順に古いやり方や、一部間違いがあったため、改訂版記事を作り直しました。改訂版はこちら
記事は複数回に分けて投稿する予定です。XilinxのARM64 SoC(ZynqMP Kria K26 SOM)で動作する、Xilinx公式認定Ubuntuを勉強していく防備録です。
Note:記事の内容に関して一切の責任を負いません。
ZynqMP(Arm64) 組込みLinux入門編
- 【ZynqMP】1.[改訂]認定UbuntuでPLのGPIOを使う
- 【ZynqMP】1.[旧版]認定UbuntuでPLのGPIOを使う ←本記事
- 【ZynqMP】1 .1 AXI-GPIOが使えない?
- 【ZynqMP】2.認定Ubuntuのカーネルビルド
- 【ZynqMP】3.認定UbuntuのDeviceTree変更
2.目次
- 1.記事一覧
- 2.目次
- 3.【概要】KR260ボードでPL側のGPIOを使いたい
- 4.前提条件
- 5.VivadoでPL向けbitstream.binを作成
- 6.Devicetree Overlay の生成とビルド
- 7.KR260でPL書込みを実行
- 8.SysfsによるGPIO入出力
3.【概要】KR260ボードでPL側のGPIOを使いたい
今回、Xilinxの評価ボードKR260を入手できたので、初めてのZynqMPの開発にチャレンジしたいと思います。
3-1. KR260ボード+認定Ubuntu22.04開発事始め
本記事でやること:
KR260評価ボードと公式認定Ubuntuを使い、以下の流れでやっていきます。
- PL(FPGA)上にAXI-GPIO IPを実装するためのBitstreamを作成
- Devicetree Overlayソース作成とコンパイル
- Linux起動
- BitstreamをDevicetree Overlayを利用して書込み
- ユーザアプリから、SysfsでGPIO入出力(オンボードLED、Pmod接続スイッチ)
- ユーザアプリから、SysfsでGPIOスイッチ入力ピン変化通知(≒割込み)
※ユーザ用オンボードLEDは矢印のところに2個あります。
Kria SOMと従来のZynqMPボードの違い:
- ブートローダのBOOT.binにPLのBitstream.bitを含まない
- Linux起動後にPLをアプリから動的書換えできる
- Linux起動中に書換えるため、Devicetree Overlayを使う
- ラズパイComputeModuleのように、ZynqMPSoC、DDR、PMIC配線が開発済
今回自分が初めてやること:
- 初ZynqMP (Zynq7は少し触った程度)
- Kria K26 SOM・KR260の回路図読解、制約条件ファイルの設定
- Devicetree Overlayの扱い
- AXI-GPIO IPでピン入力変化通知(≒割込み)
3-2. KR260ボードとは
Xilinx社公式で販売しているKria K26 SOMボードベースのロボティクス向け評価ボードです。
https://japan.xilinx.com/products/som/kria/kr260-robotics-starter-kit.html
Kria K26 SOMは今までのZynqMPSoCのチップ単位での供給ではなく、設計難易度の高い、DDR4 I/F、電源回路(PMIC)、Flashなどの配線をあらかじめ基板上に既に実装し、メザニンコネクタなどで、周辺I/Oを取り出せるようになっている、SOM(System On Module)のようです。Raspberry Pi Compute ModuleもSOMで、産業用途で普及し始めてます。ユーザはSOMが載るカスタムキャリアボードを設計すれば良いだけなのでハードルが下がります。
KR260ボードはK26 SOMを載せた評価ボードの1つで、ロボット開発向けのボードでROS2を動かすこともできるようです。KR260ボードよりも以前にKV260ボードが発売されましたが、こちらはAIビジョン向けの評価ボードになっています。
購入先ですが、私はDigikeyで個人購入しました。最終用途証明(米国EAR?)周りが面倒そうだなと思っていたら、注文時に英語で用途を記入するだけらしいことを知り、初めてDigikeyを利用しました。(実際何の証明にもなってませんが、特に追求されませんでした。まあ個数1だし…)
円安の影響も合ってか、送料込¥67,000ほどかかりました(2023/6)。結構高いです。正直個人趣味で購入するのは金銭的に結構しんどいです。今年で14年目になる骨董品UbuntuデスクトップPCを新調したかったのですが、後2年頑張ってもらいましょう。
3-3. ドキュメント類
★公式Wiki
XilinxはWikiとKriaSOM専用ページがあるので、情報はそこから探すことになります。基本的にここに書いてあることをなぞっていく記事内容になります。
★公式ドキュメント
データシートやマニュアル類はここで検索できます。ちなみに回路図のようなAMDアカウント認証が必要なものは検索に出てきません(今のところは)。
★認定Ubuntuセットアップ
本記事はセットアップ済みを前提としています。Webに情報が豊富なため、内容は説明しません。参考リンクを貼っておきます。xmutilツールも使用するのでセットアップしておいてください。
★回路図
回路図はAMDアカウント無料登録しないとダウンロードできません。また、Kria K26 SOMボードの回路図は現在非公開です。制約ファイルの書き方は後述しますが、公開されているSOM制約ファイルがあるので、それを使用します。
- KR260キャリアボード回路図 ここからBoard Filesをチェックして以下を探す
XTP743 – Kria KR260 Starter Kit Carrier Card Schematics (v1.0) - K26 SOM制約ファイル ここから資料タブ>>Board Filesをチェックして以下を探す
XTP685 – Kria K26 SOM XDC File (v1.0)
4.前提条件
作業を始める前に、前提条件を示します。既に準備を終えていることが条件となります。
- KR260ボードのQSPI Firmwareを下記のVer以上に更新しておく
- SDカードに認定Ubuntuを入れてセットアップ済みとする
- 開発PCとSSH接続と(できれば)VSCode Remote Developmentで接続の準備済みとする
- Vivado・Vitis開発環境を使える状態であること
- 認定Ubuntuはデスクトップ版ですが、GUIを使用せず、端末で操作する
開発PC | Ubuntu20.04 LTS VSCode : Remote-Development(あると便利) |
開発ツール | Xilinx社 Vivado・Vitis v2023.1 |
Xilinx IP | Vivadoにて以下を主に使用 ・AXI-GPIO |
ターゲットボード | Xilinx社製 Kria KR260 ロボティクススターターキット(SK-KR260-G) SOM : Xilinx社 Kria K26 SOM (Zynq UltraScale+ MPSoCベース) APU : Arm CA53x4 1333MHz arm64 RPU : Arm CR5x2 533MHz armv7-R SDRAM : DDR4 4GB QSPI Firmware : Xilinx download – 2022.2_update1_BOOT.BIN Linux Kernel : v5.15.0-1023-xilinx-zynqmp by 認定Ubuntu OS : Xilinx認定Ubuntu22.04 LTS(arm64) |
GPIO向け回路部品 | ・ブレッドボード ・ジャンパ線 ・10k抵抗2点 ・プッシュスイッチ2個 |
5.VivadoでPL向けbitstream.binを作成
5-1. プロジェクト作成と.bin出力の設定
注意:Vivadoの詳しい操作方法は説明しません。要点だけ載せます。
プロジェクトを作成します。→プロジェクト名demo_kr260_axi_gpio
パーツ選択画面では以下のようにします。Refreshを押して最新ボードファイルをインポートしておきます。
【追記】下記画像で”Connections”の設定を忘れていました。これをやらないとボードのプリセット設定が反映されません。
プロジェクトが作成できたら、bitstreamのbinファイルを作成するように設定します。
PROJECT MANAGER>Settings>Project Settings>Bitstream>”-bin_file”にチェック
基本的にFPGAコンフィグレーション用bitstreamは”.bit”のファイルであるのですが、今回使うDevicetree Overlay用のLinuxの機能のFPGA-Regionでは直接扱うことができないらしいです。最新のカーネルでは.bitを内部で.binに変換することで.bitでも扱うことができるらしいですが、コンフィグレーションに時間がかかるデメリットがあるそうです。
Webで調べると、bootgenコマンドで変換する方法が見つかりますが、最近のVivadoには直接.binを出力できるオプションがあるみたいなので、有効にします。
5-2. IPインテグレータで冷却ファンの制御信号を設定
ポイント:記事と無関係ですが、ファンが全開になってうるさいので対策をします。
AXI-GPIOの設定の前に下準備として先にFAN制御信号を配線します。認定Ubuntu22.04では、FANの温度制御サービスが動作していて、FANの回転数をかなりアクティブに制御します。FANへの制御信号がPLを経由してI/Oとして出ているので、制御信号を配線しないと、PLを書き換えたときにFANがフル回転でうるさくなります。
詳しくは先人の方の解説がめちゃくちゃ詳しいです。
- Debian/Ubuntu でKV260 で冷却ファンを制御する (その1) @ikwzm様
- Kria KV260 fanコントロール Ryuz88様
ikwzmさんは毎度高度な記事を執筆されています。現状私の知識ではほとんど理解が追いつかないですが、いつか理解できるようになりたいです。それにしてもファンコンでここまで語れるのすごいですね…
さて、IPインテグレータでBlock Designを作成します。名前は任意で良いです。
→Block Design名:demo_kr260_axi_gpio
ブロックは以下のようにします。
PSブロックのTTCタイマの設定は以下のようにします。
Sliceブロックは以下のようにします。2bit目を取り出します。
5-3. AXI-GPIOを追加する
以下のようにAXI GPIOブロックを追加し、自動配線します。Processor System ResetとAXI Inter connectブロックは自動配線で現れます。
手動で接続するのは以下3つの信号(オレンジ線)です。
- UFx_LED外部信号2bit ー>オンボードLEDを2つ使用します。
- PUSH_SW外部信号2bit ー>Pmodコネクタに押しボタンスイッチ2つ接続します。
- ip2intc_irpt割込み信号線 ー>スイッチ押下による、ピン入力通知に使用します。
AXI GPIOブロックの設定は以下になります。
5-4. 端子制約条件の設定
IPインテグレータによる配線は終了したので、論理合成(Run Synthesis)を実行します。
終了ダイアログが出たら、Open Synthesized Designを開きます(左のFlow Navigatorからも可)。
以下のように設定します。端子の決め方はあとで説明します。
- PUSH_SW_tri_i[1] : PackagePin=E10, I/OStd=LVCMOS33 <ーPmodスイッチ2に接続
- PUSH_SW_tri_i[0] : PackagePin=H12, I/OStd=LVCMOS33 <ーPmodスイッチ1に接続
- UFx_LED_tri_o[1] : PackagePin=E8, I/OStd=LVCMOS18 <ーUF2 LEDに接続
- UFx_LED_tri_o[0] : PackagePin=F8, I/OStd=LVCMOS18 <ーUF1 LEDに接続
- FAN_EN[0] : PackagePin=A12, I/OStd=LVCMOS33 <ー冷却ファンに接続
Ctrl+Sで保存すると、制約ファイルを保存するダイアログが出るので、適当な名前で.xdcファイルを保存します。
5-5. K26 SOM制約条件のための回路図の見方
VivadoではK26SoCチップの端子レベルで制約条件を記述します。
キャリアボードの回路図→SOMの回路図→K26 SoC端子の順で追えばわかるはずですが、現状SOMの回路図が公開されておらず、SOMの制約条件(.xdc)ファイルが公開されているようです。
詳しくは先人の方の説明を見てください。おかげさまで存在無きSOMの回路図を探す旅から帰らぬ人になるところでした。助かります。
KV260でSystemVerilogでLEDチカしてみる>LED 出力の配線を調べる – Ryuz88様
Vivado 2023.1を使えばSOM I/F端子指定で制約条件記載できるのかなと期待したのですが、私はやり方がわかりません(それかまだ機能が未実装)。
そのため、上記記事を参考にしながらやってみます。
オンボードユーザLED
KR260キャリアボード上には”UF1″、”UF2″とシルクで書かれたチップLEDが2個実装されています。
LED回路図は以下の様になっています。
正論理で以下の対応になります。
- UF1 LED = HPA14P
- UF2 LED = HPA14N
各線はSOM240_1コネクタに接続されています(SOM240_2もあるので注意)。
コネクタ端子を特定できれば、キャリアボード側はとりあえずOKです。
- UF1 LED = HPA14P = SOM240_1_D13
- UF2 LED = HPA14N = SOM240_1_D14
続いてK26 SOMの制約条件ファイルを見ると、K26 SoCの端子がわかります。
set_property PACKAGE_PIN E8 [get_ports "som240_1_d14"] ;# Bank 66 VCCO - som240_1_d1 - IO_L17N_T2U_N9_AD10N_66
set_property PACKAGE_PIN F8 [get_ports "som240_1_d13"] ;# Bank 66 VCCO - som240_1_d1 - IO_L17P_T2U_N8_AD10P_66
- UF1 LED = HPA14P = SOM240_1_D13 = F8 I/OStd=LVCMOS18 Drive Strength=12mA
- UF2 LED = HPA14N = SOM240_1_D14 = E8 I/OStd=LVCMOS18 Drive Strength=12mA
上の制約ファイルのコメントに端子の所属バンク”66″とバンク電源端子”som240_1_d1″とあります。上のSOMコネクタの回路図では”PL_1V8″つまり電源1.8Vなので、両端子とも”LVCMOS18(シングルエンド)”になります。
許容電流(Drive Strength)の決め方ですが、あくまで回路屋ではない素人の意見として聞いてください。この場合は、MAXの12mAにします。FETのゲートドライブなので、基本的に電流は流れません。しかし、ゲート容量分の充放電が発生します。その電流ピーク値が許容電流値を下回る様に設定をします。また、高速スイッチングしないので、許容電流はMAXで大丈夫だと思います。間違っていたらどなたか教えてください。
Pmodプッシュスイッチ
今回はPmod1の端子にスイッチを2つ接続させることにします。
オンボードユーザLEDと同様に追いかけると、以下のようになります。
- PMOD1_IO1_HDA11 = SOM240_1_A17 = H12 I/OStd = LVCMOS33
- PMOD1_IO1_HDA12 = SOM240_1_D20 = E10 I/OStd = LVCMOS33
ブレッドボード配線は以下のようになります。
冷却ファン
冷却ファンはローサイドスイッチングの回路です。FET(Q20)がもう一個追加されて論理反転で負論理になっています。制御信号が切断したときにファンが全開で回るようフェイルセーフしてあるようです。
オンボードユーザLEDと同様に追いかけると、以下のようになります。
- HDA20 = A12 I/OStd = LVCMOS33 Drive Strength = 16mA
許容電流もLEDと同じ理屈でMAXにしておきます。
5-6. Bitstream生成とハードウェアエクスポート
制約条件を記述できたのでGenerate Bitstreamで生成します。以下に出てきます。
demo_kr260_axi_gpio.runs/impl_1/demo_kr260_axi_gpio_wrapper.bin
次にVitis向けのハードウェアエクスポートと同じ要領で.xsaを生成します。Vitisは使用しませんが、XSCTツールとDTG(Devicetree Generator)で使用します。
注意点として、エクスポートする際、Include bitstreamを選択します。このあと、デバイスツリーをOverlay用に出力する際に(一応)必要らしいです。
6.Devicetree Overlay の生成とビルド
Devicetreeとは
デバイス構成(どのバスにどのデバイスが接続など)、デバイス定数(レジスタの物理アドレス、割込み番号、GPIObit幅など)を記述したデータ定義ファイルです。これをDTS(DeviceTreeSource)と呼びます。デバイスツリーコンパイラでコンパイルして、DTB(DeviceTreeBlob)をLinuxに組込みます。
Kernel v3.xxあたりで、Arm Linux向けに導入されました。x86はありません。
現在、Arm Linuxドライバはデバイスツリーとペアで開発されます。AXI-GPIOドライバをmodprobeするときにデバイスツリーからパラメータを読み出して初期化するので、今回はデバイスツリーを用意する必要があります。
Devicetree Overlayとは
現在のFPGA搭載SoCは起動中に動的にFPGAの内容を書き換える、動的再構成が可能です。これはすなわちLinuxから見て起動中にデバイス構成が変更されるということです。そこでデバイスツリーを動的再構成出来る技術が登場しました。それがDevicetree Overlayです。Linuxカーネルの機能のFPGA-RegionでFPGA書換とOverlay、そしてデバイスドライバのmodprobeを実行できるようにしています。
6-1. DTC(Device Tree Compiler)の準備
デバイスツリー向けのコンパイラをインストールします。aptだと簡単にインストールできますが、若干古いです。最新のものを使用する場合ソースビルドします(推奨)。
# ソースビルドする場合
$ git clone https://git.kernel.org/pub/scm/utils/dtc/dtc.git --depth=1
$ cd dtc
$ make
$ sudo cp ./dtc /usr/local/bin/
$ dtc -v
Version: DTC 1.6.1
# apt で入れる場合(Ubuntu20.04 LTS)
$ sudo apt install device-tree-compiler
$ dtc -v
Version: DTC 1.5.0
6-2. DTG(Device Tree Generator)の準備
前章でハードウェアファイル(.xsa)をエクスポートしたので、次の準備としてDTGをcloneします。.xsaを中に移動しておきます。
【追記】以下はmasterブランチをcloneしていますが、masterブランチは最新のコミットが反映されていませんので、xilinx_rel_v20XX.Xブランチをチェックアウトする必要があります。
$ ls
demo_kr260_axi_gpio.xsa
$ git clone https://github.com/Xilinx/device-tree-xlnx.git --depth=1
$ cd device-tree-xlnx
# ここのルートに.xsaをコピー
$ cp ../demo_kr260_axi_gpio.xsa ./
6-3. XSCTによるDevicetree Overlayソースの出力
DTGのフォルダに入り、XSCTでhsiコマンドを実行します。そのとき、Device tree Overlay向けのフラグを有効にして生成します。CONFIG.dt_zoclフラグは今回は使いません。
【追記】createdtsコマンドを使用すると1発でいけます。詳しくは改訂版へ
$ cd device-tree-xlnx
$ xsct
xsct% hsi open_hw_design demo_kr260_axi_gpio.xsa
demo_kr260_axi_gpio_wrapper
xsct% hsi set_repo_path ./
xsct% hsi create_sw_design device-tree -os device_tree -proc psu_cortexa53_0
xsct% hsi set_property CONFIG.dt_overlay true [hsi::get_os] #overlayフラグ
xsct% hsi generate_target -dir my_dtso
xsct% hsi close_hw_design demo_kr260_axi_gpio_wrapper
xsct% exit
#以下に出力
$ ls ./my_dtso/
device-tree.mss include pcw.dtsi pl.dtsi system-top.dts zynqmp-clk-ccf.dtsi zynqmp.dtsi
6-4. Devicetree Overlayソースを見てみる
【追記】以下のDTSに不具合があったのはDTGをclone後、適切なブランチにチェックアウトせず、古いままのmasterを使ったためです。私が公式のDocを読み飛ばしていました。
pl.dtsiがPL側のデバイスツリーソースになっています。dtsiですが、中身はOverlay記述になっていますので、このままコンパイル可能です…がこれは動作しませんでした。
L.43の”#gpio-cells = <3>;”は今回のUbuntuのKernel verだと、modprobeに失敗するので、”#gpio-cells = <2>;”に修正します。
詳細は次回の記事にまとめます。
/* * CAUTION: This file is automatically generated by Xilinx. * Version: XSCT 2023.1 * Today is: Thu Aug 31 16:43:26 2023 */ /dts-v1/; /plugin/; / { fragment@0 { target = <&fpga_full>; overlay0: __overlay__ { #address-cells = <2>; #size-cells = <2>; firmware-name = "demo_kr260_axi_gpio.bit.bin"; resets = <&zynqmp_reset 116>; }; }; fragment@1 { target = <&amba>; overlay1: __overlay__ { afi0: afi0 { compatible = "xlnx,afi-fpga"; config-afi = < 0 0>, <1 0>, <2 0>, <3 0>, <4 0>, <5 0>, <6 0>, <7 0>, <8 0>, <9 0>, <10 0>, <11 0>, <12 0>, <13 0>, <14 0xa00>, <15 0x000>; }; clocking0: clocking0 { #clock-cells = <0>; assigned-clock-rates = <99999001>; assigned-clocks = <&zynqmp_clk 71>; clock-output-names = "fabric_clk"; clocks = <&zynqmp_clk 71>; compatible = "xlnx,fclk"; }; }; }; fragment@2 { target = <&amba>; overlay2: __overlay__ { #address-cells = <2>; #size-cells = <2>; axi_gpio_0: gpio@a0000000 { #gpio-cells = <3>; /* <--gpio-cellsが2以外の場合、2に修正 */ #interrupt-cells = <2>; clock-names = "s_axi_aclk"; clocks = <&zynqmp_clk 71>; compatible = "xlnx,axi-gpio-2.0", "xlnx,xps-gpio-1.00.a"; gpio-controller ; interrupt-controller ; interrupt-names = "ip2intc_irpt"; interrupt-parent = <&gic>; interrupts = <0 89 4>; reg = <0x0 0xa0000000 0x0 0x10000>; xlnx,all-inputs = <0x0>; xlnx,all-inputs-2 = <0x1>; xlnx,all-outputs = <0x1>; xlnx,all-outputs-2 = <0x0>; xlnx,dout-default = <0x00000000>; xlnx,dout-default-2 = <0x00000000>; xlnx,gpio-width = <0x2>; xlnx,gpio2-width = <0x2>; xlnx,interrupt-present = <0x1>; xlnx,is-dual = <0x1>; xlnx,tri-default = <0xFFFFFFFF>; xlnx,tri-default-2 = <0xFFFFFFFF>; }; }; }; };
簡単な内容の説明です。
fragment@0
firmware-nameにPL書込み対象のbitstream.binファイルの名前が入ります。ハードウェアエクスポート時にbitstreamを含むと名前が入るようです。ただし、Vivadoのプロジェクト出力のbinファイル名と一致しませんので、今回はbinファイルを改名します。
fragment@1
AFIと呼ばれるPS-PL間ポートに内蔵するAxi Fifo Interface向けのドライバに関する記述です。ikwzmさんが解説されています。
Ultra96 向け Debian GNU/Linux でAXI I/F のデータのビット幅を変更する-@ikwzm様
fragment@2
今回追加したAXI-GPIOのドライバに関する記述です。
以下の記述でAXI-GPIOドライバを紐づけします。
compatible = “xlnx,axi-gpio-2.0”, “xlnx,xps-gpio-1.00.a”;
6-5. Device treeコンパイル
pl.dtsiをDTCでコンパイルします。-@オプションをつけてシンボルも出力します。Warningが出ていますが、コンパイルは完了し、dtboは出力されます。
Warningの理由は調べたのですが、知識が足りずよくわかりませんでした。ただし、今回の記事の内容は問題なく動きます。ちなみにaptでinstall出来る古いDTC(1.5.0)ではWarning出ません。
$ dtc -@ -O dtb -o demo_kr260_axi_gpio.dtbo pl.dtsi
pl.dtsi:42.30-66.6: Warning (interrupt_provider): /fragment@2/__overlay__/gpio@a0000000: Missing #address-cells in interrupt provider
$ ls | grep dtbo
demo_kr260_axi_gpio.dtbo
6-6. おまけ:認定UbuntuにAXI-GPIOドライバは入ってる?
デバイスツリーは基本的にドライバとペアで必要になります。AXI-GPIOドライバの方も認定Ubuntuで使えるようにする必要があります。
ただし、結論を書くと認定Ubuntu22.04にはプリインストールされていますので特に準備することはありません。さすが認定Ubuntuですね。カーネルモジュールのビルド&インストール、またはLinuxカーネルのリビルドは今回は不要になります。
今後認定Ubuntu以外の環境に備えて自分で調べられるように予習してみます。カーネルコンフィグを見て、ドライバが有効になっていれば大丈夫そうです。
Xilinx WikiでAXI-GPIOドライバのコンフィグ名を調べます。以下の3つあれば良いようです。
CONFIG_GPIO_SYSFS=y
CONFIG_SYSFS=y
CONFIG_GPIO_XILINX=y (
for
axi_gpio)
KR260を起動し、SSHでログインしてカーネルコンフィグを確認します。
ubuntu@kria:~/$ zcat /proc/config.gz > kconfig.txt
ubuntu@kria:~/$ $ cat kconfig.txt |grep -e CONFIG_GPIO_SYSFS -e CONFIG_SYSFS -e CONFIG_GPIO_XILINX
# CONFIG_SYSFS_DEPRECATED is not set
CONFIG_SYSFS_SYSCALL=y
CONFIG_GPIO_SYSFS=y
CONFIG_GPIO_XILINX=y
CONFIG_SYSFS=y
すべて有効”=y”になっているのでドライバは大丈夫です。
ちなみに/proc/config.gzはカーネルコンフィグのCONFIG_IKCONFIGとCONFIG_IKCONFIG_PROCが有効な状態でビルドされていないと見れません。無い場合は/boot以下のフォルダから見つけます。
ubuntu@kria:~$ ls /boot |grep config-$(uname -r)
config-5.15.0-1023-xilinx-zynqmp
7.KR260でPL書込みを実行
7-1. bitstreamとデバイスツリーの転送
以下にbitstreamとデバイスツリーが揃いました。適当なフォルダに取り出してまとめておきます。
- demo_kr260_axi_gpio/demo_kr260_axi_gpio.runs/impl_1/demo_kr260_axi_gpio_wrapper.bin
- device-tree-xlnx/my_dtso/demo_kr260_axi_gpio.dtbo
binファイルの名前がデバイスツリーソースの内容と違うので修正します。修正したら、2ファイルをKR260の適当なフォルダに転送します。
#デバイスツリーソースのfirmware-name ="hogehoge"の名前に改名
$ mv demo_kr260_axi_gpio_wrapper.bin demo_kr260_axi_gpio.bit.bin
# 転送
$ scp ./demo_kr260_axi_gpio.bit.bin ubuntu@<KR260のIPアドレス>:~/
$ scp ./demo_kr260_axi_gpio.dtbo ubuntu@<KR260のIPアドレス>:~/
7-2. xmutilによるPL書込み前準備
Linux上でPL書込みを行うにはFPGA-Region機能の仕組みを使う必要があります。しかし、認定Ubuntuにはxmutil(Xilinx-Wiki,Github)というFPGAコンフィグレーションを管理するツールが入っているので、今回はこれを使います。
まず、/lib/firmware/xilinx以下にファームウェアフォルダを作成し、bitstreamとデバイスツリーを格納します。
ubuntu@kria:~$ ls /lib/firmware/xilinx/
k26-starter-kits #<-起動直後にPLに書き込まれるデフォルトのやつ
#ファームウェアフォルダ作成
ubuntu@kria:~$ sudo mkdir /lib/firmware/xilinx/demo_kr260_axi_gpio
#bitstreamとデバイスツリー格納
ubuntu@kria:~$ sudo mv ./demo_kr260_axi_gpio.bit.bin /lib/firmware/xilinx/demo_kr260_axi_gpio/
ubuntu@kria:~$ sudo mv ./demo_kr260_axi_gpio.dtbo /lib/firmware/xilinx/demo_kr260_axi_gpio/
次にshell.jsonファイルを用意します。dfx-mgrというxmutil内部で呼び出される機能のためのメタデータファイルです。下記のデフォルトのファームウェアからコピーします。
/lib/firmware/xilinx/k26-starter-kits/shell.json
ubuntu@kria:~$ sudo cp /lib/firmware/xilinx/k26-starter-kits/shell.json /lib/firmware/xilinx/demo_kr260_axi_gpio/
ちなみにshell.jsonの中身は以下の内容です。
{
"shell_type" : "XRT_FLAT",
"num_slots": "1"
}
7-3. xmutilによるPL書込み実行
xmutil listappsで先程の準備が完了しているか確認します。認識されているようです。
ubuntu@kria:~$ sudo xmutil listapps
Accelerator Accel_type Base Base_type #slots(PL+AIE) Active_slot
k26-starter-kits XRT_FLAT k26-starter-kits XRT_FLAT (0+0) 0,
demo_kr260_axi_gpio XRT_FLAT demo_kr260_axi_gpio XRT_FLAT (0+0) -1
ubuntu@kria:~$
現状のPLの内容をアンロードします。制御信号が切断されるため冷却ファンが全開になります。
ubuntu@kria:~$ sudo xmutil unloadapp
remove from slot 0 returns: 0 (Ok)
PL書込みを実行します。ファームウェアフォルダ名を指定します。成功すればファンの制御が戻るはずです。
ubuntu@kria:~$ sudo xmutil loadapp demo_kr260_axi_gpio
demo_kr260_axi_gpio: loaded to slot 0
7-4. おまけ:デフォルトのbitstreamの設定変更
2023/8現在、認定Ubuntu22.04では起動直後にPLに書き込まれるbitstreamは以下のk26-starter-kitsファームウェアになっています。
- /lib/firmware/xilinx/k26-starter-kits
デフォルトのbitstreamを変更したい場合、Xilinx-Wikiによると、dfx-mgrデーモンの設定“/etc/dfx-mgrd/daemon.conf”を見てみれば良いようです。
ubuntu@kria:~$ cat /etc/dfx-mgrd/daemon.conf
{
"firmware_location": ["/lib/firmware/xilinx"],
"default_accel":"/etc/dfx-mgrd/default_firmware"
}
“/etc/dfx-mgrd/default_firmware”の内容を参照するようです。中身はテキストでファームウェアフォルダ名が書いてありました。これを書き換えてみましょう。
ubuntu@kria:~$ cat /etc/dfx-mgrd/default_firmware
k26-starter-kits
#デフォルトのbitstreamをカスタムのものに変更する
$ sudo sh -c "echo demo_kr260_axi_gpio > /etc/dfx-mgrd/default_firmware"
ubuntu@kria:~$ cat /etc/dfx-mgrd/default_firmware
demo_kr260_axi_gpio
再起動するとカスタムbitstreamが書き込まれていました。
8.SysfsによるGPIO入出力
8-1. GPIOポートの特定と有効化
PL書込みでデバイツリーが正常にロードされれば、AXI-GPIOドライバがmodprobeされるはずです。
#GPIO I/F一覧表示
ubuntu@kria:~$ ls /sys/class/gpio/
export gpiochip326 gpiochip334 gpiochip508 unexport
#PL書込み
ubuntu@kria:~$ sudo xmutil unloadapp
remove from slot 0 returns: 0 (Ok)
ubuntu@kria:~$ sudo xmutil loadapp demo_kr260_axi_gpio
demo_kr260_axi_gpio: loaded to slot 0
#GPIO I/F一覧再表示
ubuntu@kria:~$ ls /sys/class/gpio/
export gpiochip322 gpiochip326 gpiochip334 gpiochip508 unexport
今回はSysfsを使ったGPIOの操作を行います。
私の環境ではgpiochip322が出現しました。これがAXI-GPIOのI/Fになります。この番号を固定で設定できれば便利なのですが、調べてもよくわかりませんでした。
そのため、現状判別は/sys/class/gpioXXX/labelを見ることにします。
#AXI-GPIO
ubuntu@kria:~$ cat /sys/class/gpio/gpiochip322/label
a0000000.gpio
#ついでにデフォルトで存在する他の3つも見てみます
ubuntu@kria:~$ cat /sys/class/gpio/gpiochip326/label
slg7xl45106
ubuntu@kria:~$ cat /sys/class/gpio/gpiochip334/label
zynqmp_gpio
ubuntu@kria:~$ cat /sys/class/gpio/gpiochip508/label
firmware:zynqmp-firmware:gpio
a0000000はPL-PS I/FのHPM0_FPDポートに接続されたAXI-GPIOのベース物理アドレスになります。VivadoではIPインテグレータのAddressEditorから確認できます。
デフォルトで存在する他の3つのgpiochipも見てみたのですが、どれが何を指しているかはわかりません。(仮に調べたい場合、KR260実機のデバイスツリーを読み出してデコンパイルする必要がありそうです)
続いてGPIOを有効化します。AXI-GPIOのIP設定でデュアルチャネルポートでそれぞれ2bitずつに設定しました。そのため、計4bit分有効化します。先程のgpiochipXXXのXXXから4つ分の番号をexportします。
#まずsuユーザに移行。Ubuntuセットアップ直後の方はrootパスワード未設定かも
ubuntu@kria:~$ su
Password:
root@kria:/home/ubuntu# echo 322 > /sys/class/gpio/export
root@kria:/home/ubuntu# echo 323 > /sys/class/gpio/export
root@kria:/home/ubuntu# echo 324 > /sys/class/gpio/export
root@kria:/home/ubuntu# echo 325 > /sys/class/gpio/export
root@kria:/home/ubuntu# ls /sys/class/gpio/
export gpio322 gpio323 gpio324 gpio325 gpiochip322 gpiochip326 gpiochip334 gpiochip508 unexport
#ためしに存在しない5bit目を有効化しようとするとしっかりエラーが出てくれる
root@kria:/home/ubuntu# echo 326 > /sys/class/gpio/export
bash: echo: write error: Device or resource busy
bitの対応は以下のようになっていました。1チャネル目と2チャネル目は番号詰めされるようです。
- gpio322 = GPIO0[0] = UFx_LED_tri_o[0] = UF1_LED
- gpio323 = GPIO0[1] = UFx_LED_tri_o[0] = UF2_LED
- gpio324 = GPIO1[0] = PUSH_SW_tri_i[0] = PMOD1_IO1_HDA11 = SW1
- gpio325 = GPIO1[1] = PUSH_SW_tri_i[1] = PMOD1_IO2_HDA12 = SW2
8-2. ShellでGPIO出力
オンボードユーザLEDを点灯させてみます。suユーザを忘れずに。
#出力に設定
root@kria:/home/ubuntu# echo out > /sys/class/gpio/gpio322/direction
root@kria:/home/ubuntu# echo out > /sys/class/gpio/gpio323/direction
#ためしにUF2_LEDを点灯させる。正論理なので1=Highで点灯
root@kria:/home/ubuntu# echo 1 > /sys/class/gpio/gpio323/value
UF2_LEDだけ点灯させてみました。点きましたね。
8-3. ShellでGPIO入力
Pmodに接続したプッシュスイッチを押してみます。suユーザを忘れずに。
#入力に設定
root@kria:/home/ubuntu# echo in > /sys/class/gpio/gpio324/direction
root@kria:/home/ubuntu# echo in > /sys/class/gpio/gpio325/direction
#SW1を押さない状態で実行
root@kria:/home/ubuntu# cat /sys/class/gpio/gpio324/value
1
#SW1を押した状態で実行
root@kria:/home/ubuntu# cat /sys/class/gpio/gpio324/value
0
スイッチはプルアップ抵抗接続なので負論理です。しっかり読み取れています。
8-4. PythonでGPIO入力変化通知(≒割込み)
GPIO入力変化通知はGPIOピン割込みのことだと思ってください。つまり、スイッチなどをポーリングで監視するのではなく、ハードウェアでGPIO入力ピン状態を監視し、設定した変化が起きたら、ユーザアプリに知らせて非同期イベント処理をさせることができます。
AXI-GPIOのIP設定で、割込み信号線を接続しました。これにより、デバイスツリー生成でGPIO割込みの設定が組み込まれ、GPIO入力変化イベント通知が利用できるようになります。
イベント通知の方法はコールバックハンドラなどいろいろありますが、SysFs GPIOではpoll(2)やepoll(7)システムコールに対応しているので、Pythonでepollを使ってみたいと思います。epoll監視対象のファイルは以下の入出力値を読み書きするvalueです。
/sys/class/gpio/gpioxxx/value
epoll(7)を使うための事前準備
epollに通知するイベントを有効にするために、SysFs-GPIOのトリガを先に設定します。トリガの種類は”none”, “rising”, “falling”, or “both”の4つです。
$ su
#スイッチ1を入力に設定し、立ち下がりエッジトリガを設定
root: echo in > /sys/class/gpio/gpio324/direction
root: echo falling > /sys/class/gpio/gpio324/edge
#スイッチ2を入力に設定し、立ち下がりエッジトリガを設定
root: echo in > /sys/class/gpio/gpio325/direction
root: echo falling > /sys/class/gpio/gpio325/edge
Pythonでepoll(7)の使い方
- 監視対象のファイルをopen
- epollオブジェクト生成
- epollに監視対象ディスクリプタとイベントを登録
- 監視対象ファイルをreadしてイベントクリア
- epoll.poll()でイベントブロッキング待ち
- ディスクリプタとイベントを見てイベント元を判定
#!/usr/bin/env python #epollのサンプル import select #GPIOディスクリプタとepollオブジェクトの作成 with open('/sys/class/gpio/gpioXXX/value',mode='r') as switch1, \ open('/sys/class/gpio/gpioYYY/value',mode='r') as switch2, \ select.epoll() as poll: #epollに監視対象ディスクリプタとイベント種別を登録 #監視対象が複数ある場合必要な回数登録 #イベントはフラグ値なので、複数ある場合はbitORする poll.register(switch1, select.EPOLLPRI) poll.register(switch2, select.EPOLLPRI) #GPIOファイルを読み出してイベントクリア switch1.seek(0) switch1.read() switch2.seek(0) switch2.read() #イベントを監視する。来るまでブロッキングする events = poll.poll() #イベントが発生したので発生元を調べる #eventsはタプルのリストで監視対象が複数ある場合、 #複数イベント来る場合があるので、ループでチェックする # events -> [(fd1, event1),(fd2, event2),....(fdn, eventn)] for fileno, event in events: #GPIOスイッチ1 if fileno == switch1.fileno(): # イベントクリア switch1.seek(0) switch1.read() # イベント種別確認 if False != event & select.EPOLLPRI: print('switch1 pushed!') else: print('switch1 pushed? no EPOLLPRI events.') #GPIOスイッチ2 elif fileno == switch2.fileno(): pass #以後省略
スイッチ押下通知でLEDを操作するPythonサンプル
上の要領でスイッチを押すとLEDが点灯するサンプルを作成しました。
- SW1を押すたびにUF1_LEDがトグルする
- SW2を押すたびにUF2_LEDがトグルする
- 10回押すと終了
- 注: sudoで実行しないといけないので、内容確認せず実行はしないでください
#!/usr/bin/env python #あらかじめsysfsのgpioをexportしておく # $su #<-rootユーザに移行 # #echo 322 > /sys/class/gpio/export # #echo 323 > /sys/class/gpio/export # #echo 324 > /sys/class/gpio/export # #echo 325 > /sys/class/gpio/export import select LED1 = 'gpio322' LED2 = 'gpio323' SW1 = 'gpio324' SW2 = 'gpio325' LED1_DIR_PATH = '/sys/class/gpio/' + LED1 + '/direction' LED2_DIR_PATH = '/sys/class/gpio/' + LED2 + '/direction' SW1_DIR_PATH = '/sys/class/gpio/' + SW1 + '/direction' SW2_DIR_PATH = '/sys/class/gpio/' + SW2 + '/direction' LED1_INT_EDGE_PATH = '/sys/class/gpio/' + LED1 + '/edge' LED2_INT_EDGE_PATH = '/sys/class/gpio/' + LED2 + '/edge' SW1_INT_EDGE_PATH = '/sys/class/gpio/' + SW1 + '/edge' SW2_INT_EDGE_PATH = '/sys/class/gpio/' + SW1 + '/edge' LED1_PATH = '/sys/class/gpio/' + LED1 + '/value' LED2_PATH = '/sys/class/gpio/' + LED2 + '/value' SW1_PATH = '/sys/class/gpio/' + SW1 + '/value' SW2_PATH = '/sys/class/gpio/' + SW2 + '/value' GPIO_DIR_OUT = b'out\n' GPIO_DIR_IN = b'in\n' GPIO_INT_EDGE_FALL = b'falling\n' GPIO_INT_EDGE_RISE = b'rising\n' GPIO_HIGH = b'1\n' GPIO_LOW = b'0\n' def setup_gpio(): #GPIO入出力設定 with open(LED1_DIR_PATH,mode='w+b',buffering=0) as led1_dir, \ open(LED2_DIR_PATH,mode='w+b',buffering=0) as led2_dir, \ open(SW1_DIR_PATH,mode='w+b',buffering=0) as switch1_dir,\ open(SW2_DIR_PATH,mode='w+b',buffering=0) as switch2_dir: led1_dir.seek(0); led1_dir.write(GPIO_DIR_OUT); led1_dir.seek(0) print('Set LED1 '+LED1_DIR_PATH+' = ' + led1_dir.read().decode('utf-8')) led2_dir.seek(0); led2_dir.write(GPIO_DIR_OUT); led2_dir.seek(0) print('Set LED2 '+LED2_DIR_PATH+' = ' + led2_dir.read().decode('utf-8')) switch1_dir.seek(0); switch1_dir.write(GPIO_DIR_IN); switch1_dir.seek(0) print('Set SW1 '+SW1_DIR_PATH+' = ' + switch1_dir.read().decode('utf-8')) switch2_dir.seek(0); switch2_dir.write(GPIO_DIR_IN); switch2_dir.seek(0) print('Set SW2 '+SW2_DIR_PATH+' = ' + switch2_dir.read().decode('utf-8')) #GPIO入力変化エッジ設定 with open(SW1_INT_EDGE_PATH,mode='w+b',buffering=0) as switch1_int_edge, \ open(SW2_INT_EDGE_PATH,mode='w+b',buffering=0) as switch2_int_edge: switch1_int_edge.seek(0); switch1_int_edge.write(GPIO_INT_EDGE_FALL) switch1_int_edge.seek(0) print('Set SW1 '+SW1_INT_EDGE_PATH+' = ' + switch1_int_edge.read().decode('utf-8')) switch2_int_edge.seek(0); switch2_int_edge.write(GPIO_INT_EDGE_FALL) switch2_int_edge.seek(0) print('Set SW2 '+SW2_INT_EDGE_PATH+' = ' + switch2_int_edge.read().decode('utf-8')) # LEDトグルする def toggle_led(led_fd): led_fd.seek(0) status = led_fd.read(); led_fd.seek(0) if GPIO_HIGH == status: led_fd.write(GPIO_LOW) print('toggle led -> OFF') elif GPIO_LOW == status: led_fd.write(GPIO_HIGH) print('toggle led -> ON') else: print('Err: LED GPIO') def main(): setup_gpio() #GPIO出力用のファイルはバイナリモードかつバッファなしでOpenする with open(LED1_PATH,mode='w+b',buffering=0) as led1, \ open(LED2_PATH,mode='w+b',buffering=0) as led2, \ open(SW1_PATH,mode='r') as switch1, \ open(SW2_PATH,mode='r') as switch2, \ select.epoll() as poll: # 初期化:LED消灯 led1.seek(0); led1.write(GPIO_LOW) led2.seek(0); led2.write(GPIO_LOW) # epollにディスクリプタとイベント種別を登録 poll.register(switch1, select.EPOLLPRI) poll.register(switch2, select.EPOLLPRI) # イベントをクリアする switch1.seek(0); switch1.read() switch2.seek(0); switch2.read() # イベントループ(10回で終了) for _ in range(10): #スイッチが押されるまでブロッキングして待つ print('wait switch events...') events = poll.poll() # イベントディスパッチ(振り分け) for fileno, event in events: if fileno == switch1.fileno(): # switch1イベントがキック # イベントクリア switch1.seek(0); switch1.read() # イベントを判別 if False != event & select.EPOLLPRI: print('switch1 pushed!') toggle_led(led1) else: print('switch1 pushed? no EPOLLPRI events.') elif fileno == switch2.fileno(): # switch2イベントがキック # イベントクリア switch2.seek(0); switch2.read() # イベントを判別 if False != event & select.EPOLLPRI: print('switch2 pushed!') toggle_led(led2) else: print('switch2 pushed? no EPOLLPRI events.') else: print('Err: unknown event.') print('\n') print('exit App.') if __name__ == "__main__": main()
ついでに最初にGPIOをexportするためのShellスクリプトも作ってみました。
#!/bin/bash # Note: Run as su user. # $ su # https://www.kernel.org/doc/Documentation/gpio/sysfs.txt # GPIO_NUM : $ ls /sys/class/gpio |grep gpiochip -> "gpiochip322" # GPIO_INOUT : 'in' or 'out' # GPIO_INT_EDGE : 'none' or 'rising' or 'falling' or 'both' GPIO_NUM=( '322' '323' '324' '325' ) GPIO_INOUT=( 'out' 'out' 'in' 'in' ) GPIO_INT_EDGE=('none' 'none' 'falling' 'falling') for i in ${!GPIO_NUM[@]} ; do echo ${GPIO_NUM[i]} > /sys/class/gpio/export echo ${GPIO_INOUT[i]} > /sys/class/gpio/gpio${GPIO_NUM[i]}/direction if [ 'in' = ${GPIO_INOUT[i]} ]; then echo ${GPIO_INT_EDGE[i]} > /sys/class/gpio/gpio${GPIO_NUM[i]}/edge echo "enable gpio${GPIO_NUM[i]} ${GPIO_INOUT[i]} ${GPIO_INT_EDGE[i]}" else echo "enable gpio${GPIO_NUM[i]} ${GPIO_INOUT[i]}" fi done