用于 RP2350 RISC-V(Hazard3)内核的 SWD 调试探针。一个 Pico2 通过两根 GPIO 线调试另一个。
大约 80% 的代码是灵能编码的;自述文件几乎完全由 AI 生成(除了整个灵能编码警告部分)。我花了很多个夜晚,带着示波器和文档,制作了一个能工作的原型,能够进行 SBA/读写寄存器、执行抽象命令和程序缓冲,其余部分是用 Claude Code 完成的。测试相当全面,我在自己的项目中使用了库的核心部分,但是,正如他们所说,“这里有龙”。我也阅读了自述文件和代码,没有发现明显问题(并删除了错误/不清晰的部分)。
这个项目是我对灵能编码更复杂项目的一个案例研究,这个项目我并没有 100% 理解,而且没有明显现成的代码可以“使用”。它始于约 1000 行我亲手编写并且非常了解的代码,通过阅读 RP2350、ARM SWD 和 RISC-V 调试文档,用示波器和 OpenOCD 捕获数据,然后解码和分析唤醒序列以及读/写命令。在我让它工作后,我把它交给 Claude,让它变成一个库以便在其他项目中使用,然后我慢慢构建它。
大约在 3000-4000 行代码后,我完全失去了跟踪,我不会把这看作是我自己编写的代码,但添加越来越多的测试感觉“不错”,或者说至少令人安心。
存在一些误导,尤其是当它误解了 dap_read_mem32,以为它是在读取 RAM,而不是 MEM-AP 的 TAR/DRW/RDBUFF 协议,这导致了大量无意义的代码。
总的来说,我觉得这是一次糟糕的体验,即使只花了 10 个小时编写了近 10000 行代码,我也不认为这是自己的项目,而且没有任何成就感或成长感。
相比之下,使用 AI 阅读所有文档(成千上万页)并编写有用的脚本来解码示波器数据、从文档创建打包的 C 结构体等,感觉非常好,之后我也确实感到满意。当我第一次读取寄存器,然后能够通过 SBA 读取内存时,我感觉棒极了。
主要问题在于“品味”,当我编写代码时,我能感觉到它是好是坏,我知道它是否错了,但使用 Claude Code 后,我很快变得麻木,就是分不清,它“读起来”还行,但我不知道它感觉如何。在这个案例中,当代码规模从 1000 行增长到 4000 行(大约 4 倍)时,这种情况就发生了。更糟的是,我对代码的心理模型完全消失了,随之消失的还有我的所有权。
这些符号没有理由或目的,这让阅读代码变得极其困难,因为每个符号都可能是完全无意义的。阅读人类代码时,符号是有目的的,有人想过“我会把这个放在一个变量里,稍后检查它的状态”,所以我假装自己是那个人,思考他们为什么会这样写?很快我就明白了,因为他们是人类,我也是人类。但 AI 符号没有理由,更糟的是,它们看起来都像是正确的,所以我要花十倍的努力去判断它是否错了。对于任何人类代码(包括你自己的),很容易判断你能信任它多少,而且它相当一致;对于 AI 代码,一个函数可能比你写的要好得多,而下面两行代码可能是盲目复制的垃圾,看起来非常好,但结构上是错的。
最后,我认为我对线路、时序以及底层 AP/DP 机制、SBA 和程序缓冲有了很好的理解,但我后悔没有亲自编写整件事,即使那样会花十倍的时间。
我他妈讨厌这个。
我只能感到厌恶和羞愧。编程现在就是这样吗?我真的希望这只是某个中间阶段,未来会变得更好,问题是我不知道“更好”是什么样子,对一些人来说是不用写代码,对另一些人来说是不用建模问题,对第三类人来说是不用思考。对我而言,我不确定,我确实想制造东西,很多时候我不想了解某些东西,但我想使用它,例如 RP2350 USB 主机控制器,那种需要重新开启中断的方式以及 EPX 寄存器共享的方式非常烦人,可能是有充分理由的,但我只想用它来做我的 CBI 驱动程序。
我想问题在于我想制造什么,因为你可以一直往上层走,从 USB 芯片寄存器到 CBI 到 UFI 到 FAT16 到我在制作的老式电脑的操作系统,但为什么要停在这里?制作原理图、PCB、CAD 文件,也许自动发送到工厂?然后直接寄给我?但为什么不继续?建立我的网店,开始销售,建立社区,投放广告,营销,制作一些开箱视频,也许一些病毒式传播的 meme?直接处理订单到工厂,按需生产,如果有问题,客服随时准备。
在此期间我做什么?坐在沙滩上?我讨厌沙滩。
在哪里停下来?
附:正如诗人所说:得到你想要的东西的代价,是得到你曾经想要的东西。
应用程序
|
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 位请求数据包(开始、APnDP、RnW、地址[3:2]、奇偶校验、停止、驻留),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 的三阶段握手:停用(0x00000000)、激活(0x00000001)、完整配置(0x07FFFFC1)。预期状态响应:0x04010001。
通用寄存器通过抽象命令(regno 0x1000+n,32 位传输)。CSR 通过程序缓冲:保存 s0,执行 csrr s0, <csr> 或 csrw <csr>, s0,读取/恢复 s0。
SBCS 配置为 sbaccess=32bit 和 sbreadonaddr。写入 SBADDRESS0 触发总线读取;数据立即可在 SBDATA0 中获得。块传输启用 sbautoincrement,实现流式读写,无需为每个字设置地址。
通过程序缓冲读取 DCSR,设置步进位(位 2),恢复核心。核心执行一条指令并重新进入调试模式。之后清除步进位。
MIT。参见 LICENSE。