
Libreria di probe di debug SWD per core RISC-V RP2350. Fornisce halt/resume/step, accesso a registri e memoria, esecuzione di codice e tracciamento delle istruzioni su due fili GPIO utilizzando il bit-banging PIO.
Sonda di debug SWD per core RISC-V (Hazard3) RP2350. Una Pico2 esegue il debug di un'altra tramite due fili GPIO.
Circa l'80% del codice è stato fatto con vibe coding; Il readme è quasi completamente generato (tranne l'intera sezione del vibe-code-warning). Ho passato molte notti con l'oscilloscopio e la documentazione e ho realizzato un prototipo funzionante in grado di fare sba/read/write regs e comandi astratti e progbuf, il resto è stato fatto con claude code. I test sono una suite di test completa e uso il nucleo della libreria nei miei progetti, ma, come si dice, "hic sunt dracones". Ho anche letto il readme e il codice non sembrava avere nulla di sbagliato (e ho rimosso le parti errate/poco chiare).
Questo progetto è stato il mio caso di studio di vibecoding di un progetto più complesso che non capisco al 100% e per il quale non esiste codice esistente ovvio che possa essere "usato". È iniziato come ~1000 righe di codice che avevo scritto e conoscevo molto bene, leggendo la documentazione su rp2350, arm swd e riscv debug, catturando dati con oscilloscopio e openocd, poi decodificandoli e analizzando la sequenza di wakeup e poi i comandi read/write. Dopo averlo fatto funzionare l'ho dato a claude per trasformarlo in una libreria da usare in altri progetti, e poi l'ho costruito lentamente.
Dopo circa 3-4k righe di codice ho perso completamente il filo di ciò che stava succedendo, e non considererei più questo codice come scritto da me, ma aggiungere sempre più test sembrava "bello", o almeno rassicurante.
C'è stato un po' di gaslighting, in particolare quando ha frainteso dap_read_mem32 pensando che leggesse dalla RAM e non dal protocollo MEM-AP TAR/DRW/RDBUFF, il che ha portato a un'incredibile quantità di assurdità.
In generale direi che è stata un'esperienza orribile, anche se ci sono volute 10 ore per scrivere quasi 10000 righe di codice, non considero questo mio progetto, e non provo alcun senso di realizzazione o crescita.
Al contrario, usare l'AI per leggere tutta la documentazione (che è migliaia di pagine) e scrivere script utili per decodificare i dati dell'oscilloscopio, creare struct C impacchettate dai documenti e così via, è stato molto piacevole, e mi sono sentito bene dopo. Il momento in cui ho letto il primo registro e poi quando sono stato in grado di leggere la memoria tramite SBA mi sono sentito fantastico.
Il problema principale è il taste, quando scrivo codice sento se è buono o cattivo, mentre lo scrivo, so se è sbagliato, ma usando claude code mi desensibilizzo molto rapidamente e non riesco a distinguere, "si legge" OK, ma non so come si sente. In questo caso è successo quando il codice è cresciuto circa 4x, da 1k a 4k righe. E peggio di tutto, il mio modello mentale del codice è completamente svanito, e con esso la mia proprietà.
I token non hanno ragione o scopo, il che rende la lettura del codice ridicolmente difficile, perché ogni token può essere una totale assurdità. Quando si legge codice umano i simboli hanno uno scopo, qualcuno ha pensato "Metterò questo in una variabile, poi ne controllerò lo stato.", quindi fingo di essere loro e penso perché lo avrebbero scritto? Poco dopo capisco, perché sono umani e io sono umano. Ma i simboli dell'AI non hanno ragione, e peggio di tutto, sembrano tutti ingannevolmente corretti, quindi devo pensare 10 volte di più se sono sbagliati. Con qualsiasi codice umano (incluso il proprio) è abbastanza facile valutare quanto ci si possa fidare, ed è abbastanza coerente, con il codice AI, una funzione può essere molto meglio di quello che avresti scritto tu, e il codice 2 righe sotto può essere immondizia cargo culted che sembra incredibilmente buona, ma è strutturalmente sbagliata.
Alla fine direi di aver acquisito una buona comprensione dei fili, dei tempi e dei meccanismi di basso livello ap/dp, sba e progbuf, ma mi pento di non aver scritto tutto da solo, anche se ci avesse messo 10 volte tanto.
Odio fottutamente questa situazione.
E non posso fare a meno di provare disgusto e vergogna. È questa la programmazione ora? Spero davvero che sia una fase intermedia e che cambi in meglio, il problema è che non so cosa sia "meglio", sembra che per alcuni sia non scrivere codice, per altri non modellare il problema e per altri ancora non dover pensare. Per me non ne sono sicuro, voglio creare cose, e molte volte non voglio sapere qualcosa, ma voglio usarla, ad esempio il controller host USB rp2350, il modo in cui devi riarmare gli interrupt e il modo in cui il registro epx è condiviso è fastidiosissimo, probabilmente per buoni motivi, ma voglio solo usarlo per creare il mio driver CBI.
Immagino che la domanda sia: qual è la cosa che voglio creare? Perché puoi salire molto nella pila, dai registri del chip USB a CBI a UFI a FAT16 al sistema operativo del computer vecchia scuola che sto costruendo, ma perché fermarsi? fare gli schemi, i pcb, i file cad, magari inviarli automaticamente alla fabbrica? e poi spedirli a me? ma perché fermarsi? creare il mio negozio web, iniziare a vendere, creare una community, pubblicità, marketing, generare video di unboxing, magari qualche meme virale? elaborare gli ordini direttamente dalla fabbrica, on demand, se c'è un problema, è pronto con l'assistenza clienti.
Cosa faccio nel frattempo? Me ne sto sulla spiaggia? Odio la spiaggia.
Dove finisce?
PS: come ha detto il poeta: il prezzo per ottenere ciò che vuoi, è ottenere ciò che una volta volevi.
Application
|
rp2350.c RISC-V Debug Module (halt/resume/step, registers, memory, trace)
|
dap.c Debug Access Port (DP/AP registers, bank caching, MEM-AP)
|
swd_protocol.c SWD wire protocol (PIO bit-banging, packet encoding, retry)
|
swd.pio PIO state machine (4-cycle SWCLK, bidirectional SWDIO)
Ogni livello mantiene il proprio stato e comunica solo con il vicino.
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);
Entrambi gli hart (0 e 1) sono controllabili indipendentemente.
Non intrusivo tramite System Bus Access. Funziona mentre l'hart è in esecuzione.
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);
I trasferimenti a blocchi utilizzano l'auto-incremento SBA per le prestazioni.
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);
Carica nella SRAM di destinazione, verifica, imposta PC, riprende.
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);
Esegue passo passo le istruzioni tramite DCSR.step. ~5ms per istruzione senza cattura registro, ~80ms con cattura registro completa.
Esecuzione diretta di istruzioni RISC-V in contesto di debug:
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);
L'accesso CSR utilizza questo internamente poiché Hazard3 non supporta comandi CSR astratti.
add_subdirectory(lib/pico2-swd-riscv)
target_link_libraries(your_app pico2_swd_riscv)
Verbositù di debug (a tempo di compilazione):
set(PICO2_SWD_DEBUG_LEVEL 3) # 0=none, 1=warn, 2=info, 3=debug
SWD utilizza pacchetti di richiesta a 8 bit (start, APnDP, RnW, addr[3:2], parity, stop, park), ACK a 3 bit (OK=1, WAIT=2, FAULT=4) e fasi dati a 33 bit con parità. I cicli di turnaround gestiscono i cambi di direzione SWDIO. Le risposte WAIT vengono ritentate automaticamente (default: 5 tentativi, backoff di 100us).
Non standard: [15:12]=APSEL, [11:8]=0xD, [7:4]=bank, [0]=ctrlsel. Il 0xD nei bit[11:8] è richiesto ma non documentato.
Handshake a tre fasi tramite CSW del Bank 1: disattivazione (0x00000000), attivazione (0x00000001), configurazione completa (0x07FFFFC1). Risposta di stato prevista: 0x04010001.
GPR tramite comandi astratti (regno 0x1000+n, trasferimento a 32 bit). CSR tramite buffer di programma: salva s0, esegue csrr s0, <csr> o csrw <csr>, s0, legge/ripristina s0.
SBCS configurato con sbaccess=32bit e sbreadonaddr. Scrivere SBADDRESS0 attiva la lettura del bus; i dati sono immediatamente disponibili in SBDATA0. I trasferimenti a blocchi abilitano sbautoincrement per letture/scritture in streaming senza impostare l'indirizzo per ogni parola.
Legge DCSR tramite progbuf, imposta il bit step (bit 2), riprende l'hart. L'hart esegue un'istruzione e rientra in modalità debug. Cancella il bit step dopo.
MIT. Vedi LICENSE.