JTAG 扫描时序文档 — jtag_ir_scan 与 jtag_dr_scan
目录
1. 时序约定
1.1 时钟参数(testbench 定义)
| 参数 | 值 | 说明 |
|---|---|---|
TCK_HALF_PERIOD | 50ns | TCK 半周期 → TCK = 10 MHz |
SYS_CLK_PERIOD | 20ns | 系统时钟 → sys_clk = 50 MHz |
1.2 信号边沿规则
{
"head": {"tick": 0},
"signal": [
{"name": "TCK", "wave": "p.p.p.p.p.p", "period": 2},
{"name": "TMS", "wave": "0.1.0.1.0.1", "node": "....n.."},
{"name": "TDI", "wave": "1.0.1.0.1.0"},
{"name": "TDO", "wave": "x.0.1.0.1.0", "node": "..p...."}
]
}

关键规则(JTAG 标准):
TMS/TDI在 TCK 下降沿 由测试端驱动(testbench 的@(negedge TCK))TAP在 TCK 上升沿 采样TMS/TDI,并更新状态(@(posedge TCK))TDO在 TCK 下降沿 由 TAP 驱动,测试端在 上升沿 采样
本文档 Wavedrom 波形中:
TAP行标注每个 TCK 周期期间所处的 TAP 状态;p=时钟脉冲,n=下降沿标记,.=保持。
1.3 设备模型架构图
设备模型即 DUT(
jtag_debug_bridge):一个多 SoC JTAG 调试桥。主机(testbench,含 §2.3 的jtag_ir_scan/jtag_dr_scantask)通过 5 根 JTAG 线(TCK/TMS/TDI/TDO/TRST_n)访问设备内部,设备内部划分为 TCK 域、CDC 跨时钟域、sys_clk 域 三部分。
数据流主线(一次 JTAG 写/读命令):
- IR 扫描:主机
jtag_ir_scan沿TDI移入 8bit 指令 →ir_reg→ Update-IR 锁存到ir_latched(影子寄存器,见 §3.5)→IR 译码得到opcode/soc_id/is_reg。 - DR 扫描:主机
jtag_dr_scan沿TDI移入{addr, data}→dr_reg;dr_capture_data按ir_opcode决定回读内容(IDCODE/STATUS/读数据/写回显)。 - TDO:移位期间
TDO 输出选择把ir_scan_out或dr_scan_out的 LSB 送出,主机上升沿采样。 - 执行:Update-DR 脉冲经 CDC 同步,
cmd_*锁存命令,sys_clk 域状态机执行写/读,读数据经rd_data_reg回馈到下一次 DR 捕获。
JTAG 可见面:只有
TCK/TMS/TDI/TDO/TRST_n五根线跨出设备边界;内部 TCK 域 ↔ sys_clk 域的全部交互都通过 CDC 同步器完成。
2. TAP 状态路径
2.1 jtag_ir_scan 状态路径
{
"head": {"tick": 1, "hscale": 2},
"signal": [
{"name": "TAP", "wave": "4444444", "data": ["①","②","③","④","⑤","⑥","⑦"]}
],
"foot": {"text": "IR 扫描状态路径(共14个TCK周期)"}
}

图例(TAP 状态标签):
| 标签 | 状态 | 说明 |
|---|---|---|
| ① | RTI | 空闲(起点/终点) |
| ② | Select-DR | 选择 DR 扫描 |
| ③ | Select-IR | 选择 IR 扫描 |
| ④ | Capture-IR | 捕获 IR(装入 {6'b0,2'b01}:高6位填0 + 低2位固定捕获码01) |
| ⑤ | Shift-IR | 移位 8bit(重复 8 个 TCK 周期) |
| ⑥ | Exit1-IR | 退出移位 |
| ⑦ | Update-IR | 更新 IR:ir_latched <= ir_reg,指令生效 |
状态数: 8 个 TAP 状态跳变(不含移位内部重复的 Shift-IR)
2.2 jtag_dr_scan 状态路径
{
"head": {"tick": 1, "hscale": 2},
"signal": [
{"name": "TAP", "wave": "444444", "data": ["①","②","③","④","⑤","⑥"]}
],
"foot": {"text": "DR 扫描状态路径(共69个TCK周期)"}
}

