Sequencer 槽
本页中的每个偏移、地址、位位置和常量,都从
libtpu-0.0.40-cp314wheel 中的libtpu.so逐字节读取而来(BuildID md589edbbe81c5b328a958fe628a9f2207d,未 strip,1,233,709 个符号)。其他版本会有所不同。
摘要
每个 TPU VLIW bundle 都恰好携带一个 sequencer 槽:这是唯一拥有 program-counter mutation 权限的标量 ALU lane。它是编码 branch / jump、call、halt、pipeline-balancing delay op、hardware-loop-counter read,以及(在 SparseCore engine 上)sync-flag 和 barrier op 的 lane。矩阵单元、向量 lane 和内存端口由 bundle 发射,但只有 sequencer 槽能改变下一个周期执行什么。从功能上看,这是片上控制 CPU 被缩减为 issue word 中的一个槽。
该槽的身份在整条硅片产品线上都有字节锚定。在 Jellyfish(TPU v2)和 Pufferfish(TPU v4)上,它名为 SLOT_SCALAR_0;从 Viperfish(v5e)开始,它名为 SLOT_SCALAR_ALU_0。在两套命名方案中,PC-mutating opcode 都只在 lane 0 中合法——lane 1(SLOT_SCALAR_1 / SLOT_SCALAR_ALU_1)承载 halt / fence / delay 以及标量 ALU 的镜像,但绝不承载 branch 或 call。在 Jellyfish 上,这条规则是 ProtoUtils::ScalarOpAllowedInSlot(0x1e875a20)中的字面 bitmask;在 V5+ 上,它体现为 proto-message 结构 bundle.scalar_alu().scalar_alu_0().branch_relative()。本页把该槽作为重实现目标来记录:每代中它是哪条 lane,把 control-flow intent 转换成 bundle bytes 的三层编码路径,branch / call / halt / delay / loop-read 字段布局,以及 predication 字段如何兼作条件分支条件。逐(generation × sequencer-type)的 op 清单位于配套页面;hardware-loop-counter 细节位于 Hardware Loop-Counter。
对重实现而言,契约如下:
- 每个 bundle 一个 sequencer 槽,始终是 scalar-ALU sub-bundle 的 lane 0;PC mutation 仅限 lane 0,编译器必须强制执行。
- branch / call target 是 signed 20-bit 字段(
−0x80000 .. +0x7FFFF),落在 bundle 的 immediate slot 0 中,而不是 sequencer 槽字节内部;absolute-vs-relative 纯粹是同一字段上的 opcode discriminator。 - call 会把返回地址写入一个
dest标量寄存器(SparseCore 路径上的 link registersreg #5);没有专用 return opcode——return 是读取 link register 的BranchSreg。 - 每个 branch/call 携带 0..5 delay-slot count(3-bit);在 V5+ 上,delay slot 是 bundle-packer padding,而不是编码后的槽 bit。
- predication 字段就是 branch condition:
PREDICATION_ALWAYS= 无条件,谓词寄存器索引 = 条件,PREDICATION_NEVER= 被门控关闭(空槽编码)。 - 两种循环模型:Jellyfish 使用软件 bundle-index backward branch(
BeginLoop/EndLoop);V5+ 通过ReadRegisterLccLow/High读取硬件 loop counter。
| 槽身份(JF/PF) | SLOT_SCALAR_0 / SLOT_SCALAR_1 |
| 槽身份(V5+) | SLOT_SCALAR_ALU_0 / SLOT_SCALAR_ALU_1 |
| PC-mutating lane | 仅 lane 0(branch/call);lane 1 = halt/fence/delay 镜像 |
| JF 槽合法性规则 | ProtoUtils::ScalarOpAllowedInSlot @ 0x1e875a20(slot-0 mask 0x18000000f00) |
| JF branch 分类器 | ProtoUtils::IsBranch @ 0x1e876120(op & ~3 == 8 → 8..11) |
| JF call 分类器 | ProtoUtils::IsCall @ 0x1e876140(op & ~3 == 12 → 12..15) |
| V5+ SCS branch 编码器 | isa_emitter::EmitBranchOp<…BranchRelative> @ 0x13a5d3e0 |
| V5+ SCS call 编码器 | isa_emitter::EmitCallOp<…CallAbsolute> @ 0x13a5d4c0 |
gfc TC seq dispatch | gfc::isa::TensorCoreScalarAlu0Encoder::Encode @ 0x1f87b420 |
gfc TC opcode-HIGH / family | bit 483,宽度 6(branch/call-immediate 为 0) |
gfc TC opcode-LOW / discriminator | bit 478,宽度 5(BranchAbsolute=4/Rel=5/CallAbs=6/Rel=7) |
gfc TC seq predicate selector | bit 489,宽度 2(*(scalar_alu+28)) |
| Branch/call target | signed 20-bit,immediate slot 0(EmitImmediate<SparseCoreImmediates>) |
| Call link register | 写入 dest 的 sreg #5(SCS 路径) |
| Delay-slot 字段 | 0..5(3-bit);verifier delay_slots_op.getImm() >= 0 && <= 5 |
| Predication = condition | PREDICATION_ALWAYS/NEVER/OR_NEVER/OR_INVERTED_NEVER |
| V5+ predication 编码 | TPUMCCodeEmitter::encodePredicateOperand @ 0x13c77c40(7-bit) |
| 空槽标记 | kNeverExecute = 31(0xb834cfc) |
| 置信度 | CONFIRMED(字节锚定),除非某行另有说明 |
Sequencer 槽是什么
bundle 是一个 VLIW issue word;bundle model 覆盖 no-scoreboard 契约。在这个 word 内,sequencer 槽是唯一一个其 op 能改变 program counter 的 lane。发射 Halt,core 就停止;发射 BranchRelative,下一个被 fetch 的 bundle 是分支目标,而不是 fall-through;发射 CallAbsolute,fall-through 地址会被捕获进一个标量寄存器,让 callee 能返回到它。bundle 中的其他所有东西——matrix push、vector ALU、load、store——都“就地”执行,并 fall through 到下一个顺序 bundle。
硬件通过让 scalar-ALU sub-bundle 恰好有两条 lane,并且只允许其中一条进行 PC mutation,来强制每个 bundle 只有一个 sequencer。该约束在二进制中很具体。ProtoUtils::ScalarOpAllowedInSlot(0x1e875a20)接受一个 ScalarOpcode 和一个槽索引,对于槽索引 ≤ 1,它用两个 64-bit bitmask 测试 opcode:
// ProtoUtils::ScalarOpAllowedInSlot(ScalarOpcode op, int slot) @ 0x1e875a20
// (decoded byte-exactly from objdump)
if (slot <= 1) {
// either lane (lane-0 OR lane-1):
if (bt(0x6000060070, op)) return true; // movabs $0x6000060070; bt %rdx
// lane-0 ONLY (PC-mutating + a couple of others):
if (bt(0x18000000f00, op)) return (slot == 0); // movabs $0x18000000f00; bt %rdx
}
```text
slot-0-only mask `0x18000000f00` 在 opcode `{8, 9, 10, 11}`(四个 branch opcode)以及 `{39, 40}` 上置位——这些是 lane-0-exclusive op。either-lane mask `0x6000060070`(bit `{4, 5, 6, 17, 18, 37, 38}`)覆盖两条 lane 都合法的标量 op。允许 packer 把 branch 放进 lane 1 的重实现会产生硬件拒绝的 bundle。
> **陷阱 — sequencer 槽是两 lane sub-bundle 的一条 lane,而不是整个 bundle 字段。** scalar-ALU sub-bundle 有 `scalar_alu_0` 和 `scalar_alu_1`;sequencer op 是 `scalar_alu_0` 的 `oneof` 成员。Lane 1 确实存在,承载 ALU compute、halt、fence 和 delay,但 lane 1 中的 branch/call 是非法的。把“sequencer”建模为单个逐 bundle 字段,而不是两 lane 标量 sub-bundle 的 lane 0,会丢失 lane-1 ALU 容量,并错误检查槽合法性。
---
## 各代槽身份与位置
该槽在每一代都位于 scalar-ALU sub-bundle 的 lane 0,但周边 sub-core 分类会变化:每个 TPU 代际包含若干 **sequencer type**(`TpuSequencerType`),每种 sequencer type 都有自己的 bundle 和自己的 scalar-ALU sub-bundle。配套页面枚举了 [TpuSequencerType enum](sequencer-ops-per-gen.md#the-tpusequencertype-enum);下表固定逐(generation × sequencer-type)的槽位置。
| Gen | Sequencer type | Bundle B | Sequencer lane | Lane 1(无 PC mutation) |
|---|---|---:|---|---|
| Jellyfish (v2) | TensorCore | 41 | `SLOT_SCALAR_0` | `SLOT_SCALAR_1`(halt/fence/delay) |
| Jellyfish (v2) | BarnaCoreAddressHandler | 16 | 专用 BCAH `Branch` ScalarSlot | n/a |
| Dragonfish (v3) | TC / BCAH | 41 / 16 | Jellyfish codec 的别名 | (同 Jellyfish) |
| Pufferfish (v4) | TensorCore | 51 | `Scalar0`(`TensorCoreScalar0_*`) | `Scalar1`(halt/fence/delay) |
| Pufferfish (v4) | BarnaCoreSequencer | 32 | `BarnaCoreSequencerScalar0` | `Scalar1`(halt/fence/delay + sync) |
| Viperfish (v5e) | TensorCore | 64 | `ScalarAlu0`(`vxc::isa`) | `ScalarAlu1` |
| Viperfish (v5e) | SCS / TAC / TEC | 32 / 64 / 64 | `ScalarAlu0`(`vxc::vfc::isa`) | `ScalarAlu1` |
| Ghostlite (v6e) | TC / SCS / TAC / TEC | 64 / 32 / 64 / 64 | `ScalarAlu0`(`gxc::glc::isa`) | `ScalarAlu1` |
| 6acc60406 (TPU7x) | TC / SCS / TEC | 64 / 32 / 64 | `ScalarAlu0`(`gxc::gfc::isa`) | `ScalarAlu1` |
> **说明 — 6acc60406 (TPU7x) 移除了 TileAccess sequencer。** 二进制中缺少 `gxc::gfc::isa::SparseCoreTac*` 符号,而 `SparseCoreTec*` 和 `SparseCoreScs*` 存在(`nm -C`)。Viperfish 和 Ghostlite 携带全部三个 SparseCore sequencer engine(SCS + TAC + TEC);`6acc60406` 只携带 SCS + TEC。TensorCore sequencer 存在于每一代。
在 V5+ 上,通过固定 proto 路径抵达该 lane,这在二进制嵌入的 assertion string 中逐字可见:`bundle.scalar_alu().scalar_alu_0().branch_relative()`、`…scalar_alu_0().call_absolute()`、`…scalar_alu_0().halt()`。`scalar_alu_0` message 是一个 `oneof`,其成员是 sequencer op(`branch_absolute`、`branch_relative`、`branch_sreg`、`call_absolute`、`call_relative`、`call_sreg`、`halt`、`delay`、`scalar_fence`、`read_register_lcc_low`、`read_register_lcc_high`,以及 ALU compute op)。从符号表枚举出的 Viperfish TC scalar-ALU `oneof`(`vxc::isa::TensorCoreScalarAlu_*`)在约 60 个 ALU compute op 旁边保存完整 control-flow 集合——sequencer 槽和 scalar-ALU lane 是同一条物理 lane,仅通过设置了哪个 `oneof` 成员来区分。
---
## 三层编码模型
一个 control-flow intent 会通过三层结构转换为 bundle bytes:
1. **逐代 proto message 类型**——结构化形式。每个 control-flow op 都是一个不同的 protobuf message:`vxc::isa::TensorCoreScalarAlu_BranchRelative`、`gxc::glc::isa::SparseCoreScalarAlu_CallAbsolute`、`pxc::isa::TensorCoreScalar0_ScalarBranchAbsolute`,等等。在 Jellyfish 上,形式是一个扁平 enum:`platforms_deepsea::jellyfish::isa::ScalarOpcode`(62 个值),其 `ScalarOpcode_descriptor()` 位于 `0x1fa1fc00`。
2. **逐代 compact ref wrapper**——读取侧访问器形式。每个 op 都有一个 `*Compact_<Op>{,ConstRef,Ptr}` 家族,其访问器是基于逐代 `BitfieldsRefImpl`(实际 bit holder)的虚拟 thunk。这些访问器确认字段集合:`…Compact_BranchSregConstRef::x()` 是分支目标寄存器,`…Compact_CallSregConstRef::dest()` 是返回地址寄存器,`…Compact_BranchAbsoluteConstRef::has_delay_slots()` / `delay_slots()` 暴露逐分支 delay count。这些 bit 位于 vtable 背后,而不是 inline——例如 `gfc BranchSregConstRef::has_delay_slots()` 是 vtable `+0x18` thunk,`delay_slots()` 是 `+0x20`,`x()` 是 `+0x40`。
3. **字节发射路径**——把 proto/MCInst 转为原始字节。SparseCore(SCS/TAC/TEC)lane 使用模板化的 `xla::tpu::sparse_core::isa_emitter::EmitBranchOp<Bundle, Op>` / `EmitCallOp<Bundle, Op>` 填充器;TensorCore(Jellyfish)lane 使用 `JellyfishEmitter::EmitScalar*`;BarnaCore-AH lane 使用 `BarnaCoreAddressHandlerEmitter::EmitScalar*`。最终发射是逐代的:JF/PF 使用 `EncoderJf` / `EncoderPf::EncodeBundleInternal`(动态字节打包),V5+ 使用逐槽 `<Slot>Encoder::Encode` 调用(例如 `gfc::isa::TensorCoreScalarAlu0Encoder::Encode` @ `0x1f87b420`),通过通用 bit-packer `BitCopy`(`0x1fa0a900`)写入 64-byte buffer。
> **陷阱 — V5+ LLVM-MC emitter 不贡献任何 branch bit。** MC opcode `BRabs`(505)、`BRind`(507)、`BRrel`(508)、`BRrelrot`(509)、`CALLabs`(514)、`CALLrel`(515)、`HALT`(571)到达 `TPUMCCodeEmitter::getBinaryCodeForInstr`(`0x13c74da0`)后,索引其 jump table,并路由到 **zero-base default**——它们的 `InstBits` record 全为零。真正的 offset、dest/x register 和 predication 由 proto-bundle 的 `EmitBranchOp` / `EmitCallOp` / `EmitImmediate` / `EmitPredicationToSlot` 路径写入。见 [MC-Emitter](mc-emitter.md) 和 [Instruction Name Data](instr-name-data.md)。
---
## Branch / Jump / Call 编码
branch 和 call target 是一个 **signed 20-bit 字段**,落在 bundle 的 **immediate slot 0** 中,这是从 Ghostlite SCS branch 和 call emitter 逐字节解码得到的。`EmitBranchOp<…BranchRelative>`(`0x13a5d3e0`)和 `EmitBranchOp<…BranchAbsolute>`(`0x13a5d220`)字段完全相同——abs/rel 区别只是 opcode discriminator:
```c
// isa_emitter::EmitBranchOp<SparseCoreScalarAlu_BranchRelative> @ 0x13a5d3e0
// (decoded byte-exactly from objdump)
rsi = MCInst.getOperand(0).Imm; // the PC offset / target
if ((uint64_t)(rsi + 0x80000) >= 0x100000) // lea 0x80000(%rsi); cmp 0x100000; jae fail
return RetCheckFail(); // reject if NOT in [-0x80000, +0x7FFFF]
bundle.byte[0x10] |= 0x04; // orb $0x4, 0x10 — set scalar-alu PRESENT bit (bit 2)
rsi &= 0xFFFFF; // and $0xfffff — mask to 20 bits
EmitImmediate<SparseCoreImmediates>(slot=0, rsi); // 20-bit value → immediate slot 0范围检查 (value + 0x80000) < 0x100000 正是 signed-20-bit 测试:−524288 .. +524287。BranchAbsolute 和 BranchRelative 都写入同一字段;opcode 决定这个 20-bit 值被解释为 PC-relative delta 还是 bundle-index absolute target。
call 增加返回地址机制。EmitCallOp<…CallAbsolute>(0x13a5d4c0):
// isa_emitter::EmitCallOp<SparseCoreScalarAlu_CallAbsolute> @ 0x13a5d4c0
// (decoded byte-exactly from objdump)
assert(operand.kind == 5); // cmpb $0x5 — MCImmExpr
offset = GetValueFromSubExpr(GetTPUMCImmExpr(...));
if ((uint64_t)(offset + 0x80000) >= 0x100000) return RetCheckFail(); // SAME 20-bit check
bundle.byte[0x10] |= 0x04; // scalar-alu present bit
EmitImmediate<SparseCoreImmediates>(slot=0, offset); // 20-bit target → imm slot 0
link = GetSregno(MCOperand{kind=1, reg=5}); // movq $0x5; GetSregno — LINK REG = sreg #5
bundle.dword[0x18] = link; // mov %eax, 0x18 — write dest (return-addr) sreg
bundle.byte[0x10] |= 0x01; // orb $0x1 — set dest-present bit (bit 0)
```text
所以 call 是 `{20-bit target in imm slot 0} + {dest = return-address scalar register}`。在 SCS 路径上,link register 硬编码为 `sreg #5`。callee 通过 branch 到 `dest` 中的值来返回——即读取 link register 的 `BranchSreg`。**任何代际都没有专用 return opcode。**
间接形式从寄存器读取计算出的 target:`BranchSreg` 有 `x()` 字段(target sreg);`CallSreg` 同时有 `x()`(target)和 `dest()`(返回地址)。在 Jellyfish 上,间接 branch 改为读取由标量 `SET_BRANCH_TARGET_REGISTER` op 或 TTU `set_btr` op 设置的 Branch-Target-Register——二进制嵌入了冲突 assertion *"Cannot have a scalar SET_BRANCH_TARGET_REGISTER instruction and a TTU set_btr instruction in the same bundle."*
| 字段 | 位置 | 宽度 | 来源 |
|---|---|---:|---|
| branch/call target | immediate slot 0(GF 上 bit 423) | 20(signed) | `EmitBranchOp` @ `0x13a5d3e0`;`EmitImmediate` |
| abs vs rel vs indirect | opcode discriminator | — | 不同 proto message / `ScalarOpcode` |
| call dest(return-addr sreg) | `dest` 字段,bundle `+0x18`(SCS);GF bit 467 | 5 | `EmitCallOp` @ `0x13a5d4c0` |
| call link register(SCS) | `sreg #5` | — | `movq $0x5; GetSregno` @ `0x13a5d560` |
| indirect target(`x()`) | `BranchSreg`/`CallSreg` reg 字段;GF bit 472 | 6(GF) | compact ref `x()` 访问器 |
> **陷阱 — branch target 不在 sequencer 槽字节中。** abs 和 rel 都把 20-bit 值写入 bundle 的一个*共享 immediate slot*(slot 0),只有 sequencer 槽中的一个 opcode bit 表示如何解释它。搜索 sequencer-slot byte window 来寻找 20-bit offset 的解码器不会找到任何东西;offset 在 bundle 的 immediate region 中。这也是 sync op 复用来保存 sflag id/threshold 的同一个 immediate slot。
在 V5+ TensorCore lane 上,abs/rel/call discriminator 写在 sequencer 槽**内部**——上面的 SCS *emitter* 路径是 SparseCore engine;TensorCore engine 有自己的逐 op `BitCopy` 填充器。`gfc` TensorCore 槽从逐 op encoder 和 dispatching `TensorCoreScalarAlu0Encoder::Encode`(`0x1f87b420`)逐字节解码而来。下面每个绝对 bit 位置都是 **LSB-first**——bit 0 是 byte 0 的 least-significant bit,与 [bundle model](bundle-model-overview.md) 记录的通用 `BitCopy(dst, dst_bit, src, src_bit, nbits)` packer(`0x1fa0a900`)一致。每个 TC sequencer op 都先在 **bit 483 写一个 6-bit opcode-HIGH “family”字段**,并在 **bit 478 写一个 5-bit opcode-LOW “discriminator”字段**;branch/call-immediate family 将 opcode-HIGH 固定为 `0`,并通过 LOW 字段选择 op:
```c
// EncodeTensorCoreScalarAlu0BranchAbsolute @ 0x1f87f5c0 (decoded byte-exactly)
BitCopy(slot, 483, 0, 6); // opcode-HIGH "family" = 0 (branch/call-immediate)
BitCopy(slot, 478, 4, 5); // opcode-LOW discriminator = 4 → BranchAbsolute
// …if x() present:
BitCopy(slot, 472, x_reg, 6); // 6-bit operand / 2nd-source field四种 immediate branch/call discriminator 除 LOW 值外字段完全相同;register-indirect 形式(BranchSreg/CallSreg)则把 opcode 放在 HIGH 字段。call 的返回地址 sreg 落在 bit 467 的 5-bit 字段中,间接 target / link-source sreg 落在 bit 472 的 6-bit 字段中:
| Op | oneof case | opcode-HIGH @483(w6) | opcode-LOW @478(w5) | Encoder |
|---|---|---|---|---|
BranchAbsolute | 62 | 0 | 4 | 0x1f87f5c0 |
BranchRelative | 63 | 0 | 5 | 0x1f87f660 |
CallAbsolute | 65 | 0 | 6 | 0x1f87f7e0 |
CallRelative | 66 | 0 | 7 | 0x1f87f8e0 |
BranchSreg | 64 | 4 | (x at 472,dest n/a) | 0x1f87f700 |
CallSreg | 67 | 5 | (x at 472,dest at 467) | 0x1f87f9e0 |
CallAbsolute/CallRelative 还会把返回地址 sreg 写入 bit 467 的 5-bit 字段(BitCopy(slot, 467, dest, 5)),并把 call-target / link-source sreg 写入 bit 472 的 6-bit 字段——这确认 SCS 路径的返回地址机制也逐字节存在于 TensorCore lane 上。opcode-HIGH 0 family 也被非控制 sequencer op 共享(ScalarFence LOW=0、Delay LOW=3、SetTag LOW=8、ReadRegisterLccLow LOW=10),因此 LOW discriminator 只有在 HIGH=0 family 内才单独识别 branch/call。该映射与 Bundle GF §Sequencer Slot 一致。
说明 — SCS 和 TC lane 共享编码契约,但不共享代码路径。 SparseCore(SCS/TAC/TEC)sequencer 通过 LLVM-MC 驱动的
isa_emitter::EmitBranchOp/EmitCallOp模板抵达其字节;TensorCore sequencer 通过由TensorCoreScalarAlu0Encoder::Encodedispatch 的 proto-bundlegfc::isa::Encode*填充器抵达其字节。二者都把 20-bit signed target 落在 immediate slot 0,并把 abs/rel/call discriminator 落在 sequencer 槽中,但重实现不能假设一个函数同时发射两者——它们是按TpuSequencerTypekeyed 的独立填充器家族。
Jellyfish branch/call discriminator 是逐字节解码出的连续 ScalarOpcode 范围:
ProtoUtils::IsBranch(op): (op & ~3) == 8 // and $0xfffffffc; cmp $0x8 → {8,9,10,11}
ProtoUtils::IsCall(op): (op & ~3) == 0xc // and $0xfffffffc; cmp $0xc → {12,13,14,15}
```text
四个 branch opcode(`ScalarBranchRelative` / `ScalarBranchAbsolute` / `ScalarBranchIndirect` + 一个)和四个 call opcode(`ScalarCallRelative` / `ScalarCallAbsolute` / `ScalarCallIndirect` + 一个)。每个范围的前三个名称从 `.rodata` 字符串确认;`8..11` / `12..15` 内部精确的 integer↔name 绑定从连续性推断(MEDIUM)。
---
## Delay-Slot 字段
每个 branch 和 call 都携带一个 delay-slot count——即 branch 后、PC 改变生效前仍会 issue 的 bundle 数量。访问器存在于每个 branch/call op 上(`scalar_alu_0.branch_absolute().has_delay_slots()`、`…call_sreg().has_delay_slots()` 等),LLVM-MC verifier 对该值设边界:
```text
delay_slots_op.getImm() >= 0 && delay_slots_op.getImm() <= 5因此 delay-slot count 是一个 0..5 字段(3 bits)。packer 会在 branch bundle 后追加这么多个空 bundle;逐代 base count 位于 TpuSubtarget +0x914(其精确逐代值未提取——MEDIUM)。独立 Delay op(…Compact_Delay::delay_count())是不同的 pipeline-balancing NOP-with-count,而不是逐分支 delay。在 V5+ 上没有 in-bundle delay-slot bit——count 纯粹是 packer pad-count(GF sequencer 槽没有 delay-slot 字段)。JF / JF-BCAH / PF-TC / PF-BCS / PF-BCChan / VF-TC / GL-TC 存在 MarkBranchDelaySlot(bool) emitter 方法;6acc60406(gfc)不存在此类符号,与 V5+ 无编码 delay 模型一致。
条件编码——Predication 就是 Condition
没有单独的 branch-condition 字段。sequencer 槽的 predication 字段兼作条件分支条件。二进制嵌入了 assertion !scalar_alu_0.has_predication() || scalar_alu_0.predication() == PREDICATION_ALWAYS——无条件分支必须具有 ALWAYS predication 或没有 predication——而 query IsConditional(scalar_alu_0)(ProtoUtils::IsConditional)会测试槽的 predication,以决定 branch 是否为条件分支。
来自 .rodata 字符串的 predication enum 值:
| 值 | 含义 |
|---|---|
PREDICATION_ALWAYS | 无条件(predicate true) |
PREDICATION_NEVER | 槽被门控关闭(NOP / never-execute) |
PREDICATION_OR_NEVER | predicate-OR 变体 |
PREDICATION_OR_INVERTED_NEVER | 带 inversion 的 predicate-OR |
PREDICATION_SLOT_NEVER | 槽级 never(V5+ 双谓词) |
条件 branch 在 predication 字段中编码谓词寄存器索引;当且仅当该谓词为 true 时 branch 被 taken(或者,对于 OR/inverted 变体,组合谓词为 true)。在 Jellyfish 上,predication 字段是每槽 5 bits,常量逐字节精确:
HardwareBundleBits::kPredicateRegisterCount = 15 (0xb834cf4: 0x0f)
HardwareBundleBits::kAlwaysExecute = 15 (0xb834cf8: 0x0f)
HardwareBundleBits::kNeverExecute = 31 (0xb834cfc: 0x1f)
```text
因此值 `0..14` 是谓词寄存器 P0..P14,`15` 是 always-execute,`31` 是 never-execute。空(未填充)槽保持为 `kNeverExecute = 31`,这是规范空槽编码——见 [Bundle Model §Empty-Slot Convention](bundle-model-overview.md#empty-slot-and-nop-convention)。
在 V5+ 上,predication 是一个 **7-bit 字段**,由 `TPUMCCodeEmitter::encodePredicateOperand`(`0x13c77c40`)逐字节精确编码:
```c
// encodePredicateOperand @ 0x13c77c40 (decoded byte-exactly)
reg = reg_encoding_table[op.reg]; // movzwl (%rdi,%rcx,2)
APInt::insertBits(out, reg, /*pos=*/0, /*width=*/4); // bits [0:3] = predicate-reg index
if (flag_byte & 1) // test $0x1, %r14b
out.flags |= 0x10; // bit [4] = predicate sense / present
mode = (flag_byte >> 5) & 3; // shr $0x5; and $0x3
APInt::insertBits(out, mode, /*pos=*/5, /*width=*/2); // bits [5:6] = predication MODE也就是 {4-bit reg, 1-bit sense, 2-bit mode} = 7-bit 字段,是 Jellyfish 5-bit 字段的超集。四种 mode 对应 ALWAYS / NEVER / OR_NEVER / OR_INVERTED_NEVER。当 branch target 是尚未解析的 label 而不是 inline immediate 时,操作数走 LLVM-MC fixup 路径(getMachineOpValue @ 0x13c777e0,label relocation 使用 24-bit operand class)。
说明 — 6acc60406 (GF) 添加了双谓词。
6acc60406(gfc)把逐槽 4-bit 寄存器索引替换为专用双谓词槽(TensorCorePredicates,bits 496..505 上的两项(reg, invert)池)加逐槽 2-bit selector(sequencer 槽 @ bit 489)。GF 条件 branch 可由两个逐 bundle 谓词中的任意一个 guard;GF SparseCore 添加了读取旋转谓词环的旋转谓词 branch(BranchRelativeRotatingPreg)。见 Bundle GF §Dual-Predicate Slot。
Hardware Loop 和 Sync 的位置
两种循环模型按代际清晰拆分。Jellyfish / Dragonfish 没有 loop-counter register;hardware loop 是由 AddressHandlerProgramBuilder::BeginLoop(0xfa90d40)/ EndLoop(0xfa91300)构建的软件 bundle-index backward branch:BeginLoop 记录当前 bundle index(并 assert 没有嵌套循环处于活动状态),EndLoop 发射一个回到该 index 的 backward branch。Viperfish / Ghostlite / 6acc60406 有 64-bit hardware loop counter(LCC),由 sequencer op ReadRegisterLccLow / ReadRegisterLccHigh(低 + 高 32-bit half)读入标量寄存器;循环体使用一个由 LCC 值计算出的谓词 guard 的条件 BranchRelative。LCC read op 存在于 VF/GL/GF 的 TC 和 SCS 上,在 JF/PF 上缺失(nm -C)。完整循环细节见 Hardware Loop-Counter。
在大多数代际上,sync op 不位于 sequencer scalar slot 中。在 Jellyfish 上,sync-flag work 从向量路径发射(JellyfishEmitter::EmitVectorSyncFlagSet/Add/...);在 Pufferfish BCS 上,专用 sync op 位于 Scalar0 和 Scalar1 中;在 V5+ 上,独立的 ScalarMisc lane(与 ScalarAlu0 sequencer lane 分离)拥有 sync family。barrier op 编码器 EmitBarrierSync<…ScalarMisc>(0x13a5f100)设置 ScalarMisc present bit,并通过 ScalarY 操作数写入 sflag id/threshold,其 immediate 形式复用 branch offset 使用的同一个 20-bit immediate slot 0。逐代 sync-slot 位置和 op 名册在配套页面中制表。
交叉引用
- Bundle Model — VLIW bundle、按
(TpuVersion, TpuSequencerType)keyed 的 codec,以及空槽kNeverExecute约定。 - Sequencer Ops Per Gen — 逐(generation × sequencer-type)的 control-flow op 清单以及
TpuSequencerTypeenum。 - Hardware Loop-Counter — JF 软件 bundle-index loop 对 V5+ LCC counter read。
- SPU / Scalar Slot — 与 sequencer 共享 lane 的 scalar-ALU compute op。
- MC-Emitter — 为什么 V5+ branch/call/halt opcode 路由到 zero-base default。
- Bundle GF — 字节精确的 GF sequencer 槽(selector @ 489,opcode-HIGH @ 483)和双谓词槽。