
SWD-Debug-Probe-Bibliothek für RP2350-RISC-V-Kerne. Bietet Halten/Fortsetzen/Schritt, Register- und Speicherzugriff, Codeausführung sowie Befehlstracing über zwei GPIO-Leitungen mittels PIO-Bit-Banging.
SWD-Debug-Probe für RP2350 RISC-V (Hazard3) Kerne. Ein Pico2 debuggt einen anderen über zwei GPIO-Leitungen.
Etwa 80 % des Codes sind Vibe-Code; die Readme ist fast vollständig generiert (außer dem gesamten Vibe-Code-Warnungsabschnitt). Ich habe viele Nächte mit dem Oszilloskop und der Dokumentation verbracht und einen funktionierenden Prototypen erstellt, der in der Lage war, SBA/Lesen/Schreiben von Registern sowie abstrakte Befehle und Progbuf auszuführen. Der Rest wurde mit Claude Code erledigt. Die Tests sind recht umfassend und ich verwende den Kern der Bibliothek in meinen eigenen Projekten, aber wie man so schön sagt: „hic sunt dracones“. Ich habe auch die Readme gelesen und der Code ist mir nicht falsch aufgefallen (und ich habe die falschen/unklaren Teile entfernt).
Dieses Projekt war meine Fallstudie zum Vibecoden eines komplizierteren Projekts, das ich nicht zu 100 % verstehe und für das es keinen offensichtlichen vorhandenen Code gibt, der „verwendet“ werden kann. Es begann mit ~1000 Zeilen, die ich selbst geschrieben hatte und sehr gut kannte, nachdem ich die RP2350-, ARM SWD- und RISC-V-Debug-Dokumentation gelesen, Daten mit dem Oszilloskop und OpenOCD erfasst, dekodiert und die Wakeup-Sequenz sowie die Lese-/Schreibbefehle analysiert hatte. Nachdem ich es zum Laufen gebracht hatte, gab ich es Claude, um daraus eine Bibliothek zu machen, die ich in anderen Projekten verwenden kann, und dann baute ich es langsam auf.
Nach etwa 3.000–4.000 Zeilen Code habe ich völlig den Überblick verloren, was vor sich geht, und ich würde diesen Code nicht als von mir geschrieben betrachten, aber das Hinzufügen von immer mehr Tests fühlte sich „nett“ an, oder zumindest beruhigend.
Es gab einiges an Gaslighting, insbesondere als es dap_read_mem32 falsch verstand und dachte, es würde aus dem RAM lesen und nicht das MEM-AP TAR/DRW/RDBUFF-Protokoll verwenden, was zu einer unglaublichen Menge an Unsinn führte.
Insgesamt würde ich sagen, es war eine schreckliche Erfahrung. Obwohl ich 10 Stunden gebraucht habe, um fast 10.000 Zeilen Code zu schreiben, betrachte ich dies nicht als mein Projekt, und ich habe kein Gefühl der Erfüllung oder des Wachstums.
Im Gegensatz dazu war es sehr schön, KI zu nutzen, um die gesamte Dokumentation (die tausende Seiten umfasst) zu lesen und hilfreiche Skripte zum Dekodieren der Oszilloskopdaten zu schreiben, gepackte C-Strukturen aus Dokumentationen zu erstellen usw. Ich habe mich danach gut gefühlt. In dem Moment, als ich das erste Register las und dann in der Lage war, Speicher über SBA zu lesen, fühlte ich mich großartig.
Das Hauptproblem ist der Geschmack: Wenn ich Code schreibe, fühle ich, ob er gut oder schlecht ist. Während ich ihn schreibe, weiß ich, ob er falsch ist. Aber mit Claude Code werde ich sehr schnell desensibilisiert und kann es einfach nicht sagen. Es „liest“ sich OK, aber ich weiß nicht, wie es sich anfühlt. In diesem Fall geschah es, als der Code um das Vierfache wuchs, von 1.000 auf 4.000 Zeilen. Und schlimmer noch: Mein mentales Modell des Codes ist vollständig verschwunden, und damit auch mein Besitzgefühl.
Die Tokens haben keinen Grund oder Zweck, was das Lesen von Code unglaublich schwierig macht, da jedes Token völliger Unsinn sein kann. Beim Lesen von menschlichem Code haben die Symbole einen Zweck. Jemand dachte: „Ich werde das in eine Variable stecken, später werde ich ihren Status überprüfen.“ Also stelle ich mir vor, ich wäre diese Person, und denke: Warum hätten sie das geschrieben? Kurz danach verstehe ich es, denn sie sind menschlich und ich bin menschlich. Aber die KI-Symbole haben keinen Grund, und schlimmer noch, sie sehen alle täuschend korrekt aus, also muss ich zehnmal härter darüber nachdenken, ob sie falsch sind. Bei jedem menschlichen Code (einschließlich Ihres eigenen) ist es ziemlich einfach einzuschätzen, wie sehr Sie ihm vertrauen können, und er ist ziemlich konsistent. Beim KI-Code kann eine Funktion viel besser sein, als Sie es geschrieben hätten, und der Code zwei Zeilen darunter kann ein Cargo-Cult-Mist sein, der unglaublich gut aussieht, aber strukturell falsch ist.
Am Ende würde ich sagen, dass ich ein gutes Verständnis der Leitungen, Timings und der tieferen AP/DP-Mechanismen, SBA und Progbuf gewonnen habe, aber ich bereue es, nicht das Ganze selbst geschrieben zu haben, selbst wenn es zehnmal so lange gedauert hätte.
Ich hasse das verdammt noch mal.
Und ich kann nicht anders, als Ekel und Scham zu empfinden. Ist das jetzt Programmieren? Ich hoffe wirklich, dass dies eine Zwischenstufe ist und sich zum Besseren ändert. Das Problem ist, dass ich nicht weiß, was „besser“ ist. Für manche Leute scheint es darum zu gehen, keinen Code zu schreiben, für andere, das Problem nicht zu modellieren, und für Dritte, nicht denken zu müssen. Für mich bin ich mir nicht sicher. Ich will Dinge machen, und oft möchte ich etwas nicht wissen, sondern es nur nutzen, z. B. den RP2350-USB-Host-Controller, die Art, wie man Interrupts neu aktivieren muss und wie das EPX-Register gemeinsam genutzt wird, ist super nervig, wahrscheinlich aus guten Gründen, aber ich möchte es einfach nur nutzen, um meinen CBI-Treiber zu schreiben.
Ich denke, die Frage ist, was das Ding ist, das ich machen will, denn man kann weit die Hierarchie hinaufgehen: von den USB-Chip-Registern zu CBI, zu UFI, zu FAT16, zum Betriebssystem des alten Schulcomputers, den ich baue. Aber warum aufhören? Mach die Schaltpläne, die Leiterplatten, die CAD-Dateien, schicke es vielleicht automatisch an die Fabrik und dann direkt zu mir? Aber warum aufhören? Mach meinen Webshop, fang an zu verkaufen, baue eine Community auf, schalte Anzeigen, Marketing, generiere ein paar Unboxing-Videos, vielleicht ein paar virale Memes? Verarbeite die Bestellungen direkt an die Fabrik, auf Abruf, wenn es ein Problem gibt, ist der Kundenservice bereit.
Was mache ich in der Zwischenzeit? Am Strand sitzen? Ich hasse den Strand.
Wo hört es auf?
PS: Wie der Dichter sagte: Der Preis dafür, das zu bekommen, was man will, ist das zu bekommen, was man einmal wollte.
Application
|
rp2350.c RISC-V-Debug-Modul (Anhalten/Fortsetzen/Schritt, Register, Speicher, Trace)
|
dap.c Debug Access Port (DP/AP-Register, Bank-Caching, MEM-AP)
|
swd_protocol.c SWD-Leitungsprotokoll (PIO-Bit-Banging, Paketkodierung, Wiederholung)
|
swd.pio PIO-Zustandsmaschine (4-Takt SWCLK, bidirektional SWDIO)
Jede Schicht verwaltet ihren eigenen Zustand und kommuniziert nur mit ihrem Nachbarn.
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);
Beide Harts (0 und 1) sind unabhängig steuerbar.
Nicht-intrusiv über Systembus-Zugriff. Funktioniert während der Hart läuft.
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);
Blocktransfers verwenden SBA-Autoinkrement für Leistung.
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);
Lädt ins Ziel-SRAM hoch, verifiziert, setzt PC, setzt fort.
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);
Einzelschritte durch Befehle mittels DCSR.step. ~5ms pro Befehl ohne Registererfassung, ~80ms mit vollständiger Registererfassung.
Direkte RISC-V-Befehlsausführung im Debug-Kontext:
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-Zugriff verwendet dies intern, da Hazard3 keine abstrakten CSR-Befehle unterstützt.
add_subdirectory(lib/pico2-swd-riscv)
target_link_libraries(your_app pico2_swd_riscv)
Debug-Ausführlichkeit (Kompilierzeit):
set(PICO2_SWD_DEBUG_LEVEL 3) # 0=keine, 1=Warnung, 2=Info, 3=Debug
SWD verwendet 8-Bit-Anforderungspakete (Start, APnDP, RnW, Adresse[3:2], Parität, Stop, Park), 3-Bit-ACK (OK=1, WAIT=2, FAULT=4) und 33-Bit-Datenphasen mit Parität. Turnaround-Zyklen handhaben Richtungswechsel von SWDIO. WAIT-Antworten werden automatisch wiederholt (Standard: 5 Wiederholungen, 100µs Backoff).
Nicht standardmäßig: [15:12]=APSEL, [11:8]=0xD, [7:4]=Bank, [0]=ctrlsel. Das 0xD in Bits[11:8] ist erforderlich, aber undokumentiert.
Drei-Phasen-Handshake über Bank 1 CSW: deaktivieren (0x00000000), aktivieren (0x00000001), Vollkonfiguration (0x07FFFFC1). Erwartete Statusantwort: 0x04010001.
GPRs über abstrakte Befehle (regno 0x1000+n, 32-Bit-Transfer). CSRs über Programmpuffer: s0 speichern, csrr s0, <csr> oder csrw <csr>, s0 ausführen, s0 lesen/wiederherstellen.
SBCS konfiguriert mit sbaccess=32bit und sbreadonaddr. Das Schreiben von SBADDRESS0 löst den Bus-Lesevorgang aus; Daten sind sofort in SBDATA0 verfügbar. Blocktransfers aktivieren sbautoincrement für Streaming-Lese-/Schreibvorgänge ohne Adresseinrichtung pro Wort.
DCSR über Progbuf lesen, Step-Bit (Bit 2) setzen, Hart fortsetzen. Hart führt einen Befehl aus und kehrt in den Debug-Modus zurück. Step-Bit danach löschen.
MIT. Siehe LICENSE.