3. JTAG核心概念
JTAG 的核心,说白了就是一台有 16 个状态的有限状态机。你每次在调试器上点“连接”或“单步”,背后都是这个状态机在按规则跳动。我刚开始学 JTAG 时,总觉得这 16 个状态太复杂,后来发现——你真正需要记住的,其实就 8 个关键状态。
今天我们就把这 8 个状态掰开揉碎,一个一个讲清楚。
3.1 状态机全景图
先看一张总图,把 16 个状态的位置关系搞清楚。我习惯把状态机分成两条腿:一条是 DR(数据寄存器)路径,一条是 IR(指令寄存器)路径。两条路径的结构完全对称,只是操作的对象不同。
嗯,这张图看着有点密,但你只要记住:从 Reset 出发,经过 Idle,然后选择走 DR 路还是 IR 路。两条路的结构一模一样,都是 Capture → Shift → Exit1 → Pause → Exit2 → Update 的流程。
3.2 八个关键状态详解
3.2.1 Test-Logic-Reset
这是状态机的起点,也是安全屋。上电后 TAP 控制器默认就在这个状态。此时 TDI 和 TDO 之间是直通的——不对,准确说是 TDO 处于高阻态,TDI 输入被忽略。整个测试逻辑被复位,所有寄存器清零。
3.2.2 Run-Test/Idle
从 Reset 出来,TMS 拉低一个时钟,就进入 Idle。这是状态机的“待机室”。在这里,你可以选择什么都不做,也可以让某些测试逻辑运行(比如自测试)。
我个人习惯把 Idle 当作“安全过渡态”。比如在两次扫描操作之间,我会让状态机回到 Idle 待一两个时钟,确保前一次操作完全结束。
3.2.3 Select-DR-Scan
这是分岔路口。从 Idle 发一个 TMS=1,就进入 Select-DR-Scan。如果你紧接着发 TMS=0,就进入 Capture-DR;如果发 TMS=1,就跳到 Select-IR-Scan。
说白了,这个状态就是让你决定:接下来我要操作数据寄存器,还是指令寄存器?
3.2.4 Capture-DR
进入 Capture-DR 后,在 TCK 的上升沿,选中的数据寄存器会把当前值“捕获”到移位寄存器链中。比如你要读 IDCODE,芯片就会把 ID 值加载到移位链里。
3.2.5 Shift-DR
这是最忙的状态。在 Shift-DR 里,每个 TCK 上升沿,移位寄存器都会移动一位:TDI 上的数据移入,TDO 上的数据移出。你要读多少位,就在这个状态待多少个时钟。
举个例子,读 IDCODE 通常是 32 位,那就在 Shift-DR 里待 32 个时钟。第 1 个时钟移出最低位,第 32 个时钟移出最高位。
// 伪代码:在 Shift-DR 中移位 32 位
for (i = 0; i < 32; i++) {
TDI = (data_to_send >> i) & 0x01; // 逐位发送
TCK = 1; delay(); // 上升沿
bit_received = TDO; // 读取 TDO
data_received |= (bit_received << i);
TCK = 0; delay(); // 下降沿
}
3.2.6 Exit1-DR
Shift-DR 结束后,你必须进入 Exit1-DR。这是一个临时暂停点。在这里你可以选择:
- TMS=0 → 进入 Pause-DR,暂停移位
- TMS=1 → 进入 Update-DR,结束本次扫描
我一般直接走 TMS=1 到 Update-DR,除非我需要暂停一下处理其他事情。
3.2.7 Pause-DR
Pause-DR 允许你暂停移位操作,同时保持移位寄存器中的数据不变。这在处理长数据流时很有用——比如你要读一个 1024 位的边界扫描链,中间可以去处理一下中断,再回来继续。
从 Pause-DR 出来有两个选择:
- TMS=0 → 留在 Pause-DR
- TMS=1 → 进入 Exit2-DR
3.2.8 Update-DR
这是 DR 路径的终点。在 Update-DR 的 TCK 下降沿,移位寄存器中的数据会被锁存到并行的输出寄存器中。对于写操作,数据在这一步真正生效。
3.3 状态转换速查表
| 当前状态 | TMS=0 下一状态 | TMS=1 下一状态 | 典型用途 |
|---|---|---|---|
| Test-Logic-Reset | Run-Test/Idle | Test-Logic-Reset | 上电复位、异常恢复 |
| Run-Test/Idle | Run-Test/Idle | Select-DR-Scan | 待机、等待 |
| Select-DR-Scan | Capture-DR | Select-IR-Scan | 选择数据路径 |
| Capture-DR | Shift-DR | Exit1-DR | 捕获数据到移位链 |
| Shift-DR | Shift-DR | Exit1-DR | 逐位移位(核心操作) |
| Exit1-DR | Pause-DR | Update-DR | 暂停或结束移位 |
| Pause-DR | Pause-DR | Exit2-DR | 暂停移位 |
| Exit2-DR | Shift-DR | Update-DR | 从暂停恢复 |
| Update-DR | Run-Test/Idle | Select-DR-Scan | 数据生效、结束扫描 |
3.4 实战:一个完整的读 IDCODE 流程
光讲理论不过瘾,我们走一遍实际流程。假设你要读芯片的 IDCODE(32 位),状态机应该怎么跳?
- Test-Logic-Reset → 发 5 个 TMS=1 的 TCK,确保复位
- Run-Test/Idle → TMS=0,进入 Idle
- Select-DR-Scan → TMS=1,选择 DR 路径
- Capture-DR → TMS=0,捕获 IDCODE 到移位链
- Shift-DR → TMS=0,停留 32 个时钟,逐位移出数据
- Exit1-DR → TMS=1,结束移位
- Update-DR → TMS=1,更新(读操作其实不需要,但流程必须走完)
- Run-Test/Idle → TMS=0,回到 Idle,准备下一次操作
3.5 避坑指南
- 不要在 Shift-DR 里多待时钟:移位位数必须精确等于寄存器长度。多一个时钟,数据就多移了一位,整个字节序就乱了。
- 注意 TCK 的边沿:状态机在 TCK 上升沿跳转,数据在 TCK 下降沿变化。这个时序关系搞反了,数据就采不对。
- Pause-DR 不是必须的:大多数调试场景根本用不到 Pause-DR。我只有在调试边界扫描链(几百位长)时才用过它。
- IR 路径和 DR 路径完全对称:你学会了 DR 路径,IR 路径自然就会了。唯一的区别是 IR 路径操作的是指令寄存器,通常只有 4-8 位。
好了,JTAG 状态机的 8 个关键状态就讲到这里。你想想看,其实核心就是一条线:Reset → Idle → Select → Capture → Shift → Exit1 → Update → 回到 Idle。把这个循环刻在脑子里,JTAG 调试就入门了。