4. JTAG 指令寄存器:标准指令与自定义指令
JTAG 接口的核心,说白了就是通过 TDI 往指令寄存器(IR)里写指令,然后数据寄存器(DR)根据这条指令干活。指令寄存器通常 4~5 位宽,具体看芯片设计。我刚开始接触 JTAG 时,总觉得指令就是一堆 0 和 1,后来调了块板子才发现——选错指令,整个调试链路都跑不通。
这一节我们重点看几条标准指令:BYPASS、EXTEST、SAMPLE/PRELOAD、IDCODE,以及用户自定义指令怎么玩。
4.1 标准指令概览
IEEE 1149.1 标准定义了几条强制指令和可选指令。我整理了一张表,方便你对照着看:
| 指令名称 | 指令编码(4位示例) | 类型 | 选中的数据寄存器 | 主要用途 |
|---|---|---|---|---|
| BYPASS | 1111 | 强制 | 1 位 Bypass 寄存器 | 旁路当前器件,缩短扫描链 |
| EXTEST | 0000 | 强制 | 边界扫描寄存器 | 板级互连测试 |
| SAMPLE/PRELOAD | 0001 | 强制 | 边界扫描寄存器 | 采样引脚状态 / 预加载数据 |
| IDCODE | 0010 | 可选 | 32 位 ID 寄存器 | 读取器件标识码 |
| USERCODE | 0011 | 可选 | 32 位 User 寄存器 | 读取用户自定义标识 |
| HIGHZ | 0100 | 可选 | Bypass 寄存器 | 将所有输出置为高阻 |
4.2 BYPASS 指令
这条指令最简单,也最常用。当你在一条 JTAG 链上挂了多个器件,只想测其中一个时,其他器件必须设成 BYPASS。
BYPASS 指令选中 1 位的 Bypass 寄存器。TDI 进来的数据,经过一个 TCK 周期就从 TDO 出去了。说白了就是个直通移位寄存器。
// 假设链上有 3 个器件,只想访问器件 2
// 器件 1 和器件 3 的指令寄存器写入 BYPASS(0xF)
// 器件 2 写入目标指令(如 IDCODE)
// 此时扫描链总长度 = 1 + 32 + 1 = 34 位
我曾经遇到过一个问题:链上 8 个器件,其中一个 BYPASS 没生效,结果整个扫描链长度算错了,TDO 回来的数据全是乱的。排查了半天才发现是那个器件的指令寄存器没锁存住。
4.3 EXTEST 指令
EXTEST 是板级测试的核心指令。它选中边界扫描寄存器,把 TDI 输入的数据加载到输出引脚上,同时捕获输入引脚的状态。
说白了,就是通过 JTAG 接口控制芯片的引脚电平,检测板级焊接有没有短路、断路。
EXTEST 的执行流程:
- PRELOAD 阶段:先用 SAMPLE/PRELOAD 指令把安全数据加载到边界扫描寄存器
- 切换指令:写入 EXTEST 指令
- 驱动输出:边界扫描寄存器中的数据驱动到输出引脚
- 捕获输入:相邻器件的输入引脚捕获电平
- 移位读出:通过 Shift-DR 把捕获结果移出来
4.4 SAMPLE/PRELOAD 指令
这条指令有两个功能,名字已经说清楚了:
- SAMPLE:在不影响芯片正常工作的情况下,采样引脚当前状态
- PRELOAD:在切到 EXTEST 之前,预加载边界扫描寄存器的数据
我个人的习惯是,在系统运行时先用 SAMPLE 抓一抓关键引脚的状态,看看时序对不对。这比用示波器去点方便多了,尤其是 BGA 封装的芯片,引脚根本没法直接测。
// SAMPLE 操作示例
// 1. 进入 Shift-IR,写入 SAMPLE/PRELOAD 指令(0x1)
// 2. 进入 Run-Test/Idle
// 3. 进入 Shift-DR,移出边界扫描数据
// 4. 移出的数据就是当前引脚状态的快照
4.5 IDCODE 指令
IDCODE 是调试时最常用的指令之一。它返回一个 32 位的器件标识码,包含:
- 位 [31:28]:版本号
- 位 [27:12]:器件编号(16 位)
- 位 [11:1]:制造商编号(11 位)
- 位 [0]:固定为 1
你想想看,如果 JTAG 链上挂了不同厂家的芯片,通过 IDCODE 就能自动识别每个器件的位置和型号。很多调试工具(比如 OpenOCD)启动时都会自动扫描 IDCODE 来建立器件列表。
4.6 用户自定义指令
标准指令之外,芯片厂商可以定义自己的指令。比如 ARM 的 Cortex-M 系列就用自定义指令来实现 SWD 切换、调试寄存器访问等功能。
用户自定义指令的编码范围是 0x08~0x3F(4 位指令寄存器时),具体看芯片手册。我见过一些 FPGA 厂商用自定义指令来配置 PLL、读取温度传感器、甚至控制内部逻辑分析仪。
// 以某款 MCU 为例的自定义指令
// 0x0A: 读取芯片内部温度
// 0x0B: 写入调试锁定寄存器
// 0x0C: 触发软件复位
// 使用流程
// 1. Shift-IR: 写入 0x0A
// 2. Update-IR: 锁存指令
// 3. Shift-DR: 移出 16 位温度数据
嗯,这里要注意:自定义指令的编码不能和标准指令冲突。有些芯片会把 0x0~0x7 保留给标准指令,0x8~0xF 给自定义。但不同厂商的分配方式不一样,一定要看 datasheet。
4.7 指令寄存器操作时序
指令寄存器的操作和状态机绑定。我画了一张流程图,帮你理清指令写入的完整路径:
从流程图可以看到,指令写入的关键路径是:Select-IR-Scan → Capture-IR → Shift-IR → Exit1-IR → Update-IR。Capture-IR 阶段会把当前指令寄存器的值捕获到移位链中,Shift-IR 阶段逐位写入新指令,最后 Update-IR 锁存生效。
4.8 指令寄存器的实际调试技巧
最后分享几个我在项目中积累的经验:
- 读 IDCODE 确认链路:上电后第一件事,发 IDCODE 指令,确认每个器件都能正常响应
- BYPASS 缩短扫描链:链上器件多时,把不操作的都设成 BYPASS,减少移位时间
- PRELOAD 保护电路:切 EXTEST 前一定做 PRELOAD,避免输出引脚出现意外电平
- 自定义指令先读手册:不同厂商的自定义指令编码可能冲突,务必确认
- 状态机走完整:指令写入必须走到 Update-IR,别偷懒
指令寄存器这部分,说白了就是 JTAG 的「遥控器」。你按哪个键,芯片就执行哪个功能。理解每条指令的用途和操作流程,后面调试时才能得心应手。