图例(TAP 状态标签):
| 标签 | 状态 | 说明 |
|---|---|---|
| ① | RTI | 空闲(起点/终点) |
| ② | Select-DR | 选择 DR 扫描 |
| ③ | Capture-DR | 捕获 DR(装入 dr_capture_data) |
| ④ | Shift-DR | 移位 N bit(64bit 时重复 64 个 TCK 周期) |
| ⑤ | Exit1-DR | 退出移位 |
| ⑥ | Update-DR | 更新 DR(触发 sys_clk 域命令) |
状态数: 6 个 TAP 状态跳变(不含移位内部重复的 Shift-DR)
2.3 协议 Task 代码封装(jtag_debug_tb.v)
两个扫描 task 都建立在基础移位 task jtag_shift 之上:jtag_shift 按 LSB-first 在每个 TCK 下降沿驱动 TMS/TDI、上升沿采样 TDO。
jtag_shift — 基础移位(N bit)
// 发送一个 TMS 序列 + 对应数量的 TDI bit,返回 TDO bit 序列
// JTAG 是 LSB-first 协议:TDI[0] 先移入,TDO[0] 先移出
// 内部自动处理位序反转(tdi_vec/tdo_vec 仍按自然顺序存放)
task jtag_shift;
input integer num_bits; // 移位 bit 数
input [127:0] tms_vec; // TMS 序列, tms_vec[0]=最后一个bit的TMS
input [127:0] tdi_vec; // TDI 序列, tdi_vec[0]=最后移入的bit
output [127:0] tdo_vec; // TDO 序列, tdo_vec[0]=最后移出的bit
integer i;
begin
tdo_vec = 0;
for (i = 0; i < num_bits; i = i + 1) begin
@(negedge TCK); // TMS/TDI 在 TCK 下降沿变化
TMS = tms_vec[i];
TDI = tdi_vec[i];
@(posedge TCK); // TDO 在 TCK 上升沿采样
tdo_vec[i] = TDO;
end
end
endtask
jtag_ir_scan — IR 扫描(8 bit,对应 §3.2 波形 C1~C14)
// 扫描 IR (进入 Shift-IR → 移位 → 退出到 RTI)
task jtag_ir_scan;
input [7:0] ir_data;
output [7:0] ir_tdo;
reg [127:0] tms_ir;
begin
// 导航: RTI → Select-DR → Select-IR → Capture-IR → Shift-IR (C1~C4)
@(negedge TCK); TMS = 1'b1; TDI = 1'b0; @(posedge TCK); // C1 RTI→Sel-DR
@(negedge TCK); TMS = 1'b1; TDI = 1'b0; @(posedge TCK); // C2 Sel-DR→Sel-IR
@(negedge TCK); TMS = 1'b0; TDI = 1'b0; @(posedge TCK); // C3 Sel-IR→Cap-IR (Capture: ir_reg<={6'b0,2'b01})
@(negedge TCK); TMS = 1'b0; TDI = 1'b0; @(posedge TCK); // C4 Cap-IR→Shift-IR
// 移位 8 bit (C5~C12), 最后一位(MSB) TMS=1 → Exit1-IR
tms_ir = 0;
tms_ir[7] = 1'b1; // bit7=MSB 时 TMS=1 (C12)
jtag_shift(8, tms_ir, {120'h0, ir_data}, ir_tdo);
// 退出: Exit1→Update (C13) → Update→RTI (C14, 锁存 ir_latched<=ir_reg)
@(negedge TCK); TMS = 1'b1; TDI = 1'b0; @(posedge TCK); // C13 Exit1→Update
@(negedge TCK); TMS = 1'b0; TDI = 1'b0; @(posedge TCK); // C14 Update→RTI (指令生效, 见 §3.5)
end
endtask
jtag_dr_scan — DR 扫描(N bit,对应 §4.2 波形 C1~C69)
// 扫描 DR (进入 Shift-DR → 移位 → 退出到 RTI)
// num_bits : DR 扫描链长度 = 本次移位的 bit 数 (IDCODE=32, 读写=64, BYPASS=32 ...)
// dr_data : 要移入的 TDI 数据(低 num_bits 位有效)
// - 写: {addr[31:0], data[31:0]}; 读: 只给地址数据位填0; 纯读: 全0
// dr_tdo : 移位期间从 TDO 采样的回读(低 num_bits 位有效)
task jtag_dr_scan;
input integer num_bits; // DR 扫描链长度(bit 数)
input [127:0] dr_data; // 移入的 TDI 数据(低 num_bits 位有效)
output [127:0] dr_tdo; // 移出的 TDO 回读数据(低 num_bits 位有效)
reg [127:0] tms_dr;
begin
// 导航: RTI → Select-DR → Capture-DR → Shift-DR (C1~C3)
@(negedge TCK); TMS = 1'b1; TDI = 1'b0; @(posedge TCK); // C1 RTI→Sel-DR
@(negedge TCK); TMS = 1'b0; TDI = 1'b0; @(posedge TCK); // C2 Sel-DR→Cap-DR (Capture: dr_reg<=dr_capture_data)
@(negedge TCK); TMS = 1'b0; TDI = 1'b0; @(posedge TCK); // C3 Cap-DR→Shift-DR
// 移位 N bit (C4~C(3+N)), 最后一位 TMS=1 → Exit1-DR
tms_dr = 0;
tms_dr[num_bits-1] = 1'b1; // 最后移入的 bit (MSB) 设 TMS=1
jtag_shift(num_bits, tms_dr, dr_data, dr_tdo);
// 退出: Exit1→Update → Update→RTI (C(4+N)~C(5+N))
@(negedge TCK); TMS = 1'b1; TDI = 1'b0; @(posedge TCK); // C(4+N) Exit1→Update (Update-DR: 触发 sys_clk 域命令)
@(negedge TCK); TMS = 1'b0; TDI = 1'b0; @(posedge TCK); // C(5+N) Update→RTI
end
endtask
封装要点:两个 task 结构对称 —— 3~4 拍导航进入 Shift 状态 →
jtag_shift移位 N bit(最后一位 TMS=1 退出)→ 2 拍退出回 RTI。TMS/TDI一律在下降沿驱动、由 TAP 上升沿采样,TDO在上升沿采样(见 §1.2)。
3. jtag_ir_scan 典型波形
3.1 调用示例与指令位域详解
IR 编码(8-bit,每一位的作用)
build_ir(soc_id, is_reg, opcode) 拼出的 8-bit IR 结构:
代码中使用宏(
`JTAG_OP_* `、`JTAG_DEVICE_ID等)前需include "jtag_defines.vh"; 本文代码示例与jtag_tap.v/jtag_debug_bridge.v/jtag_debug_tb.v` 保持一致。
IR 8-bit: [7:5] soc_id [4] is_reg [3:0] opcode
┌───────────┬──────────┬────────────────┐
│ SoC 选择 │ 空间选择 │ 操作类型 │
└───────────┴──────────┴────────────────┘
| IR 位 | 位宽 | 字段 | 说明 |
|---|---|---|---|
[7:5] | 3 bit | soc_id | 目标 SoC 编号:`JTAG_SOC0 ~ `JTAG_SOC7(0~7) |
[4] | 1 bit | is_reg | 空间选择:`JTAG_SPACE_SRAM = 0,`JTAG_SPACE_REG = 1 |
[3:0] | 4 bit | opcode | 操作类型:`JTAG_OP_*(见下表) |
操作码表(宏定义见 jtag_defines.vh):
| opcode | 宏定义 | 助记符 | 含义 | 是否访问存储 |
|---|---|---|---|---|
0000 | `JTAG_OP_IDCODE | IDCODE | 读设备 ID | 否 |
0001 | `JTAG_OP_BYPASS | BYPASS | 旁路 | 否 |
0010 | `JTAG_OP_READ | READ | 读 SRAM/寄存器 | 是 |
0011 | `JTAG_OP_WRITE | WRITE | 写 SRAM/寄存器 | 是 |
0100 | `JTAG_OP_STATUS | STATUS | 读调试状态 | 否 |
0111 | `JTAG_OP_RESET_DBG | RESET_DBG | 复位调试逻辑 | 否 |
代码中一律用宏(
`JTAG_OP_*),数值统一由jtag_defines.vh维护,避免魔法数字。
关键点:IR 只决定「对哪个 SoC / 哪个空间 / 什么操作」;
具体的地址和写入的数据由紧随其后的 DR 扫描 提供(DR[63:32]=地址,DR[31:0]=数据)。
例 1:`JTAG_OP_IDCODE(IDCODE,读设备 ID)→ 拼出 8'h00
build_ir(`JTAG_SOC0, `JTAG_SPACE_SRAM, `JTAG_OP_IDCODE) = {3'b000, 1'b0, 4'h0} = 8'h00
└────┬────┘ └────┬────┘ └────┬────┘
SoC0 SRAM IDCODE
| 位 | 值 | 含义 |
|---|---|---|
[7:5] | `JTAG_SOC0 | SoC0(IDCODE 不访问存储,此字段无实际作用) |
[4] | `JTAG_SPACE_SRAM | SRAM 空间(同样无实际作用) |
[3:0] | `JTAG_OP_IDCODE | IDCODE:读设备 ID |
完整调用(IDCODE 不需要地址,DR 捕获即返回设备 ID):
jtag_ir_scan(build_ir(`JTAG_SOC0, `JTAG_SPACE_SRAM, `JTAG_OP_IDCODE), ir_tdo); // 装入 IDCODE 指令
jtag_dr_scan(64, 64'h0, dr_tdo); // 64=DR长度64bit; 64'h0=移入全0(读取无需输入); DR捕获→设备ID回读到dr_tdo
// 结果:dr_tdo[31:0] = `JTAG_DEVICE_ID("JTAG",bit0=1)
例 2:`JTAG_OP_WRITE(写 SoC1 寄存器 0x10)→ 拼出 8'h33
build_ir(`JTAG_SOC1, `JTAG_SPACE_REG, `JTAG_OP_WRITE) = {3'b001, 1'b1, 4'h3} = 8'h33
└────┬────┘ └────┬────┘ └────┬────┘
SoC1 寄存器 WRITE
| 位 | 值 | 含义 |
|---|---|---|
[7:5] | `JTAG_SOC1 | SoC1(第 2 个 SoC) |
[4] | `JTAG_SPACE_REG | 寄存器空间(不是 SRAM) |
[3:0] | `JTAG_OP_WRITE | WRITE 写操作 |
⚠️ IR 里没有地址和数据!「写哪个寄存器、写什么值」由紧随其后的 DR 扫描给出。
完整调用(testbench T5 中的实际用法):
// Step 1: 装入 WRITE 指令(SoC1 + 寄存器 + 写)
jtag_ir_scan(build_ir(`JTAG_SOC1, `JTAG_SPACE_REG, `JTAG_OP_WRITE), ir_tdo); // 8'h33
// Step 2: 提供 目标寄存器地址(0x10) + 写入数据(0xABCD0001)
jtag_dr_scan(64, build_dr_rw(32'h0000_0010, 32'hABCD_0001), dr_tdo); // 64=DR长度64bit({addr,data}); 0x0010=SoC1寄存器地址; 0xABCD0001=写入数据
// └───── DR[63:32] = 0x10 寄存器地址 ──────┘
// DR[31:0] = 0xABCD0001 写入数据
// 结果:SoC1.寄存器[0x10] 被写入 0xABCD0001
读操作对比(两步读协议)
若要读 SoC1 寄存器 0x10,用 `JTAG_OP_READ(拼出 8'h32)+ 两次 DR 扫描:
// Step 1: 装入 READ 指令,DR 提供读地址(0x10) → 触发读取
jtag_ir_scan(build_ir(`JTAG_SOC1, `JTAG_SPACE_REG, `JTAG_OP_READ), ir_tdo); // 8'h32 (READ)
jtag_dr_scan(64, build_dr_rw(32'h0000_0010, 32'h0), dr_tdo); // 64=DR长度64bit; 0x0010=读地址; 数据位填0(读无需输入数据)
// Step 2: 再次 READ,DR 捕获返回读到的数据
jtag_ir_scan(build_ir(`JTAG_SOC1, `JTAG_SPACE_REG, `JTAG_OP_READ), ir_tdo);
jtag_dr_scan(64, 64'h0, dr_tdo); // 64=DR长度64bit; 64'h0=移入全0(仅回读); 读数据在dr_tdo
// 结果:dr_tdo[31:0] = SoC1.寄存器[0x10] 的当前值
注:地址/数据为测试场景魔法数字,含义已在注释中说明。
本波形以
ir_data = 8'h33(SoC1/REG/WRITE) 为例。
说明:tms_ir[7]=1,即最后一个移入 bit(bit7=MSB)时 TMS=1。
3.2 完整波形(以 ir_data = 8'h33 为例)
tms_ir[7]=1:最后一个移入 bit(bit7=MSB)时 TMS=1,引导 TAP 从 Shift-IR 进入 Exit1-IR。
{
"head": {"tick": 1},
"signal": [
{"name": "TCK", "wave": "p............."},
{"name": "TAP", "wave": "44444444444444", "data": ["①","②","③","④","⑤","⑤","⑤","⑤","⑤","⑤","⑤","⑤","⑥","⑦"]},
{"name": "TMS", "wave": "11000000000110"},
{"name": "TDI", "wave": "00001100110000", "data": ["","","","","0","1","2","3","4","5","6","7","",""]},
{"name": "TDO", "wave": "00001000000000", "data": ["","","","","0","1","2","3","4","5","6","7","",""]}
]
}

图例:
- TAP 标签: ①=RTI ②=Sel-DR ③=Sel-IR ④=Cap-IR ⑤=Shift-IR(×8) ⑥=Exit1-IR ⑦=Update-IR
- TDI/TDO 数字: 移位 bit 序号(0 = LSB,最先移入/移出)
分步说明:
| TCK 周期 | TMS | TDI | 动作 | TAP 状态跳转 | 事件 |
|---|---|---|---|---|---|
| C1 | 1 | 0 | RTI→SelDR | RTI → Select-DR | — |
| C2 | 1 | 0 | SelDR→SelIR | Select-DR → Select-IR | — |
| C3 | 0 | 0 | SelIR→CapIR | Select-IR → Capture-IR | Capture-IR: ir_reg <= {6'b0, 2'b01}(8位IR: 高6位填0 + 低2位捕获码01) |
| C4 | 0 | 0 | CapIR→ShiftIR | Capture-IR → Shift-IR | 进入移位 |
| C5 | 0 | ir_data[0]=1 | Shift bit0 | Shift-IR | ir_reg 右移,TDI 从高位插入 |
| C6 | 0 | ir_data[1]=1 | Shift bit1 | Shift-IR | 同上 |
| C7 | 0 | ir_data[2]=0 | Shift bit2 | Shift-IR | 同上 |
| C8 | 0 | ir_data[3]=0 | Shift bit3 | Shift-IR | 同上 |
| C9 | 0 | ir_data[4]=1 | Shift bit4 | Shift-IR | 同上 |
| C10 | 0 | ir_data[5]=1 | Shift bit5 | Shift-IR | 同上 |
| C11 | 0 | ir_data[6]=0 | Shift bit6 | Shift-IR | 同上 |
| C12 | 1 | ir_data[7]=0 | Shift bit7 | Shift-IR → Exit1-IR | 最后 bit,TMS=1 退出移位 |
| C13 | 1 | 0 | Exit1→Update | Exit1-IR → Update-IR | Update-IR: 指令生效 |
| C14 | 0 | 0 | Update→RTI | Update-IR → RTI | 回到空闲 |
3.3 TDI/TDO 位序图(移位阶段 C5~C12)
TDI 输入 (从右到左依次移入):
ir_data[7] ir_data[6] ... ir_data[1] ir_data[0]
C12 ▲ ▲ ▲ ▲
(MSB 最后) (LSB 最先) C5
移位链内部: TDI ──► ir_reg[7] → ir_reg[6] → ... → ir_reg[0] ──► TDO
(高位插入,右移,最低位移出)
移完 8 bit 后: ir_reg = ir_data (LSB-first 协议,自然位序)
ir_tdo = 移位期间 TDO 依次采样的值(即 Capture-IR 装入的 8'b0000_0001 逐位移出)
3.4 ir_tdo 回读值说明
ir_tdo 拿到的是 IR 扫描链在 Capture-IR 时装入的值、移位期间从 TDO 逐位移出的结果——它是 scan-out(回读值),不是指令本身。
固定值 8'h01(与装入什么指令无关)。原因:jtag_debug_bridge.v 的 IR 扫描链在 Capture-IR 阶段装入固定值:
else if (ir_capture_en) begin
ir_reg <= {{IR_WIDTH-2{1'b0}}, 2'b01}; // 装入 8'b0000_0001
end
移位时 TDO 输出 ir_reg[0](最低位先出),8 个移位周期(C5~C12)依次移出:
Capture 装入: 0000_0001
TDO 移出(LSB-first): 1 → 0 → 0 → 0 → 0 → 0 → 0 → 0
└── C5 C6 C7 C8 C9 C10 C11 C12
ir_tdo = 8'h01
对应 §3.2 波形 TDO 行 00001000000000(C5=1,其余 0)。
含义:
8'h01=bit[1:0]=01,正是 JTAG 规范要求的 Capture-IR 固定捕获码,向主机表明"本设备存在 IR 扫描链 / 支持 IDCODE"- 完整 JTAG 设计中,Capture-IR 通常捕获当前已装入的指令寄存器值供主机读回确认;本例简化为固定
{6'b0,2'b01}
一句话总结:
jtag_ir_scan(build_ir(`JTAG_SOC1, `JTAG_SPACE_REG, `JTAG_OP_WRITE), ir_tdo);
// ir_tdo = 8'h01 ← 只是回读的捕获码,与 WRITE 指令无关
// 真正生效的指令是 ir_data 移入的那 8 位
ir_tdo= 扫描链"吐出来"的旧值/捕获值(固定8'h01);ir_data才是"塞进去"的新指令。
3.5 Update-IR 影子指令寄存器(ir_latched)
规范要求(IEEE 1149.1):IR 的并行输出(生效指令)只能在 Update-IR 状态改变,防止 Shift-IR 移位中途「半截指令」被组合逻辑误用。本设计用 TCK 域影子寄存器
ir_latched实现。
// jtag_debug_bridge.v — IR 扫描链 + Update-IR 影子锁存
reg [IR_WIDTH-1:0] ir_reg; // 移位寄存器(Capture/Shift 用,可被中途改写)
reg [IR_WIDTH-1:0] ir_latched; // 影子指令寄存器(只在 Update-IR 更新)
// ir_reg: 仅 capture/shift 两分支(无 update 分支)
always @(posedge TCK or negedge TRST_n) begin
if (!TRST_n) begin
ir_reg <= {IR_WIDTH{1'b0}};
end else if (ir_capture_en) begin
ir_reg <= {{IR_WIDTH-2{1'b0}}, 2'b01}; // Capture-IR 捕获码
end else if (ir_shift_en) begin
ir_reg <= {ir_scan_in, ir_reg[IR_WIDTH-1:1]}; // Shift-IR 移位
end
end
// ir_latched: 仅在 Update-IR(ir_update_en 脉冲)锁存移好的新指令
always @(posedge TCK or negedge TRST_n) begin
if (!TRST_n)
ir_latched <= {IR_WIDTH{1'b0}}; // 复位 = IDCODE
else if (ir_update_en) // Update-IR 脉冲
ir_latched <= ir_reg; // 锁存新指令
end
// 译码一律取自 ir_latched(稳定指令),不再直接读 ir_reg
assign ir_soc_id = ir_latched[IR_WIDTH-1 -: SOC_IDX_WIDTH];
assign ir_opcode = ir_latched[3:0];
assign ir_bypass = (ir_opcode == OP_BYPASS);
行为(注:锁存发生在离开 Update-IR 的 posedge,即 Update→RTI 边沿):
| 时刻 | ir_reg | ir_latched(生效指令) |
|---|---|---|
| Shift-IR 移位中途 | 逐位变化(半截新指令) | 保持上一指令不变 |
| 移位完成(Exit1-IR) | = 新指令 | 仍为上一指令 |
| 进入 Update-IR 瞬间 | = 新指令 | 仍为上一指令(离开时才锁存) |
| 离开 Update-IR(→RTI) | = 新指令 | 更新为新指令 ✓ |
验证:testbench 新增 T10: Shadow IR Latch (Update-IR),用分层引用 u_dut.ir_latched 直接观察:
[init] ir_reg=0x00 ir_latched=0x00 ← 装入 IDCODE 后锁存生效
[mid-shift] ir_reg=0x30 ir_latched=0x00 ← 移位中途: ir_reg 已是半截新指令, ir_latched 仍为旧指令
[pre-update] ir_reg=0x33 ir_latched=0x00 ← 移完 8bit, 未到 Update-IR, 锁存仍未更新
[in-update] ir_reg=0x33 ir_latched=0x00 ← 进入 Update-IR 瞬间仍未更新(离开时锁存)
[after] ir_reg=0x33 ir_latched=0x33 ← 离开 Update-IR 后锁存为新指令
Read-back: 0xdeadcafe ← 用新指令 WRITE 写 SoC1 REG[0x10] 读回生效
这样即使主机在 Shift-IR 中途暂停/退出,
ir_reg留下半截值,生效指令ir_latched也不受影响,符合 IEEE 1149.1 对指令并行输出稳定性的要求。
4. jtag_dr_scan 典型波形
4.1 调用示例
// 例:64-bit 数据扫描(IDCODE 读取,全 0 输入)
jtag_dr_scan(64, 64'h0, dr_tdo); // 64=DR长度64bit; 64'h0=移入全0(IDCODE回读无需输入)
// 例:64-bit 读写扫描 {addr=0x40, data=0x1234_5678}
jtag_dr_scan(64, build_dr_rw(32'h0000_0040, 32'h1234_5678), dr_tdo); // 64=DR长度64bit({addr,data}); 0x0040=写地址; 0x12345678=写入数据
4.2 完整波形(64-bit,TDI/TDO 用 x 表示数据移位)
{
"head": {"tick": 1},
"signal": [
{"name": "TCK", "wave": "p...................................................................."},
{"name": "TAP", "wave": "444444444444444444444444444444444444444444444444444444444444444444444", "data": ["①","②","③","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","④","⑤","⑥"]},
{"name": "TMS", "wave": "100000000000000000000000000000000000000000000000000000000000000000110"},
{"name": "TDI", "wave": "000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx00"},
{"name": "TDO", "wave": "000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx00"}
]
}



图例:TAP 标签 ①=RTI ②=Sel-DR ③=Cap-DR ④=Shift-DR(×64) ⑤=Exit1-DR ⑥=Update-DR
分步说明:
| TCK 周期 | TMS | 动作 | TAP 状态跳转 | 事件 |
|---|---|---|---|---|
| C1 | 1 | RTI→SelDR | RTI → Select-DR | — |
| C2 | 0 | SelDR→CapDR | Select-DR → Capture-DR | Capture-DR: dr_reg <= dr_capture_data |
| C3 | 0 | CapDR→ShiftDR | Capture-DR → Shift-DR | 进入移位 |
| C4 | 0 | Shift bit0 | Shift-DR | dr_reg 右移 |
| C5 | 0 | Shift bit1 | Shift-DR | 同上 |
| … | … | … | … | … |
| C66 | 0 | Shift bit62 | Shift-DR | 同上 |
| C67 | 1 | Shift bit63 | Shift-DR → Exit1-DR | 最后 bit,TMS=1 |
| C68 | 1 | Exit1→Update | Exit1-DR → Update-DR | Update-DR: 触发 sys_clk 域命令执行 |
| C69 | 0 | Update→RTI | Update-DR → RTI | 回到空闲 |
注意:
num_bits可配置。例如 BYPASS 时jtag_dr_scan(32, ...),则移位阶段为 C4~C35,尾部 C36~C37。
4.3 64-bit DR 数据布局
| DR 位域 | 长度 | 内容 | 移入顺序 |
|---|---|---|---|
DR[63:32] | 32 bit | Address(读写目标地址) | 后移入(C36~C67) |
DR[31:0] | 32 bit | Data(写入数据 / 读时忽略) | 先移入(C4~C35) |
移入顺序为 LSB-first:先移入低 32 位(Data),再移入高 32 位(Address)。
5. IDCODE 读回 TDO 波形
5.1 场景
// 装入 IDCODE 指令(等价于 8'h00)
jtag_ir_scan(build_ir(`JTAG_SOC0, `JTAG_SPACE_SRAM, `JTAG_OP_IDCODE), ir_tdo);
jtag_dr_scan(64, 64'h0, dr_tdo); // 64=DR长度64bit; 64'h0=移入全0(仅回读); Capture 装入 {32'h0=高32位, `JTAG_DEVICE_ID=低32位}
5.2 TDO 波形(前 36 周期:3 导航 + 32 位 IDCODE)
`JTAG_DEVICE_ID=0x4A544147= “JTAG”,bit0=1 符合 JTAG 规范;移位期间按 LSB-first 逐位移出。
{
"head": {"tick": 1},
"signal": [
{"name": "TCK", "wave": "p..................................."},
{"name": "TDO", "wave": "000111000101000001000101010010100100", "data": ["","","","0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32"]}
]
}


图例:TDO 数字 = 移出的 bit 序号(0 最先移出,即 LSB);波形电平 1/0 即该 bit 的值
说明:C4~C35 输出
`JTAG_DEVICE_ID(0x4A544147)的 32bit(LSB-first,bit0=1符合 JTAG 规范);C36 起为高 32 位(0x00000000)的 bit0=0。
6. TDO 输出语义
6.1 TDO 何时有意义
TDO 只有在 Shift-DR / Shift-IR 状态 时才输出有效数据,其余状态输出 0:
// jtag_tap.v 中的 TDO 逻辑
always @(negedge TCK) begin
if (tap_state == ST_SHIFT_IR)
tdo_reg <= ir_scan_out[0]; // IR 链最低位
else if (tap_state == ST_SHIFT_DR)
tdo_reg <= dr_scan_out; // DR 链最低位
else
tdo_reg <= 1'b0;
end
6.2 移位期间 TDO 采样的内容
Capture 时刻装入扫描链的值(进入 Shift 前的那个 posedge):
- IR: Capture-IR 装入 {6'b0, 2'b01} → 移位期间逐个移出
- DR: Capture-DR 装入 dr_capture_data(IDCODE/状态/读回数据)
所以测试端在移位阶段采样到的 TDO 序列 = Capture 装入值按 LSB-first 移出
6.3 波形示例:IDCODE 读取
{
"signal": [
{"name": "TCK", "wave": "p.........."},
{"name": "TDO", "wave": "1.1.1.0.0.0"}
]
}

说明:IR 扫描装入
`JTAG_OP_IDCODE(IDCODE),DR 扫描 Capture 装入`JTAG_DEVICE_ID;最低位 bit0=1 符合 JTAG 规范。
7. 关键时序点总结
| # | 关键点 | 说明 |
|---|---|---|
| 1 | TMS/TDI 下降沿驱动 | testbench 在 @(negedge TCK) 改变信号,保证上升沿采样时稳定 |
| 2 | TDO 上升沿采样 | testbench 在 @(posedge TCK) 采样 TDO,与 TAP 的下降沿驱动错开半周期 |
| 3 | 最后一位 TMS=1 | tms_ir[7]=1 / tms_dr[num_bits-1]=1 引导 TAP 从 Shift 进入 Exit1 |
| 4 | LSB-first 位序 | jtag_shift 从 i=0 循环到 num_bits-1,TDI[0] 先移入,TDO[0] 先移出 |
| 5 | Capture 先于 Shift | Capture-DR/IR 在进入 Shift 前的那个上升沿装入数据 |
| 6 | Update 触发执行 | Update-IR 把 ir_reg 锁存进影子寄存器 ir_latched(指令生效,规范要求);Update-DR 触发 sys_clk 域命令执行(经 CDC) |
| 7 | 两步读协议 | 读取需两次 DR 扫描:第一次 Update-DR 执行读取,第二次 Capture-DR 返回数据 |
| 8 | IR 8bit / DR 可变 | IR 固定 8 bit;DR 长度由 num_bits 指定(IDCODE=32, R/W=64, BYPASS=32 等) |
| 9 | 影子指令寄存器 | 译码取自 ir_latched(仅 Update-IR 更新),Shift-IR 中途保持稳定,符合 IEEE 1149.1 |
附录: 时序参数速查
| 事件 | 起始周期 | 结束周期 | 周期数 |
|---|---|---|---|
| IR 扫描: 导航 | C1 | C4 | 4 |
| IR 扫描: 移位 | C5 | C12 | 8 |
| IR 扫描: 退出 | C13 | C14 | 2 |
| IR 扫描总计 | C1 | C14 | 14 TCK |
| DR 扫描: 导航 | C1 | C3 | 3 |
| DR 扫描: 移位 | C4 | C(3+N) | N |
| DR 扫描: 退出 | C(4+N) | C(5+N) | 2 |
| DR 扫描总计 (N=64) | C1 | C69 | 69 TCK |
| DR 扫描总计 (N=32) | C1 | C37 | 37 TCK |

414

被折叠的 条评论
为什么被折叠?



