1. 课程导入
调试,说白了就是让芯片在运行时能被我们“看穿”。我刚开始接触RISC-V调试时,总觉得这东西离验证很远——直到有一次,一个bug在仿真里死活复现不了,最后靠JTAG单步才抓到问题。从那以后,我对调试子系统的态度就变了。
这一章,我们先搭个框架。把调试子系统在SoC里到底长什么样、怎么切换模式、JTAG和DM是什么关系,一次性说清楚。
1.1 调试子系统在SoC中的位置
蜂鸟E203的调试子系统,不是孤立的模块。它嵌在Core和总线之间,像一个“监听者”。
我习惯把它的位置画成下面这样:
从图上你能看到:JTAG从芯片外部接入,经过DM(调试模块),再分别连到Core和总线。DM是核心枢纽,它决定了调试器能不能“看到”CPU内部。
关键点:DM不直接挂在总线上,而是通过专用调试接口与Core交互。这样做的好处是——调试时不会干扰正常的总线事务。
1.2 调试模式与运行模式切换
E203的Core有两种工作状态:运行模式和调试模式。说白了,就是“正常干活”和“停下来被检查”的区别。
切换过程是这样的:
| 步骤 | 动作 | 说明 |
|---|---|---|
| 1 | 调试器通过JTAG发送请求 | 比如写DM的control寄存器 |
| 2 | DM向Core发出halt信号 | Core在指令边界停下 |
| 3 | Core进入调试模式 | PC冻结,流水线暂停 |
| 4 | 调试器读写GPR/CSR/内存 | 通过DM的抽象命令 |
| 5 | 调试器发送resume请求 | Core退出调试模式,继续执行 |
嗯,这里要注意:Core进入调试模式后,中断响应行为会变化。我记得有一次调试时,发现中断没被响应,查了半天才发现Core停在调试模式里,中断被屏蔽了。所以,调试时如果发现中断异常,先看看Core是不是在调试模式。
我的习惯:在验证环境中,我会在测试用例里显式检查Core的调试状态寄存器。如果预期是运行模式但读出来是调试模式,那大概率是DM状态机出了问题。
1.3 JTAG与调试模块(DM)的基本概念
JTAG是物理层的通信协议,DM是逻辑层的控制单元。两者分工明确:
- JTAG:负责把调试器的命令“搬”进芯片。它只有4根线(TCK、TMS、TDI、TDO),通过状态机实现串行通信。
- DM:负责解析JTAG送进来的命令,然后操作Core或总线。它内部有一组寄存器,调试器通过读写这些寄存器来控制调试流程。
我画了个简图,帮你理解它们的关系:
DM内部有几个关键寄存器,我列个表:
| 寄存器 | 地址 | 作用 |
|---|---|---|
| dmcontrol | 0x10 | 控制halt/resume、复位等 |
| dmstatus | 0x11 | 反映当前调试状态 |
| abstractcs | 0x16 | 抽象命令状态与控制 |
| command | 0x17 | 发送抽象命令(读写寄存器/内存) |
| data0~data11 | 0x04~0x0F | 抽象命令的数据缓冲区 |
避坑指南:我曾经在调试时,连续发送抽象命令没检查abstractcs的busy位,结果命令被丢弃,调试器以为成功了,实际上Core根本没反应。所以,每次发命令前,一定要轮询busy位,等它空闲再发下一条。
JTAG的TAP状态机我就不展开了,你只需要知道:通过TMS信号切换状态,最终进入Shift-DR或Shift-IR,就能读写DM的寄存器。E203的JTAG实现里,IR(指令寄存器)长度是5位,DR(数据寄存器)长度可变。
举个例子,读dmstatus的流程:
// 伪代码:通过JTAG读dmstatus
1. 进入Shift-IR状态,发送5位指令 0x11 (选择dmstatus)
2. 进入Shift-DR状态,移位读出32位数据
3. 返回Run-Test/Idle状态
嗯,这里要注意:JTAG的时钟TCK和Core的主时钟是异步的。所以DM内部有同步处理,避免跨时钟域问题。我在验证时,会特意在TCK和Core时钟之间插入随机相位偏移,确保同步逻辑能覆盖所有边界情况。
总结一下:调试子系统 = JTAG(物理通路)+ DM(控制中枢)+ Core(被调试对象)。三者配合,才能实现单步、断点、寄存器读写等调试功能。