Intel FPGA を扱うとき、手元にある USB-Blaster が足りなかったり、実験用にもう 1 本ほしいと思うことがあります。
そのようなときに便利なのが、RP2040 を使って USB-Blaster 互換デバイスを作る方法です。
今回は、GitHub 上で公開されている thisiseth/pico-usb-blaster をベースにして、Seeed XIAO RP2040 で動作する XIAO 版 USB-Blaster を作成しました。最終的に、Quartus から MAX 10 デバイスを認識し、実際に書き込みまでできるところまで確認できました。
この記事では、どこでつまずいたか、何を修正したか、最終的にどうやって動作確認できたかをまとめます。
元にしたリポジトリ
ベースにしたのは以下のプロジェクトです。
thisiseth/pico-usb-blaster
このプロジェクトは Raspberry Pi Pico を前提としており、RP2040 を USB-Blaster 互換デバイスとして動かすものです。ただし、そのままでは Seeed XIAO RP2040 には持っていけない部分 がありました。
何がそのままでは動かなかったのか
最初は「GPIO の割り当てだけ変更すれば動くだろう」と考えていました。しかし、実際にはそこまで単純ではありませんでした。
問題は主に次の 2 点でした。
1. blaster.c が Pico 前提の GPIO 実装になっている
元の実装は、GPIO が連番で並んでいることを前提にしていたり、mask による一括操作を前提にしていました。Raspberry Pi Pico では都合が良くても、XIAO RP2040 で任意の GPIO に信号を割り当てたい場合には、この前提が邪魔になります。
そのため、XIAO 版では blaster.c を 任意 GPIO に対応した形で書き直す 必要がありました。
2. vendor control request の受け口が環境に合っていなかった
最初は USB としては見えているのに、Quartus から実際の通信が始まらない状態に悩まされました。jtagconfig --debug では USB-Blaster として見えるのに、JTAG 信号は出ない、という状態です。
調べていくと、USB の vendor request に対する処理を tud_control_request_cb() ではなく tud_vendor_control_xfer_cb() で受ける必要がある ことが分かりました。ここが合っていないと、ホスト側からの vendor request が STALL してしまい、その先の通信に進みません。
今回の XIAO RP2040 側のピン割り当て
今回使用したピン割り当ては以下の通りです。
- TCK = GPIO2
- TMS = GPIO3
- nCE = GPIO4
- nCS = GPIO6
- TDI = GPIO7
- TDO = GPIO26
- DATAOUT / nSTATUS = GPIO27
JTAG 用途として主に重要なのは TCK / TMS / TDI / TDO / GND ですが、元実装の流れに合わせて nCE, nCS, DATAOUT/nSTATUS も割り当てています。
途中での切り分け
今回かなり役に立ったのは、USB 側と JTAG 側を分けて切り分けたことです。
最初の段階では、
- USB として列挙される
jtagconfig --debugでも USB-Blaster として見える- しかし JTAG 信号が出ない
という状態でした。
そこで、main.c にデバッグ用 GPIO を仕込んで、
main()に到達しているか- mount しているか
- control request が来ているか
- bulk transfer が来ているか
を段階ごとに確認しました。
その結果、
- mount までは OK
- しかし vendor control request が来ていない、または処理できていない
ことが分かりました。さらに Linux 側で usbmon を見ることで、vendor request が STALL している ことも確認できました。
ここで main.c の USB control request の処理を見直し、tud_vendor_control_xfer_cb() に変更したところ、control / bulk ともに通るようになりました。
最後の詰まりどころ
USB 通信が通ったあとも、まだ JTAG チェーンの認識には失敗していました。
原因は blaster.c の shift() 処理でした。元の考え方のままだと、条件によって TDO と DATAOUT/nSTATUS を切り替えて読んでいましたが、JTAG 用途では常に TDO を読むべきでした。
この部分を修正して、JTAG shift 時には常に TDO をサンプリングするようにしたところ、ようやくチェーン認識に成功しました。
最終結果
最終的には、jtagconfig --debug で以下のようにデバイスを認識できました。
1) USB Blaster [2-2.8]
(JTAG Server Version 20.1.1 Build 720 11/11/2020 SJ Standard Edition)
0318A0DD 10M04S(A|C) (IR=10)
Captured DR after reset = (0318A0DD) [32]
Captured IR after reset = (155) [10]
Captured Bypass after reset = (0) [1]
Captured Bypass chain = (0) [1]
JTAG clock speed 6 MHz
この状態まで来たあと、Quartus Programmer から 実機への書き込みも成功 しました。
変更点の要点
今回の XIAO 版で重要だった変更点をまとめると、次の通りです。
usb_descriptors.c は元のままでよい
最初は descriptor 側を疑いましたが、結論としては GitHub 版そのままで問題ありませんでした。
main.c は vendor control transfer の受け方を修正する
main.c では、vendor control request を TUD の汎用 control callback ではなく、tud_vendor_control_xfer_cb() で受けるように変更しました。
blaster.c は XIAO 向けに実質書き直し
- 任意 GPIO に対応
- 連番 GPIO 前提を排除
- 一括 mask 操作依存を排除
- JTAG shift 時は 常に TDO を読む
このあたりがポイントでした。
使い方
使い方としては、元の pico-usb-blaster のリポジトリに対して、
src/blaster.hsrc/blaster.csrc/main.c
を XIAO 版に置き換え、usb_descriptors.c はそのまま使います。
ビルドは通常通りです。
mkdir -p build
cd build
cmake ..
make pico_usb_blaster
生成された UF2 を XIAO RP2040 に書き込めば動作します。
まとめ
今回の移植で感じたのは、「USB として見える」ことと「USB-Blaster として実際に使える」ことは別だということです。最初は descriptor や VID/PID の問題に見えても、実際には control request の受け方や、JTAG shift の細かな実装差が本質でした。
XIAO RP2040 は小型で扱いやすく、手元に余っていることも多いので、ちょっとした USB-Blaster 互換器として使えるとかなり便利です。同じように XIAO RP2040 で pico-usb-blaster を動かしたい方の参考になれば幸いです。
補足
- ターゲット FPGA との GND 共通 は必須
- 電圧レベル互換は要確認
- JTAG 配線はできるだけ短くする
- 今回は JTAG 用途での動作確認 が中心で、非 JTAG モードの詳細確認は行っていません
参考
ダウンロード
XIAO RP2040 用 USB-Blaster(ビルド済みUF2+ソース一式)
すぐ使う(おすすめ)
- XIAO RP2040 の BOOT ボタンを押しながら USB 接続
- 「RPI-RP2」ドライブが表示される
pico_usb_blaster_xiao_v1.uf2をコピー
これだけで USB-Blaster として認識されます。
ソースからビルド
src フォルダを元の pico-usb-blaster に上書きしてビルドしてください。
