Skip to content
This is the development version of the documentation. It may change before the next release. See 0.14.x for the latest release.

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) などの可搬ロジック. ハードウェアには Port trait (コマンド層) 越しにのみアクセスし, unsafe を含まない. ホストテストと autd3-rs-firmware-emulator からも同じコードが使われる.
  • board/ (autd3-cpu): 実機ターゲット (armv7r-none-eabi) の staticlib. レジスタ定義・BSP・ETHSW / GMAC のドライバ・Port trait の実機実装・列挙と受信振り分け (udp/node.rs)・PTP の状態機械 (udp/ptp.rs)・main・panic handler を持ち, unsafe はここに閉じ込める.

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 実書き換え
用途 ツール
実機ビルド 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 は以下を行う.

  1. cargo xtask cpu gen-param で FPGA の params.svh から fw/src/fpga_params.rs (と wire/src/fpga_params.rs) を生成する.
  2. board/ を armv7r-none-eabi ターゲットでビルドする. アプリの staticlib (libautd3_cpu.a) とローダの実行ファイル (autd3-cpu-loader) ができる.
  3. rust-lld でリンカスクリプト platform/autd3-cpu.ld を用いてアプリをリンクする.
  4. llvm-objcopy でローダとアプリをそれぞれバイナリにし, ローダを先頭, アプリをスロット A に置いて build/autd3-cpu.bin に合成する.
  5. スロット A のヘッダにアプリの長さと CRC32 を書き込む.

ローダ (board/src/bin/loader.rs)・スタートアップ (board/src/startup.rs)・シリアルフラッシュドライバ (board/src/sflash.rs) も Rust で書かれている. ローダはブート ROM が BTCM に展開して実行するプログラムで, 起動するスロットを選んで ATCM にコピーする. スタートアップは board/src/lib.rs の main を呼び, 以後の初期化とメインループを行う.

cargo xtask cpu flash は tools/firmware-writer に --cpu build/autd3-cpu.bin を渡して呼ぶ. writer は J-Link を JTAG 接続し, autd3-cpu.bin を 0x30000000 へロードする. 書き込み後は電源を入れ直して起動する. デバイスは R7S910018_R4F として認識される.

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 のサーボを検証する.

ホストは送るものがあるときに, 各デバイスへ 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 は 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 ( 周期) から ELC → MTU → PPG を経て, 立ち上がりが 境界に乗る 周期のパルスを作る.

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 は以下を行う.

  1. 次に同期パルスが立ち上がるデバイス時刻 (現在時刻から 以上先の最初の 境界) を求める.

    • 同期パルスがまだ動いていない場合は ERR_SYNC_NOT_READY を返す
  2. デバイス時刻がその立ち上がりの 前を過ぎるまで待つ (最長 ).

    • FPGA はフラグを受けたあと最初の立ち上がりで SYS_TIME をセットするので, 1 つ手前の立ち上がりより前にフラグを立てると SYS_TIME が 進む
  3. 求めた立ち上がりの時刻を FPGA 内部の SYS_TIME の単位 () へ変換し, ADDR_SYNC_TIME_* へ書き込む.

  4. SYNC_SET フラグを立て, FPGA の Synchronizer が SYS_TIME をセットするのを待つ.

  5. デバイス時刻を読み直し, 書き込んだ立ち上がりより手前であることを確かめる.

    • フラグが立ち上がりより後に届くと SYS_TIME が 遅れる
    • 手前でなければ ERR_SYNC_MISSED を返す. やり直しはしない (受け取ったホストが送り直す)

CPU ファームは遷移モードと繰り返し回数の組み合わせも, SysTime 遷移の時刻も検査せず, そのまま FPGA へ書く. FPGA は, 無限ループ (REP == 0xFFFF) のバンクへはどの遷移モードでも即時に切り替える. 有限ループのバンクへは SyncIdx / SysTime / Gpio の条件を待って切り替え, Immediate / Ext では切り替わらずに待ち続ける. SysTime の時刻が過ぎていれば即時に切り替える.