
2026/02/19 0:06
**HN投稿:無人トンネルにおけるAM放送用FPGAウォッチドッグの形式検証**
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
改良版概要
本プロジェクトは、Red Pitaya STEMlab 125‑10 をベースにした 12チャネル AM ラジオ放送システム を提供し、無人トンネル環境での緊急警報を目的としています。各チャネルは FPGA ベースの数値制御発振器(NCO)を用いて 500 kHz から 1.7 MHz の動的搬送周波数 を生成し、最大 12 チャネル同時に 505–1605 kHz の搬送波 を出力します。設計は MVC アーキテクチャを採用しており、Rust/Tauri バックエンド がハードウェアを制御し、JavaScript フロントエンド はステートレスな UI を提供し、FPGA の状態確認(SCPI コマンドを
/dev/mem 経由で送信)後にのみ更新されます。ハードウェア機能としては、チャネルごとの NCO、16 384 サンプル音声バッファ(約 5 kHz)を備えた AM モデレーター、活性チャネルに応じて動的にパワーをスケールする制御、および GUI のハートビートが 5 秒 停止した場合に RF 出力を停止させるウォッチドッグがあります。SymbiYosys + Z3 を用いた形式検証では、縮小パラメータ(CLK_FREQ=1、TIMEOUT_SEC=5)で 14 の安全プロパティ(さらに 6 のカバレッジシナリオ)が証明されています。SCPI サーバー(am_scpi_server.py)は TCP/SCPI コマンドを FPGA レジスタ書き込みへ変換し、音声再生ループ(axi_audio_sequence_loop.py)は 3 本の WAV ファイル(アラーム・パート 1・パート 2)を約 5 kHz で BRAM に循環的にロードします。デプロイ手順は、リポジトリをクローンし、Node.js + Rust/Tauri で GUI をビルド、Red Pitaya 上で /dev/xdevcfg 経由でビットストリームをロード、SCPI サーバーと音声ループを起動、その後ローカル GUI を実行して接続・放送するという流れです。推奨使用は 1–2 チャネル で優れた信号品質、4–5 チャネル まで安定した受信、8 チャネル以上 は増幅が必要または弱い信号を許容する設定になります。完全な SCPI コマンドセットには *IDN?、STATUS?、OUTPUT:STATE ON/OFF、CHx:FREQ <Hz>、CHx:OUTPUT ON/OFF、SOURCE:MSG n、ウォッチドッグ制御(WATCHDOG:RESET、WATCHDOG:STATUS?)が含まれます。トラブルシューティングでは、ビットストリームの再ロード、GUI 接続問題、音声ループ未起動、無効な WAV ファイル、弱い信号への調整、ウォッチドッグ発火などを網羅しています。将来の改善点としては、より大きな DDR/SD 音声バッファ、ギャップを排除する DMA ベースのストリーミング、すべて 12 チャネル用の外部 RF アンプ、さらに FPGA の状態空間最適化が挙げられます。本文
AMラジオブレークインシステム
Red Pitaya FPGAを用いた無人トンネルでの緊急アラート送信に対応する12チャンネルAMラジオ放送システム。
特徴
| Feature | Status |
|---|---|
| 12同時キャリア周波数 | ✅ |
| 実行時周波数設定(ハードウェア変更不要) | ✅ |
| 事前録音済み音声によるAM変調 | ✅ |
| 動的電力スケーリング | ✅ |
| MVC構成 (Rust + JavaScript) | ✅ |
| イベント駆動型pub/sub(イベントバス) | ✅ |
| ステートレスUI – デバイスが真実源 | ✅ |
| ネットワークポーリング & 自動再接続 | ✅ |
| フェイルセーフハードウェアウォッチドッグ (5 sタイムアウト) | ✅ |
| 形式検証 (14プロパティ、6カバー、すべて証明済み) | ✅ |
アーキテクチャ
ソフトウェア層
- フレームワーク:Rust(Tauriバックエンド)+JavaScriptフロントエンド
- MVC + イベント駆動pub/sub
- Model (
):model.rs
がTCP/SCPI、デバイス状態、500 msポーリング、指数バックオフ付き自動再接続を担当。NetworkManager - View (
,view.js
): ステートレス – 確認済みデバイス状態のみ描画し、ハードウェア状態を仮定しない。index.html - Controller (
): ユーザー入力を処理し、イベントをバスへ発行。controller.js - Event Bus (
,event_bus.rs
): 中央通信チャネル;RustがTauriブリッジ経由でJSへイベントを送出。event_bus.js - State Machine (
): IDLE → ARMING → ARMED → STARTING → BROADCASTING → STOPPING。中間状態により無効遷移を防止。state_machine.rs - 真実源:デバイス(ソフトウェアではなく)。UIはハードウェア確認後のみ更新される。
- Model (
ハードウェア層
- NCO: 12個の数値制御発振器が505–1605 kHzのキャリア周波数を生成。
- AMモジュレーター: 各キャリアに音声ソースを組み合わせる。
- 動的スケーリング: 有効チャンネル数に応じて出力電力が調整される。
- オーディオバッファ: BRAMに16,384サンプルの事前録音済み緊急メッセージを格納(~5 kHz再生速度)。AXIオーディオローダーで実行時ロード可能。
- ウォッチドッグタイマー (
): GUIハートビート停止時に5 s以内にRF出力を停止し保持;オペレーターリセットで復旧。wd.v - SCPIサーバー (
): Red Pitaya上で動作し、テキストコマンドを解析して周波数を位相増分へ変換しam_scpi_server.py
経由でFPGAレジスタを書き込む。/dev/mem
信号生成フロー
GUIクリック → invoke("set_frequency") → model.rs が "FREQ:CH1 700000" をTCP経由送信 → am_scpi_server.py が (700000 × 2³²) / 125 MHz を計算し phase_inc に変換 → /dev/mem 経由でFPGAレジスタへ書き込み → NCO がキャリアを生成 → AMモジュレーション → RF出力
形式検証
- ウォッチドッグタイマーは境界モデルチェックとk‑インデクション(SymbiYosys + Z3 SMTソルバー)で数学的に正しさが証明。
- 14の安全プロパティ(すべてPASS)+6カバースナリオ(すべて到達)。
プロパティ概要
| # | Property | Guarantee |
|---|---|---|
| 1 | リセットで全クリア | |
| 2 | ハートビートがトリガーを防止 | ハートビートはカウンタをリセットし、triggered & warning を消去 |
| 3 | 早期トリガー禁止 | |
| … | 他のプロパティ省略 | – |
カバースナリオ
- トリガーファイア(カウンタがタイムアウトに到達)。
- ウォーニングのみ。
- 正確なタイムアウト境界。
- ラストセカンドハートビート。
でトリガーからの回復。force_reset- ウォーニング→トリガーライフサイクル。
実行例
cd fpga/formal/ sby -f wd.sby
期待出力:
SBY [wd_prove] DONE (PASS, rc=0) summary: successful proof by k-induction. SBY [wd_cover] DONE (PASS, rc=0) summary: 6/6 cover statements reached.
ファイル構成
ugl_am_radio/ ├── gui/ │ ├── src/ │ │ ├── index.html # HTML + CSS │ │ └── js/ │ │ ├── event_bus.js # フロントエンド pub/sub + Tauri リスナー │ │ ├── model.js # Rust API呼び出し(ステートレス) │ │ ├── view.js # DOM描画 │ │ └── controller.js # イベントハンドラ │ └── src-tauri/src/ │ ├── main.rs # エントリポイント │ ├── model.rs # NetworkManager + DeviceState │ ├── commands.rs # Tauri コマンドブリッジ │ ├── event_bus.rs # Rust pub/sub + Tauri emit │ ├── state_machine.rs # 放送状態遷移 │ └── config.rs # 定数 ├── fpga/ │ ├── formal/ │ │ ├── wd.v # ウォッチドッグ+14プロパティ │ │ ├── wd.sby # SymbiYosys設定 │ │ └── README.md # 形式検証ドキュメント │ ├── am_mod.sv # AM変調モジュール │ ├── am_radio_ctrl.v # 12チャンネルAMラジオコントローラ │ ├── axi_audio_buffer.v # AXI音声バッファ(BRAM再生用) │ ├── nco_sin.v # 数値制御発振器 │ ├── red_pitaya_top.sv # トップレベルFPGA統合 │ ├── sine_lut_4096.mem # 4096ポイント正弦 LUT │ └── watchdog_timer.v # ウォッチドッグタイマー(本番用) ├── am_scpi_server.py # SCPIサーバー(Red Pitaya上実行) ├── axi_audio_sequence_loop.py # 音声シーケンスローダー(アラーム→パート1→パート2ループ) ├── alarm_fast.wav # アラーム音 ├── 0009_part1.wav # 緊急メッセージパート 1 ├── 0009_part2_fast.wav # パート 2 ├── requirements.txt # Python依存(numpy) └── README.md
チャンネル周波数(デフォルト)
| Channel | Frequency |
|---|---|
| CH1 | 505 kHz |
| CH2 | 605 kHz |
| CH3 | 705 kHz |
| CH4 | 805 kHz |
| CH5 | 905 kHz |
| CH6 | 1005 kHz |
| CH7 | 1105 kHz |
| CH8 | 1205 kHz |
| CH9 | 1305 kHz |
| CH10 | 1405 kHz |
| CH11 | 1505 kHz |
| CH12 | 1605 kHz |
実行時に500–1700 kHzの範囲で調整可能。
ウォッチドッグ安全設計
- 標準ウォッチドッグ:デバイスがハング → タイマーオーバーフロー → デバイス再起動。
- 本プロジェクト用:GUIが停止するとカウンタがタイムアウトし、RF出力を即座に停止。操作員リセットまで無効化状態を維持。
理由:無人トンネルでのラジオ送信機を再起動することは危険であり、RF出力再開には必ず人的確認が必要。
安全マージン:GUIが500 msごとにポーリングし、5 sタイムアウト ⇒ 10連続ハートビート欠如でトリガー発生。
パフォーマンスメモ
| Channels | 信号強度 | 推奨 |
|---|---|---|
| 1–2 | 優秀 | ✅ 最良品質 |
| 3–4 | 良好 | ✅ 最大推奨 |
| 5–8 | 公平 | ⚠️ アンプが必要かも |
| 9–12 | 弱い | ⚠️ 範囲が短くなる |
おすすめ:信頼性確保のため、最大4–5チャンネル使用。
SCPIコマンドリファレンス
| Command | Description |
|---|---|
| デバイス識別情報取得 |
| 完全デバイス状態確認 |
| マスター放送有効化/無効化 |
| CH1周波数設定(Hz) |
| CH1の有効化/無効化 |
| 音声メッセージ選択 |
| ウォッチドッグタイマーリセット |
| ウォッチドッグ状態照会 |
使用手順
- Red PitayaへSSH
ssh root@<RED_PITAYA_IP> - FPGAビットストリームロード(最初のターミナル)
cat /root/red_pitaya_top.bit > /dev/xdevcfg - SCPIサーバー起動(同じまたは別ターミナル)
python3 /root/am_scpi_server.py - 音声ループ開始(第三ターミナル)
sudo python3 /root/axi_audio_sequence_loop.py - GUI実行:ローカルで起動、またはビルド済みバイナリを使用。
(macOS)やgui/src-tauri/target/release/bundle/macos/*.app
(Windows)。.exe
インストール
1. リポジトリクローン
git clone https://github.com/Park07/amradio.git cd amradio/ugl_am_radio
2. GUIビルド
macOS
# Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source $HOME/.cargo/env # Xcode CLIツール xcode-select --install # Homebrew経由でNode.js brew install node # ビルド cd gui npm install npm run build
Windows (PowerShell)
# Rust rustup-init.exe (https://rustup.rs) # Visual Studio Build Tools # 「C++によるデスクトップ開発」を選択してインストール # Node.js LTS # https://nodejs.org からダウンロード # インストール確認 rustc --version cargo --version node --version npm --version # ビルド cd gui npm install npm run build
3. Red Pitaya設定
scp am_scpi_server.py root@<RED_PITAYA_IP>:/root/ scp axi_audio_sequence_loop.py root@<RED_PITAYA_IP>:/root/ scp alarm_fast.wav 0009_part1.wav 0009_part2_fast.wav root@<RED_PITAYA_IP>:/root/ scp fpga/red_pitaya_top.bit root@<RED_PITAYA_IP>:/root/
4. Python環境(Red Pitaya)
pip install numpy # 音声ローダーのみ必要
テスト
- Rust単体テスト
cd gui/src-tauri cargo test - 形式検証 – 上記 Formal Verification セクション参照。
- モックサーバー(GUIのみ)
GUIで# ターミナル1 — モックFPGA起動 cd gui npm run mock # ターミナル2 — GUI起動 cd gui npm run dev
に接続。127.0.0.1:5000
トラブルシューティング
| Problem | Solution |
|---|---|
| 電源投入後RF出力がない | ビットストリームを再ロード: |
| GUIが接続できない | IP確認、SCPIサーバー実行中かチェック |
| 音声なしでキャリアだけ | 音声ループ起動: |
| ファイルにRIFF IDが無い | で再変換 |
| 信号が弱い | 有効チャンネルを減らす(最大4–5) |
| 接続タイムアウト | ネットワーク・Red Pitaya電源確認 |
| ウォッチドッグが予期せず作動 | ネットワーク安定性チェック、必要ならタイムアウト延長 |
が見つからない(Windows) | 「C++によるデスクトップ開発」付き Visual Studio Build Tools をインストール |
が見つからない | Rust インストール後ターミナル再起動 |
が見つからない | Node.js インストール後ターミナル再起動 |
| xcode‑select エラー(macOS) | を実行 |
今後の開発者向け
- 現状:全シグナルチェーンが機能;GUI切断時にRF出力停止。
- 改善点:オーディオバッファを16,384サンプル以上拡張、DMAギャップ解消、4–5チャンネル超えで外部アンプ対応。
- 主要ファイル:
,model.rs
,am_scpi_server.py
.am_radio_ctrl.v - 開発フロー:GUI →
; Rustバックエンドは自動再コンパイル;FPGA → Vivado → 新ビットストリーム → SDカードへコピー。npm run dev
作者
| Name | Contribution |
|---|---|
| William Park | ソフトウェア設計(GUI、MVC、イベントバス)、ウォッチドッグタイマー、形式検証 |
| Bowen Deng | FPGA開発(NCO、AM変調、RF出力) |
謝辞
- UGL Limited – プロジェクトスポンサー
- University of New South Wales – EPIプログラム
- Robert Mahood (UGL) – エンジニアリング監督
- Andrew Wong (UNSW) – 学術監督
最終版:2026年2月13日