
Biblioteca de probe de depuração SWD para núcleos RP2350 RISC-V. Fornece halt/resume/step, acesso a registradores e memória, execução de código e rastreamento de instruções através de dois fios GPIO usando bit-banging PIO.
Sonda de depuração SWD para núcleos RP2350 RISC-V (Hazard3). Um Pico2 depura outro através de dois fios GPIO.
Cerca de 80% do código é vibe coded; o readme é quase totalmente gerado (exceto toda a seção de aviso de vibe code). Passei muitas noites com o osciloscópio e a documentação e fiz um protótipo funcional que conseguia fazer sba/ler/escrever registradores e executar comandos abstratos e progbuf, o resto foi feito com claude code. Os testes são bastante abrangentes (comprehensive test suite) e uso o núcleo da biblioteca nos meus próprios projetos, mas, como dizem, "hic sunt dracones". Também li o readme e o código e não notei nada de errado (e removi as partes erradas/confusas).
Este projeto foi meu estudo de caso de vibecoding de um projeto mais complicado que não entendo 100% e não há código existente óbvio que possa ser "usado". Começou como ~1000 linhas de código que eu mesmo escrevi e conhecia muito bem, lendo os documentos do rp2350, arm swd e riscv debug, capturando dados com osciloscópio e openocd, depois decodificando e analisando a sequência de wakeup e depois comandos de leitura/escrita. Depois que consegui fazer funcionar, entreguei ao claude para transformá-lo em uma biblioteca que eu pudesse usar em outros projetos, e então fui construindo lentamente.
Depois de cerca de 3‑4k linhas de código, perdi completamente o controle do que estava acontecendo, e não consideraria este código como algo que escrevi, mas adicionar mais e mais testes parecia "legal", ou pelo menos reconfortante.
Houve um certo gaslighting, particularmente quando ele entendeu mal o dap_read_mem32 pensando que estava lendo da RAM e não o protocolo MEM‑AP TAR/DRW/RDBUFF, o que levou a uma quantidade incrível de bobagem.
No geral, diria que foi uma experiência horrível, mesmo tendo levado 10 horas para escrever quase 10000 linhas de código, não considero este meu projeto, e não tenho nenhum senso de realização ou crescimento.
Em contraste, usar IA para ler toda a documentação (que são milhares de páginas) e escrever scripts úteis para decodificar os dados do osciloscópio, criar structs C compactadas a partir dos documentos, etc., foi muito bom, e me senti bem depois. No momento em que li o primeiro registrador e depois quando consegui ler memória via SBA, me senti incrível.
O principal problema é o "taste" (bom senso), quando escrevo código sinto se é bom ou ruim, enquanto escrevo, sei se está errado, mas usando claude code fico dessensibilizado muito rapidamente e simplesmente não consigo distinguir, ele "parece" OK, mas não sei como "se sente". Neste caso, aconteceu quando o código cresceu cerca de 4x, de 1k para 4k linhas. E pior de tudo, meu modelo mental do código desapareceu completamente, e junto com ele minha propriedade (ownership).
Os tokens não têm razão ou propósito, o que torna a leitura do código ridiculamente difícil, pois cada token pode ser completo absurdo. Ao ler código humano, os símbolos têm um propósito, alguém pensou "Vou colocar isso em uma variável, depois verificarei seu status.", então finjo que sou eles e penso por que eles teriam escrito isso? Logo depois entendo, pois são humanos e eu sou humano. Mas os símbolos da IA não têm razão, e pior de tudo, todos parecem enganosamente corretos, então tenho que pensar 10 vezes mais se está errado. Com qualquer código humano (incluindo o seu próprio), é bastante fácil avaliar o quanto você pode confiar nele, e é bastante consistente; com o código de IA, uma função pode ser muito melhor do que você teria escrito, e o código 2 linhas abaixo pode ser gunk de cargo cult que parece incrivelmente bom, mas está estruturalmente errado.
No final, diria que ganhei um bom entendimento dos fios, temporizações e da mecânica de baixo nível de ap/dp, sba e progbuf, mas me arrependo de não ter escrito tudo sozinho, mesmo que tivesse levado 10x mais tempo.
Eu odeio isso pra caramba.
E não consigo evitar, mas sentir nojo e vergonha. É isso que a programação é agora? Espero muito que isso seja algum estágio intermediário e que mude para melhor, o problema é que não sei o que é "melhor", parece que para alguns é não escrever o código, para outros é não modelar o problema e para terceiros é não precisar pensar. Para mim, não tenho certeza, quero fazer coisas, e muitas vezes não quero saber algo, mas quero usá-lo, por exemplo, o controlador de host USB rp2350, a forma como você tem que rearmar interrupções e a forma como o registrador epx é compartilhado é super irritante, por boas razões provavelmente, mas eu só quero usá-lo para fazer meu driver CBI.
Acho que a pergunta é: o que é a coisa que quero fazer? Porque você pode subir a pilha, desde os registradores do chip USB até CBI, UFI, FAT16, o sistema operacional do computador old school que estou fazendo, mas por que parar? fazer os esquemáticos, as PCBs, os arquivos CAD, talvez enviar automaticamente para a fábrica? e depois apenas enviar para mim? mas por que parar? fazer minha loja online, começar a vender, criar uma comunidade, anúncios, marketing, gerar alguns vídeos de unboxing, talvez alguns memes virais? processar os pedidos diretamente para a fábrica, sob demanda, se houver um problema, está pronto com suporte ao cliente.
O que eu faço enquanto isso? Sentar na praia? Odeio a praia.
Onde isso para?
PS: como disse o poeta: o preço de conseguir o que você quer, é conseguir o que você uma vez quis.
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)
Cada camada mantém seu próprio estado e se comunica apenas com seu vizinho.
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);
Ambos os harts (0 e 1) são controláveis independentemente.
Não intrusivo via Acesso ao Barramento do Sistema (System Bus Access). Funciona enquanto o hart está em execução.
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);
Transferências em bloco usam auto‑incremento SBA para desempenho.
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);
Envia para a SRAM de destino, verifica, define o PC, retoma.
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);
Executa passo a passo através de instruções via DCSR.step. ~5ms por instrução sem captura de registradores, ~80ms com captura completa de registradores.
Execução direta de instruções RISC-V no contexto de depuração:
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);
O acesso a CSR usa isso internamente, pois o Hazard3 não suporta comandos CSR abstratos.
add_subdirectory(lib/pico2-swd-riscv)
target_link_libraries(your_app pico2_swd_riscv)
Verbosidade de depuração (tempo de compilação):
set(PICO2_SWD_DEBUG_LEVEL 3) # 0=none, 1=warn, 2=info, 3=debug
SWD usa pacotes de requisição de 8 bits (start, APnDP, RnW, addr[3:2], parity, stop, park), ACK de 3 bits (OK=1, WAIT=2, FAULT=4) e fases de dados de 33 bits com paridade. Ciclos de turnaround lidam com mudanças de direção do SWDIO. Respostas WAIT são repetidas automaticamente (padrão: 5 tentativas, backoff de 100us).
Não padrão: [15:12]=APSEL, [11:8]=0xD, [7:4]=bank, [0]=ctrlsel. O 0xD nos bits[11:8] é necessário, mas não documentado.
Handshake de três fases através do Bank 1 CSW: desativar (0x00000000), ativar (0x00000001), configuração completa (0x07FFFFC1). Resposta de status esperada: 0x04010001.
GPRs via comandos abstratos (regno 0x1000+n, transferência de 32 bits). CSRs via buffer de programa: salvar s0, executar csrr s0, <csr> ou csrw <csr>, s0, ler/restaurar s0.
SBCS configurado com sbaccess=32bit e sbreadonaddr. Escrever SBADDRESS0 aciona a leitura do barramento; dados disponíveis imediatamente em SBDATA0. Transferências em bloco habilitam sbautoincrement para leituras/escritas em streaming sem configuração de endereço por palavra.
Ler DCSR via progbuf, definir o bit de passo (bit 2), retomar o hart. O hart executa uma instrução e reentra no modo de depuração. Limpar o bit de passo depois.
rp2350_execute_code com um stub)MIT. Veja LICENSE.