CPU ファームウェア
This content is not available in your language yet.
CPU ボード (AP-RZT-01, Alpha Project) は Renesas RZ/T1 (Arm Cortex-R4F) を搭載している. これの役割は, ホストから UDP (IPv6 リンクローカル) で受け取ったフレームを解釈し, FPGA 内の BRAM へ書き込むことである. RZ/T1 内蔵の Ethernet スイッチ (ETHSW) の 2 ポートを使い, デバイスをデイジーチェーンで接続する.
ソースは firmware/cpu/ 以下にあり, no_std Rust で記述されている.
ファームウェア本体は 2 つのクレートに分かれており, ホストとの通信の取り決め (コマンド ID・ペイロードやヘッダのレイアウト・エラーコード・既定値など) はクライアントと共有するクレート wire/ (autd3-cpu-wire) に置かれている.
fw/(autd3-cpu-fw): プロトコル解釈・コマンド処理・FPGA 書き込み, および IPv6 / UDP / ICMPv6 の解析と組み立て (net), PTP のメッセージとサーボ (ptp) などの可搬ロジック. ハードウェアにはPorttrait (コマンド層) 越しにのみアクセスし,unsafeを含まない. ホストテストとautd3-rs-firmware-emulatorからも同じコードが使われる.board/(autd3-cpu): 実機ターゲット (armv7r-none-eabi) の staticlib. レジスタ定義・BSP・ETHSW / GMAC のドライバ・Porttrait の実機実装・列挙と受信振り分け (udp/node.rs)・PTP の状態機械 (udp/ptp.rs)・main・panic handler を持ち,unsafeはここに閉じ込める.
開発環境構築
Section titled “開発環境構築”CPU ファームウェアに関する作業はすべて autd3-sdk/ ディレクトリ内で cargo xtask 経由で実行する.
cargo xtask cpu build # 実機用 .bin を生成cargo xtask cpu flash # build 後, firmware-writer (J-Link) で実機へ書き込むcargo xtask cpu test # ホスト上の単体テスト (cargo test -p autd3-cpu-fw)cargo xtask cpu test --loom # 受信割り込みとメインループの FIFO の受け渡しを loom でモデル検査するcargo xtask cpu lint # clippy (fw + board)cargo xtask cpu format # rustfmt --check (fw + board)cargo xtask cpu format --fix # rustfmt 実書き換え必要なツール
Section titled “必要なツール”| 用途 | ツール |
|---|---|
| 実機ビルド | Rust ツールチェイン + rustup target add armv7r-none-eabi, rustup component add llvm-tools (rust-lld, llvm-objcopy) |
| 実機書き込み | J-Link Commander (JLinkExe) |
| ホストテスト | Rust ツールチェイン (cargo test) |
| lint / format | clippy, rustfmt |
JLinkExe が PATH に無い場合は環境変数 JLINK でパスを指定できる.
cargo xtask cpu build は以下を行う.
cargo xtask cpu gen-paramで FPGA のparams.svhからfw/src/fpga_params.rs(とwire/src/fpga_params.rs) を生成する.board/をarmv7r-none-eabiターゲットでビルドする. アプリの staticlib (libautd3_cpu.a) とローダの実行ファイル (autd3-cpu-loader) ができる.rust-lldでリンカスクリプトplatform/autd3-cpu.ldを用いてアプリをリンクする.llvm-objcopyでローダとアプリをそれぞれバイナリにし, ローダを先頭, アプリをスロット A に置いてbuild/autd3-cpu.binに合成する.- スロット A のヘッダにアプリの長さと CRC32 を書き込む.
ローダ (board/src/bin/loader.rs)・スタートアップ (board/src/startup.rs)・シリアルフラッシュドライバ (board/src/sflash.rs) も Rust で書かれている.
ローダはブート ROM が BTCM に展開して実行するプログラムで, 起動するスロットを選んで ATCM にコピーする.
スタートアップは board/src/lib.rs の main を呼び, 以後の初期化とメインループを行う.
実機書き込み
Section titled “実機書き込み”cargo xtask cpu flash は tools/firmware-writer に --cpu build/autd3-cpu.bin を渡して呼ぶ.
writer は J-Link を JTAG 接続し, autd3-cpu.bin を 0x30000000 へロードする. 書き込み後は電源を入れ直して起動する.
デバイスは R7S910018_R4F として認識される.
ホストテスト
Section titled “ホストテスト”fw/ は Port trait (fpga_write / fpga_read / memory_barrier など) 以外のハードウェア依存を持たない.
cargo xtask cpu test では fw/src/test_utils/mock.rs の MockPort がこれを置き換え, FPGA への書き込みを記録する.
fw/src/app と fw/src/cmd の各モジュールのテストがプロトコル層 (Cpu::recv_frame / Cpu::process_one) と各コマンドの振る舞いを, fw/src/net と fw/src/ptp のテストがパケットの解析・組み立てと PTP のサーボを検証する.
フレーム処理
Section titled “フレーム処理”ホストは送るものがあるときに, 各デバイスへ 1 つの UDP データグラム (デバイス側ポート 44336) を送る.
データグラムは 4 byte のヘッダ (版数・種別・メッセージ ID) の後ろに最大 1448 byte のフレームを持つ.
送るものが無い間は, TransportOption::heartbeat_interval を指定して開いた Client からフレームの代わりに生存確認 (Heartbeat) が周期的に届く (read_state も 1 回送る).
受信フレームは先頭が予約 (1 byte, ホストは 0 を送り, デバイスは読まない), cmd (1 byte), exec_at (4 byte) で, 残り (最大 1442 byte) がペイロードである.
exec_at が 0 でなければ, メインループはデバイス時刻がその時刻 (µs) に達するまでそのフレームの処理を待つ.
ペイロードの長さはデータグラムの長さだけで決まり, ペイロードの中に長さのフィールドは無い.
デバイスは受け取ったペイロードを長さごとコマンド層 (Cpu::recv_frame) へ渡し, 各コマンドはその長さを検査する (固定長のコマンドは完全一致, 可変長のデータはヘッダの後ろの残り全部).
各フレームの扱いを終えた時点で, status (エラーコード, 1 byte)・読み出し値 (最大 40 byte) を載せた返信を 1 回送る.
返信にはこのほか, デバイスの状態フラグ・ID・デバイスの時刻が載る. 返信の先頭 1 byte は予約 (0) である.
ホストは返信をデータグラムのヘッダのメッセージ ID (msg_id) だけで送ったフレームに対応づける.
デバイスは届いたフレームを届いた順にすべて処理する. シーケンス番号による欠落検出・順序保護・重複排除は持たないので, 途中のフレームが欠けても後続のフレームはそのまま処理され, 同じフレームが 2 回届けば 2 回処理する. 前のフレームの成否に依存するフレームは, 前のフレームの返信を待ってから送るのがホスト側 (呼び出し側) の責務である.
受信割り込みはフレームを深さ 8 のリングバッファ (FIFO. 同時に積めるのは 7 フレーム) に積むだけで抜け, メインループが処理して返信する.
コマンドを受信割り込みの中で処理する経路は無い.
フレームと Heartbeat のうち受信割り込みの中で返信するのは Heartbeat だけで, その返信は直前に処理したフレームの結果 (スロットに残した返信) を写す.
Heartbeat への返信は割り込みの中で返るので, メインループが長い処理で止まっていても返り続ける.
FIFO に積んだフレームはメインループが処理した後に, 割り込みを禁止した区間で返信する.
メインループは待ちを挟まずに FIFO を見続けるので, FIFO を通すことによる遅延は 1 フレームあたり 10 us 未満である (実測).
FIFO が満杯の場合, フレームは捨てられ (fifo_drop に数える), 返信も送らない.
返信が来ないため, クライアント側では Timeout になり, 呼び出し側がコマンドを送り直す (クライアントは 7 フレームまでしか同時に送らないので, 通常は溢れない).
handle_frame はコマンドを実行し, エラーがあればエラーコードを返信の status に載せる.
コードはデバイス側に保持せず, 回数も数えない (エラーの回数はホストが返信の status から数える).
| コード | 名前 | 意味 |
|---|---|---|
0x00 |
ERR_NONE |
エラーなし |
0x01 |
ERR_UNKNOWN_CMD |
未知のコマンド |
0x02 |
ERR_INVALID_PAYLOAD |
ペイロードの長さ・オフセットが不正 |
0x03 |
(欠番) | 再利用しない |
0x04 |
ERR_INVALID_SILENCER_SETTING |
Silencer の設定が厳格モードの条件を満たさない |
0x05 |
(欠番) | Firmware v0.14.x までの ERR_INVALID_TRANSITION_MODE. 遷移モードと繰り返し回数の組み合わせは検査しない |
0x06 |
(欠番) | Firmware v0.14.x までの ERR_MISS_TRANSITION_TIME. SysTime 遷移の時刻は検査しない |
0x07 |
ERR_FPGA_TIMEOUT |
FPGA が制御フラグを期限内にクリアしない |
0x08 |
ERR_SYNC_NOT_READY |
同期パルスがまだ動いていない (時刻合わせ前) |
0x09 |
(欠番) | Firmware v0.9.0 までの ERR_INVALID_SYNC0_CYCLE. 同期パルスの周期はファームが決めるので発生しない |
0x0A |
ERR_UPDATE_NOT_STARTED |
更新セッションが開いていない (UpdateBegin が必要) |
0x0B |
ERR_UPDATE_IMAGE_INVALID |
書き込んだイメージの読み戻し CRC32 が一致しない |
0x0C |
ERR_UPDATE_FLASH |
シリアルフラッシュの消去・書き込み・読み出しが失敗した |
0x0D |
ERR_UPDATE_NOT_COMMITTED |
Commit 済みのイメージが無い |
0x0E |
ERR_UPDATE_NOTHING_TO_CONFIRM |
確定の対象 (今回起動したイメージ) が不明, または書き直されている |
0x0F |
ERR_UPDATE_UNSUPPORTED |
稼働中の FPGA が自身のコンフィギュレーションフラッシュを書けない |
0x10 |
ERR_FPGA_UPDATE_IN_PROGRESS |
FPGA 更新のロック中に出力系コマンドを受けた |
0x12 |
ERR_UPDATE_ACTIVATING |
Reboot 受理後のリセット待ちの間に更新の開始を受けた |
0x14 |
ERR_SYNC_MISSED |
Synchronize の書き込みが同期パルスの立ち上がりに間に合わなかった. 送り直せばよい |
0x15 |
ERR_FAILSAFE_CONDITION_ACTIVE |
ReleaseFailsafe を受けたが, フェイルセーフの発火条件がまだ成り立っている |
FPGA への書き込み
Section titled “FPGA への書き込み”FPGA は CS1 空間 (0x44000000) にマップされた Port trait の fpga_write / fpga_read (実機実装は board/src/port.rs の HwPort) がこの領域への volatile アクセスを担う.
fpga::write(select, addr, value) は select (BRAM 選択, 2 bit) と addr (14 bit) を連結してアドレスを作る.
詳細は Memory を参照されたい.
設定値の反映は制御フラグ経由のハンドシェイクで行う.
set_and_wait_update(flag) が ADDR_CTL_FLAG の該当ビットを立て, FPGA の Controller がそれをクリアするまでポーリングする.
FPGA の同期入力 (SYNC_IN ピン) には, ETHSW のタイマから作った同期パルスを入れる.
タイマの SYNCOUT (
ETHSW のタイマはデバイス間で PTP (IEEE 1588, L2, E2E, two-step) によって揃える (状態機械は board/src/udp/ptp.rs, メッセージとサーボは fw/src/ptp).
以下の周期・閾値・回数は既定値であり, SetCpuConfig で変えられる.
- ID 0 のデバイスが grandmaster (GM) になる. ホストは
Clientを開くときに (TransportOption::sync_on_openが真のとき) GM にだけ時刻を送る (SetTime. 送る値は 0 で, デバイスの時刻は開いた時点から数える). GM 以外はSetTimeをNotGrandmasterで拒否する - GM は
ごとに下流ポートへ Sync/Follow_Upを送り,Delay_ReqにDelay_Respで答える. 中継する各デバイスの ETHSW が Transparent Clock として滞留時間をcorrectionFieldに足す - 他のデバイス (slave) は上流ポートから届く
Syncと自分のDelay_Reqから GM とのずれを求め, 固定小数点の PI サーボでタイマの周波数を補正する. ずれがを超えるとタイマをステップする - ずれが
未満の状態が 64 回続くとロックとみなし, 同期パルスを張り直す. ステップするとロックを外して同期パルスを止める - PTP の処理は受信割り込みと 1 ms のタイマ割り込みだけで行うので, メインループが止まっても (フラッシュの消去など) 同期は保たれる
Synchronize は以下を行う.
-
次に同期パルスが立ち上がるデバイス時刻 (現在時刻から
以上先の最初の 境界) を求める. - 同期パルスがまだ動いていない場合は
ERR_SYNC_NOT_READYを返す
- 同期パルスがまだ動いていない場合は
-
デバイス時刻がその立ち上がりの
前を過ぎるまで待つ (最長 ). - FPGA はフラグを受けたあと最初の立ち上がりで
SYS_TIMEをセットするので, 1 つ手前の立ち上がりより前にフラグを立てるとSYS_TIMEが進む
- FPGA はフラグを受けたあと最初の立ち上がりで
-
求めた立ち上がりの時刻を FPGA 内部の
SYS_TIMEの単位 () へ変換し, ADDR_SYNC_TIME_*へ書き込む. -
SYNC_SETフラグを立て, FPGA の Synchronizer がSYS_TIMEをセットするのを待つ. -
デバイス時刻を読み直し, 書き込んだ立ち上がりより手前であることを確かめる.
- フラグが立ち上がりより後に届くと
SYS_TIMEが遅れる - 手前でなければ
ERR_SYNC_MISSEDを返す. やり直しはしない (受け取ったホストが送り直す)
- フラグが立ち上がりより後に届くと
CPU ファームは遷移モードと繰り返し回数の組み合わせも, SysTime 遷移の時刻も検査せず, そのまま FPGA へ書く.
FPGA は, 無限ループ (REP == 0xFFFF) のバンクへはどの遷移モードでも即時に切り替える.
有限ループのバンクへは SyncIdx / SysTime / Gpio の条件を待って切り替え, Immediate / Ext では切り替わらずに待ち続ける.
SysTime の時刻が過ぎていれば即時に切り替える.