
SWD debug probe library for RP2350 RISC-V cores. Provides halt/resume/step, register and memory access, code execution, and instruction tracing over two GPIO wires using PIO bit-banging.
RP2350 RISC-V (Hazard3) コア向け SWD デバッグプローブ。1台の Pico2 が別の Pico2 を 2 本の GPIO ワイヤでデバッグします。
コードの約 80% はバイブコーディングです。README は(バイブコード警告セクション全体を除いて)ほぼ生成されています。私は多くの夜をオシロスコープとドキュメントとともに過ごし、SBA の読み書き、レジスタの読み書き、抽象コマンド、プログラムバッファを実行できるプロトタイプを動作させました。残りは Claude Code で行われました。テストはかなり包括的なテストスイートであり、私は自身のプロジェクトでライブラリの中核を使用していますが、「ここにドラゴンがいる」という言葉通りです。README を読んだりコードを確認しても問題は見つかりませんでした(間違った部分や不明瞭な部分は削除しました)。
このプロジェクトは、私が 100% 理解していない、かつ明らかに「流用できる」既存コードがない、より複雑なプロジェクトをバイブコーディングするケーススタディでした。最初は私が書き、よく知っている約 1000 行のコードから始まり、RP2350、ARM SWD、RISC-V デバッグのドキュメントを読み、オシロスコープと OpenOCD でデータをキャプチャし、それをデコードしてウェイクアップシーケンス、そして読み書きコマンドを分析しました。動作するようになった後、Claude に渡して他のプロジェクトで使えるライブラリにし、その後少しずつ作り上げていきました。
約 3〜4k 行のコード以降、何が起こっているのか完全に把握できなくなりました。これは自分が書いたコードとは考えていませんが、テストを追加するたびに「良い感じ」、少なくとも安心感がありました。
特に、dap_read_mem32 を RAM からの読み取りではなく MEM-AP TAR/DRW/RDBUFF プロトコルの一部だと誤解していた点で、ガスライティングがあり、それが信じられないほどのナンセンスを生み出しました。
全体的に、これはひどい経験だったと言わざるを得ません。10000 行近いコードを書くのに 10 時間かかったにもかかわらず、これを自分のプロジェクトとは思えず、達成感や成長も感じていません。
対照的に、AI を使ってすべてのドキュメント(数千ページ)を読み、オシロスコープデータをデコードするスクリプトを書いたり、ドキュメントからパックされた C 構造体を作成したりするのは非常に快適で、良い気分になりました。最初のレジスタを読み取った瞬間、そして SBA でメモリを読み取れるようになった瞬間は素晴らしい気分でした。
主な問題は「味覚」です。コードを書くとき、それが良いか悪いか感じ取れます。書いている最中に間違っているとわかりますが、Claude Code を使うとすぐに鈍感になり、それがわかりません。「読める」けど、どう感じるかはわかりません。この場合は、コードが 4 倍(1k→4k 行)に増えたときに起こりました。さらに悪いことに、コードのメンタルモデルが完全に失われ、それに伴い所有権も失われました。
トークンには理由や目的がありません。 そのため、コードを読むのが途方もなく難しくなります。各トークンが完全にナンセンスかもしれません。人間のコードを読むとき、シンボルには目的があります。「これを変数に入れて、後でステータスを確認しよう」と誰かが考えたのです。そのため、私はその人になったつもりで、なぜこれを書いたのかと考えます。すぐに理解できます。なぜなら人間同士だからです。しかし AI のシンボルには理由がなく、さらに悪いことにすべてが欺瞞的に正しく見えるため、間違っているかどうかを 10 倍注意深く考えなければなりません。人間のコード(自分のものも含む)では、どの程度信頼できるかを測るのは簡単で、一貫性があります。AI のコードでは、ある関数は自分が書いたものよりはるかに優れている一方、その 2 行下のコードは非常に良く見えるが構造的に間違っている、カルゴカルトのゴミである可能性があります。
結局、ワイヤ、タイミング、低レベルの AP/DP メカニック、SBA、プログラムバッファについての理解は深まりましたが、すべてを自分で書かなかったことを後悔しています。たとえ 10 倍の時間がかかったとしてもです。
本当に嫌です。
そして、嫌悪感と恥ずかしさを感じずにはいられません。これが今のプログラミングなのでしょうか?これが中間段階で、より良い方向に変わることを願っています。問題は、何が「より良い」かわからないことです。一部の人にとってはコードを書かないこと、他の人にとっては問題をモデル化しないこと、また別の人にとっては考えなくて済むことのようです。私にとってはわかりません。ものを作りたいという気持ちはありますが、多くの場合、何かを知りたくはなく、使いたいだけです。例えば、RP2350 USB ホストコントローラの割り込み再アーム方法や EPX レジスタの共有方法は非常に面倒ですが、おそらく正当な理由があります。しかし、私は自分の CBI ドライバを作るためにそれを使いたいだけです。
結局のところ、私が作りたいものは何か、という問いになります。USB チップレジスタから CBI、UFI、FAT16、そして私が作っている昔のコンピュータの OS へとスタックを上っていくことができます。しかしなぜそこで止めるのでしょう?回路図、PCB、CAD ファイルを作り、自動で工場に送り、そして自分に送ってもらう?しかしなぜそこで止める?ウェブショップを作り、販売を始め、コミュニティを作り、広告、マーケティング、アンボクシング動画を生成し、バイラルミームをいくつか?注文を直接工場に処理し、オンデマンドで、問題があればカスタマーサポートを用意する。
その間、私は何をするのか?ビーチに座る?ビーチは嫌いです。
どこで止まるのか?
追伸:詩人が言ったように、欲しいものを手に入れる代償は、かつて欲しかったものを手に入れることです。
アプリケーション
|
rp2350.c RISC-V デバッグモジュール(停止/再開/ステップ、レジスタ、メモリ、トレース)
|
dap.c デバッグアクセスポート(DP/AP レジスタ、バンクキャッシュ、MEM-AP)
|
swd_protocol.c SWD ワイヤプロトコル(PIO ビットバング、パケットエンコード、リトライ)
|
swd.pio PIO ステートマシン(4サイクル SWCLK、双方向 SWDIO)
各層は自身の状態を保持し、隣接する層とのみ通信します。
swd_config_t config = swd_config_default();
config.pin_swclk = 2;
config.pin_swdio = 3;
swd_target_t *target = swd_target_create(&config);
swd_connect(target);
rp2350_init(target);
rp2350_halt(target, 0);
swd_result_t pc = rp2350_read_pc(target, 0);
rp2350_resume(target, 0);
swd_target_destroy(target);
rp2350_halt(target, 0);
rp2350_step(target, 0);
rp2350_resume(target, 0);
rp2350_reset(target, 0, true);
swd_result_t pc = rp2350_read_pc(target, 0);
rp2350_write_pc(target, 0, 0x20000000);
swd_result_t val = rp2350_read_reg(target, 0, 5);
rp2350_write_reg(target, 0, 5, 0xDEADBEEF);
uint32_t regs[32];
rp2350_read_all_regs(target, 0, regs);
swd_result_t csr = rp2350_read_csr(target, 0, 0x300);
rp2350_write_csr(target, 0, 0x300, value);
両方のハート(0 と 1)は独立して制御できます。
システムバスアクセスによる非侵入型アクセス。ハート動作中でも動作します。
swd_result_t val = rp2350_read_mem32(target, 0x20000000);
rp2350_write_mem32(target, 0x20000000, 0xDEADBEEF);
rp2350_read_mem16(target, addr);
rp2350_write_mem8(target, addr, byte);
uint32_t buf[256];
rp2350_read_mem_block(target, 0x20000000, buf, 256);
rp2350_write_mem_block(target, 0x20000000, buf, 256);
ブロック転送ではパフォーマンスのために SBA 自動インクリメントを使用します。
const uint32_t program[] = {
0x200415b7, // lui a1, 0x20040
0xabcd0537, // lui a0, 0xabcd0
0x00a5a223, // sw a0, 4(a1)
0x0000006f, // j . (loop)
};
rp2350_execute_code(target, 0, 0x20000000, program, 4);
ターゲットの SRAM にアップロード、検証、PC 設定、再開を行います。
bool on_instruction(const trace_record_t *rec, void *ctx) {
printf("0x%08x: 0x%08x\n", rec->pc, rec->instruction);
return true;
}
int traced = rp2350_trace(target, 0, 100, on_instruction, NULL, false);
DCSR.step を使用して命令をシングルステップ実行。レジスタキャプチャなしで約 5ms/命令、フルレジスタキャプチャで約 80ms。
デバッグコンテキストでの直接 RISC-V 命令実行:
uint32_t progbuf[] = {
0x34202473, // csrr s0, mcause
0x00100073 // ebreak
};
rp2350_execute_progbuf(target, 0, progbuf, 2);
swd_result_t mcause = rp2350_read_reg(target, 0, 8);
CSR アクセスは、Hazard3 が抽象 CSR コマンドをサポートしていないため、内部的にこれを使用します。
add_subdirectory(lib/pico2-swd-riscv)
target_link_libraries(your_app pico2_swd_riscv)
デバッグ出力レベル(コンパイル時):
set(PICO2_SWD_DEBUG_LEVEL 3) # 0=なし, 1=警告, 2=情報, 3=デバッグ
SWD は 8 ビットリクエストパケット(start, APnDP, RnW, addr[3:2], parity, stop, park)、3 ビット ACK(OK=1, WAIT=2, FAULT=4)、およびパリティ付き 33 ビットデータフェーズを使用します。ターンアラウンドサイクルで SWDIO の方向を切り替えます。WAIT 応答は自動的にリトライします(デフォルト: 5 回リトライ、100us バックオフ)。
非標準:[15:12]=APSEL, [11:8]=0xD, [7:4]=bank, [0]=ctrlsel。ビット[11:8]の 0xD は必須ですが、文書化されていません。
Bank 1 CSW を介した 3 フェーズハンドシェイク:非アクティブ化 (0x00000000)、アクティブ化 (0x00000001)、フル設定 (0x07FFFFC1)。期待されるステータス応答:0x04010001。
GPR:抽象コマンド経由(regno 0x1000+n、32 ビット転送)。CSR:プログラムバッファ経由:s0 を保存、csrr s0, <csr> または csrw <csr>, s0 を実行、s0 を読み取り/復元。
SBCS を sbaccess=32bit かつ sbreadonaddr で設定。SBADDRESS0 に書き込むとバス読み取りがトリガーされ、データは SBDATA0 で即座に利用可能。ブロック転送では sbautoincrement を有効にし、ワード単位のアドレス設定なしでストリーミング読み書きを行います。
プログラムバッファ経由で DCSR を読み取り、step ビット(ビット 2)を設定し、ハートを再開。ハートは 1 命令を実行し、デバッグモードに再突入。その後 step ビットをクリア。
MIT。詳細は LICENSE を参照。