Skip to content

谓词寄存器文件与逐槽谓词化

本页中的每个偏移、地址、位位置和常量,都从 libtpu-0.0.40-cp314 wheel 中的 libtpu.so 逐字节读取而来(BuildID md5 89edbbe81c5b328a958fe628a9f2207d,未 strip)。其他版本会有所不同。

摘要

TPU VLIW 核心以两种粒度对执行进行谓词化,二者由两个物理上不同的寄存器文件支撑。标量谓词文件Preg / LLVM PPRRegClass)保存单比特布尔值,用来门控整个 bundle 槽以及条件分支;向量掩码文件Vmreg / LLVM MPRRegClass)保存逐 lane 布尔值,用来门控一个向量操作内部的各个 lane。本页记录标量谓词文件:各代的寄存器数量、逐槽谓词字段编码、逐槽谓词化模型,以及 predicate-set / predicate-combine 操作族。向量掩码文件有独立页面;这里通过对比让标量文件更易读。

模型是熟悉的谓词化 VLIW 模型:每个功能槽都携带一个 guard,空槽编码是“predicate = never”,而不是单独的 valid bit。但有两个 TPU 特性是重实现者必须精确复现的。第一,字段会随硅片代际增长:Jellyfish/Dragonfish 使用 5-bit 逐槽字段(4-bit 寄存器索引 + 1 个取反 bit),而 Viperfish 之后使用 7-bit 字段(4-bit 索引 + 1 个取反 + 2-bit 模式/扩展),这是从 TPUMCCodeEmitter::encodePredicateOperand 逐字节解码得到的。第二,6acc60406(gen 5,外部名称 TPU7x)打破了逐槽模型:bundle 不再让每个槽携带完整寄存器索引,而是携带一个专用 TensorCorePredicates 槽,在 bit 496..505 保存一个两项谓词池(pred_0pred_1),每项都有自己的 inversion bit;逐槽字段则保存一个 2-bit selector(例如 sequencer 槽的 selector @ bit 489),索引到该池中。同一 bundle 中出现第三个不同谓词会成为编码期错误。

对重实现而言,契约如下:

  • 各代谓词寄存器数量:JF/DF/PF-TensorCore 通过 HardwareBundleBits 常量提供 15 个命名寄存器(P0..P14);BarnaCore subtarget 以及所有 V5+ 上存在 16 值可编码索引命名空间(getNumPredicateRegisters() 返回 16),其中 chip_parts proto 报告 PREG = 14 作为可分配硬件数量(见各代号常量)。
  • 两个哨兵值:kAlwaysExecute = 15(槽无条件运行)和 kNeverExecute = 31(槽被丢弃 / 为空)。
  • 逐槽谓词字段布局:传统 5-bit(index@0:3,negate@4)对 V5+ 7-bit(index@0:3,negate@4,mode@5:6)。
  • 逐槽谓词化模型:每个已填充的功能槽都携带自己的谓词字段(6acc60406 除外,它的逐槽字段是指向两个 bundle 级池项的 2-bit selector)。
  • 操作族:16 个标量 compare→predicate 操作(带 PdstFieldCompare*),以及四个 combine 操作(PredicateOr / PredicateNegate / PredicateMove / PredicateImmediate)——任何代际都没有原生 predicate-AND
标量谓词文件Preg / deepsea::mnemonics::PregNumber / LLVM TPU::PPRRegClass @ 0x2192f380
向量掩码文件Vmreg / VmregNumber / LLVM TPU::MPRRegClass @ 0x2192f0c0(独立页面)
JF/DF/PF-TC 数量15 个命名项(P0..P14)— HardwareBundleBits::kPredicateRegisterCount @ 0xb834cf4 = 15
BC + V5+ 数量16 — *Subtarget::getNumPredicateRegisters() 返回 16(Bc/Vfc/Glc/Gfc)
Always-execute 哨兵kAlwaysExecute = 15 @ 0xb834cf8(printer 中的 LLVM reg #6)
Never-execute 哨兵kNeverExecute = 31 @ 0xb834cfc(空槽标记)
传统字段编码/解码DecoderJf::GetPredicateRegister @ 0x1e84eae0val & 15),GetPredicateValue @ 0x1e84eb00(val & 16)==0
V5+ 字段编码器TPUMCCodeEmitter::encodePredicateOperand @ 0x13c77c40(4+1+2 = 7-bit)
旋转谓词6acc60406TPUGfcSubtarget::hasRotatingPredicates() @ 0x13c62c20 = 1;其他全部为 0
原生 predicate-AND无 — 每个 subtarget 上 *Subtarget::hasPredicateAnd() = 0
置信度CONFIRMED(字节锚定),除非某行另有说明

两个不同的布尔寄存器文件

硬件有两个布尔寄存器文件,而不是以两种方式查看的同一个文件;混淆二者是重实现时的第一个陷阱。标量谓词文件门控槽和分支;向量掩码文件门控向量操作内部的 lane。它们可以组合:一个 vector store 槽既可以被标量谓词化(这个槽是否运行?),也可以被 lane 掩码化(哪些 lane 写入?)。

标量谓词(Preg向量掩码(Vmreg
IR 强整数类型deepsea::mnemonics::PregNumberdeepsea::mnemonics::VmregNumber
LLVM 寄存器类TPU::PPRRegClass @ 0x2192f380TPU::MPRRegClass @ 0x2192f0c0MPR_3Tuple/MPR_4Tuple 跨度)
寄存器名称根P0..P14(逐 core 的 _0/_10 后缀)M0M8_M9_M10_M11(tuple)
宽度1 bit逐(lane × sublane-group)——掩码 word 的 tuple
门控对象bundle 槽、标量 ALU commit、条件分支masked vector store/load、masked vector ALU lane commit、select/blend
调试访问器Read/WritePredicateRegister(core[, seq], int)Read/WriteVectorMaskRegister(core[, seq], sublane_group, lane)

3 参数向量掩码调试访问器(coresublane_grouplane)证明 Vmreg 是按(lane × sublane-group)寻址的,而 Preg 是单个 bit。两个文件存在于每一代:Read*Register 调试方法在 JXC、PXC 和 VXC 驱动上都存在。JF/DF 还额外暴露一个独立的 BarnaCore 谓词子文件(Read/WriteBarnaCorePredicateRegister),其寄存器范围未被单独解码(PF BarnaCore subtarget 报告 16)。

陷阱 — predicate ≠ mask。 如果重实现分配一个布尔文件,并把槽 guard 和 lane mask 都路由到其中,就会错误编码每个 masked vector op:槽的标量谓词和操作的 lane mask 是独立操作数,来自独立寄存器命名空间。Preg/Vmreg 拆分是核心架构事实。

向量掩码文件(通过 VectorAlu*CreateMask/CreateLaneMask/CreateSublaneMask 产生,通过 mlir::llo::VectorMaskAnd/Or/NegateOp 组合)位于 vcreate_mask / M-Register。本页剩余部分讲标量谓词文件。


各代谓词寄存器数量

数量通过两条路径暴露,二者可归结为同一个 4-bit 索引加特殊编码。传统 proto 路径使用 .rodata 中的 HardwareBundleBits 常量;LLVM 路径使用每个 subtarget 的 getNumPredicateRegisters() override。

三个 Jellyfish 常量在 0xb834cf4 被字节验证为三个小端 uint320f000000 0f000000 1f000000

c
// platforms_deepsea::jellyfish::isa::HardwareBundleBits  (.rodata)
kPredicateRegisterCount = 15;   // @ 0xb834cf4  — count of named usable Pregs P0..P14
kAlwaysExecute          = 15;   // @ 0xb834cf8  — the always-true sentinel (slot runs)
kNeverExecute           = 31;   // @ 0xb834cfc  — slot is a NOP / not executed
```text

V5+ subtarget 各自把 `getNumPredicateRegisters()` override 为字面量 `16`(每个都反编译为单个 `return 16;`):

```c
llvm::TPUBcSubtarget ::getNumPredicateRegisters()  @ 0x13c59780  -> 16
llvm::TPUVfcSubtarget::getNumPredicateRegisters()  @ 0x13c5f6e0  -> 16
llvm::TPUGlcSubtarget::getNumPredicateRegisters()  @ 0x13c615c0  -> 16
llvm::TPUGfcSubtarget::getNumPredicateRegisters()  @ 0x13c630e0  -> 16

基类 llvm::TPUSubtarget 不提供 override——JF/DF/PF-TensorCore 继承 15 寄存器的 HardwareBundleBits 模型。1516 并不矛盾:15可分配命名寄存器数量(ValidatePredicateRegister 只接受索引 < 15cmp $0xf; jb),而 16可编码 4-bit 索引命名空间 0..15 的大小,其中索引 15 映射为 always-execute 哨兵,即常真寄存器。

代号TpuVer家族getNumPredicateRegisters命名可用寄存器备注
Jellyfish0JXC15(HardwareBundleBitsP0..P14+ always(15),never(31)
Dragonfish1JXC15(继承 JF)P0..P14Jellyfish 模型的别名
Pufferfish2PXCTC 15(base)/ BC 16(TPUBcSubtargetP0..P14(TC)/ P0..P15(BC)BC subtarget 提升为 16-index
Viperfish3VXC16(TPUVfcSubtargetP0..P15完整 16-index 命名空间
Ghostlite4GXC/GLC16(TPUGlcSubtargetP0..P15
6acc60406 (TPU7x)5GXC/GFC16(TPUGfcSubtargetP0..P15+ 旋转谓词,+ 双槽池

说明 — always-execute 寄存器在 proto/HardwareBundleBits 视图中显示为索引 15,但在 MC printer 中显示为 LLVM register #6printPredicateOperandAux @ 0x13c73c80 测试 reg == 6 并且不发出 @Pn)。这两套编号方案是同一个架构“未谓词化”寄存器的不同 register-ID 空间;解码器不能假设 LLVM 寄存器号等于硬件索引。


逐槽谓词字段编码

有两种编码视图,映射到相同硬件 bit,并按代际区分。

传统 5-bit 字段(Jellyfish / Dragonfish)

DecoderJf::GetPredicateRegister0x1e84eae0)和 GetPredicateValue0x1e84eb00)反编译为单个表达式,固定了布局:

c
// DecoderJf::GetPredicateRegister(uint8 v) @ 0x1e84eae0
return v & kPredicateRegisterCount;          // v & 15  -> 4-bit reg index (0..14; 15 = always)

// DecoderJf::GetPredicateValue(uint8 v) @ 0x1e84eb00
return (v & (kPredicateRegisterCount + 1)) == 0;   // (v & 16) == 0  -> bit 4 is the negate/value bit
```text

因此 Jellyfish 的逐槽谓词字段是 **5 bits**

| Bits | 字段 | 含义 |
|---|---|---|
| `[0:3]` | register index | `0..14` → P0..P14;`15` → always-execute(`kAlwaysExecute`) |
| `[4]` | polarity / negate | “value” bit;清零 = 按原样采用谓词 |
|5 bit 置位 | `0x1F = kNeverExecute` | 槽不运行(空槽填充值) |

BarnaCore vector-load 解码路径还会提取完整 5-bit 字段(`shld $0x2; and $0x1f`),并与 `kNeverExecute = 31` 比较以跳过槽。

### V5+ 7-bit 字段(Viperfish / Ghostlite / `6acc60406`)

`TPUMCCodeEmitter::encodePredicateOperand`(`0x13c77c40`)用三次 deposit 构建字段——确认了 [MC-Emitter](mc-emitter.md) 记录的 `4 + 1 + 2` 布局:

```c
// encodePredicateOperand @ 0x13c77c40 (decompiled, exact)
//   a1 = reg-encoding table, a2 = MCInst operands, a3 = per-slot operand index, a4 = dst APInt
flags = ops[a3].flags;                                  // operand flags word
APInt::insertBits(dst, regEncTable[ops[a3].preg], /*pos=*/0, /*width=*/4);  // [0:3] reg index
if (flags & 1)
    dst.word0 |= 0x10;                                  // [4] negate / inversion
return APInt::insertBits(dst, (flags >> 5) & 3, /*pos=*/5, /*width=*/2);    // [5:6] mode/extension
Bits字段含义
[0:3]register index4-bit,0..15(经过置换——见下文)
[4]negate / inversion通过 |= 0x10 设置
[5:6]2-bit mode / extensionpredicate-bank / rotating-stage selector;非旋转代码中为 0,在 6acc60406 上驱动旋转谓词阶段

四种 [5:6] mode 对应 Sequencer Slot 记录的 PREDICATION_ALWAYS / NEVER / OR_NEVER / OR_INVERTED_NEVER enum。printer(printPredicateOperandAux @ 0x13c73c80)确认了读取侧:reg == 6(LLVM always)不发出 @Pnflags & 1 发出 ! 取反前缀;flags & 0x7E(bit 1-6 置位)路由到 BarnaCore predicate printer。

怪癖 — 硬件寄存器索引是置换,不是原始 0..15。 pxc::mnemonics::PredicationIsPositive0x1d2e98c0)用 0x7FFF(bit 0-14)掩码正值,用 0x7FFF0000(bit 16-30)掩码取反值:Predication enum 是 0..14 = +P0..P1416..30 = ~P0..~P14GetPregNumberpxc @ 0x1d2e9920vxc @ 0x1d32ad40glc @ 0x1d36b100gfc @ 0x1d3ab9a0)是一个 16 路 jump table,把置换后的硬件 enum 值映射回规范 Preg 索引。重实现若把原始寄存器索引发射进字段,会选择错误谓词;置换表才是编码/解码映射。每代完整 16 项置换未 dump(只有 dispatch 形状和 Pufferfish 开头 0→none, 14, 11, 4, 12, 9, 2, 3, 7, 1, 5, … — MEDIUM)。

predicate-OR 和 V5+ 槽谓词化使用的宽谓词源操作数是 PredicationOr,一个稠密 enum [0, 31](32 个值 = 16 regs × {positive, negated}),由 pxc/vxc/gxc::glc/gxc::gfcNameOfDenseEnum<…PredicationOr_descriptor, 0, 31> 实例确认。


逐槽谓词化模型

bundle 中每个可执行功能槽都携带自己的独立谓词字段——谓词化是逐槽的,而不是逐 bundle 的。因此同一个周期中,一个 bundle 可以让不同槽在不同谓词下运行(逐槽谓词化 VLIW 发射)。证据是每种槽类型各自的逐槽 SetPredicate / EmitPredicationToSlot 模板实例:

text
Pufferfish (PXC) — xla::pufferfish::(anon)::SetPredicate<Slot> for each of:
  TensorCoreScalar0/1, TensorCoreVectorAlu0/1, TensorCoreVectorExtended0/1,
  TensorCoreVectorLoad, TensorCoreVectorStore, TensorCoreVectorResult0/1, TensorCoreMisc
  (+ BarnaCoreSequencerScalar0/1, BarnaCoreChannelVectorAlu1, … for BC)

V5+ (VXC/GXC) — isa_emitter::EmitPredicationToSlot<PredicateDest, Predication, Slot> for each of:
  SparseCoreScalarAlu, SparseCoreScalarMisc, SparseCoreStream, SparseCoreDma, SparseCoreTecDma,
  SparseCoreTecVectorAlu, SparseCoreTecVectorExtended, SparseCoreTecVectorLoad,
  SparseCoreTecVectorResult, SparseCoreTecVectorStore  (TensorCore side mirrors these)
```text

每个已填充槽独立选择一个 guard 谓词寄存器及其 polarity;编码器把每个槽的谓词字段写入,空槽则填充 `kNeverExecute = 31`(NOP 填充——见 [Bundle Model §empty-slot convention](bundle-model-overview.md#empty-slot-and-nop-convention))。LLO IR 中谓词的 def-use 由 `ScalarInstruction::add_consumes_predicate_register`(`0x1e851be0`)和 `add_produces_predicate_register`(`0x1e851c60`)跟踪,供 bundle packer 的 hazard analysis 使用;`ValidatePacking` 会重新检查每个槽字段都在 `{0..14, 15, 31}` 范围内。

### `6acc60406` 双谓词池

`6acc60406`(gfc)是唯一带有专用 **bundle 级**谓词编码器槽的代际。它不是让每个功能槽携带完整寄存器索引,而是让 bundle 携带一个 `TensorCorePredicates` 容器(`asic_sw::deepsea::gxc::gfc::isa`,通过 `TensorCorePredicatesCompactPtr` 访问),保存两个引用——`pred_0` 和 `pred_1`,每个都是一个 `PredicationSlot` enum 值(`0..15` 寄存器索引)加独立的 `inversion` bit——放在 64-byte bundle 的 bit 496..505;逐功能槽字段则保存一个 2-bit selector(例如 sequencer 槽的 selector @ bit 489),索引到这个两项池。容器的 opcode descriptor 是 `NoInstructionsH`(纯容器,不是可调用 op)。

该池是共享资源:一个 bundle 在其所有子槽中最多能引用两个不同的 `(register, inversion)` 对。第三个不同谓词会触发编码期错误,二进制中嵌入了逐字字符串:

```text
"Field pred_0 of TensorCorePredicates PredicationSlot is an enum and value 0x%x does not match any encodings."
"Field pred_1 of TensorCorePredicates PredicationSlot ..."
"bundle predication overflow; both predicate slots are already taken
 [pred_0: %s, inversion: %d], [pred_1: %s, inversion: %d];
 attempted to place another predicate: [%s, inversion: %d] in the same bundle."

PredicationSlot 是稠密 enum [0, 15](16 个寄存器索引);inversion 由每个池项单独携带。这种打包方式让 6acc60406 能把更宽的 compute fabric 塞进同一个 64-byte bundle——见 Bundle GF §dual-predicate slot


Predicate-Set 操作(标量比较 → 谓词)

标量 ALU 通过 compare op 产生谓词。每个 compare op 都携带显式 PdstField(目标 Preg)以及 XField / YField(两个标量操作数)。完整族包含 16 个 opcode,按类型和运算符拆分:

分组操作数量
浮点比较CompareFloatingPoint{Eq, Neq, Gt, Gte, Lt, Lte}6
整数 eq/ne(无类型)CompareInteger{Eq, Ne}2
有符号整数顺序CompareSignedInteger{Gt, Gte, Lt, Lte}4
无符号整数顺序CompareUnsignedInteger{Gt, Gte, Lt, Lte}4

整数相等性不需要 signedness;只有顺序比较拆分为 signed 与 unsigned,因此该族是 6 + 2 + 4 + 4 = 16。每个都会发射四个字段:<Op>Opcode<Op>PdstField(目标 Preg)、<Op>XField<Op>YField。这些存在于每个 V5+ engine 的两个标量 lane(Alu0 和 Alu1)上(TensorCoreScalarAlu0/1SparseCoreScalarAlu0/1SparseCoreTecScalarAlu0/1SparseCoreScalarMisc)——任一标量 lane 都能产生谓词。已确认的编码器包括 EncodeTensorCoreScalarAlu0CompareIntegerEq0x1f87e6c0)和 EncodeSparseCoreScalarAlu0CompareFloatingPointEq0x1eb6d3a0)。向量侧则产生 Vmreg 掩码,而不是谓词。


Predicate-Combine 操作(标量谓词逻辑)

标量谓词文件通过四个专用 emitter op 组合,这些 op 从 Pufferfish TensorCore emitter 解码得到(其他代际类似):

操作地址语义
EmitPredicateOr(dst, a, b)0x14105300dst = a OR b;硬件暴露的唯一逻辑组合原语
EmitPredicateNegate(src, dst)0x141052e0dst = NOT src;实现为通过 vtable +0xe0 dispatch 的 dst | 0x100000000(高 dword 设置 negate flag)
EmitPredicateMove(src, dst)0x141052c0dst = src(谓词复制)
EmitPredicateImmediate(dst, value)0x141033e0dst = const(true/false 物化)

EmitPredicateNegate 函数体逐字节确认了 negate 机制:

c
// PufferfishTensorCoreEmitter::EmitPredicateNegate @ 0x141052e0
return (*(vtable + 0xe0))(this, dst, src | 0x100000000LL);   // high dword = negate flag
```text

ISA 级 `PredicateOr` opcode(`EmitPredicateOr` lower 到的对象)以 `Encode/Decode<engine>PredicateOr` 形式存在于 TensorCore、SparseCore、SparseCoreTec 和 Misc 标量 lane;其操作数是 `PredicationOr`(32 值稠密 enum)。在 sequencer 级别,它显示为 `ScalarPredicateOrH` / `PredicateOrH`。

> **怪癖 — 没有原生 predicate-AND。** 每个 subtarget 上 `hasPredicateAnd()` 都返回 `0`(`TPUBcSubtarget` @ `0x13c59260`、`TPUVfcSubtarget` @ `0x13c5ef60`、`TPUGlcSubtarget` @ `0x13c60e40`、`TPUGfcSubtarget` @ `0x13c629c0`,全部是 `xor eax`/`return 0`)。编译器通过 De Morgan 合成 `a AND b`:`NOT(NOT a OR NOT b)`,使用 `PredicateOr` 加 `Predication` 操作数上的 negate bit(这让 inversion 免费)。期望硬件 AND opcode 的重实现找不到任何此类 opcode。

---

## 旋转谓词(仅 `6acc60406`)

`hasRotatingPredicates()` 在 `6acc60406`(gfc)上为 `1`,其他地方均为 `0`:

```c
TPUBcSubtarget ::hasRotatingPredicates() @ 0x13c59400 -> 0
TPUVfcSubtarget::hasRotatingPredicates() @ 0x13c5f1e0 -> 0
TPUGlcSubtarget::hasRotatingPredicates() @ 0x13c610c0 -> 0
TPUGfcSubtarget::hasRotatingPredicates() @ 0x13c62c20 -> 1   // 6acc60406 only

旋转谓词提供一圈谓词寄存器,硬件在每次循环迭代时前进一个位置,用于启用/禁用软件流水阶段(prolog / steady-state / epilog),无需显式 guard 代码。相关机制是 TPURotatingPredicateModuloExpanderexpandpeelPrologsinitializeRotatingPredicatesAndPredicatePrologspropagateRotatingPredicategenerateBranchRotate);在没有硬件特性的代际(Vfc/Glc)上,TPURotatingPredicateEmuModuloExpander 会在 EnableRotatingPredicateEmulation0x224e1148)启用时,把旋转谓词循环 lowering 为显式谓词寄存器 move。encodePredicateOperand 中的 2-bit [5:6] 扩展是编码旋转阶段偏移的自然位置;仅 6acc60406 拥有的 sequencer op SetRotatingPredicateRegisterBranchRelativeRotatingPreg 驱动这条环(见 Hardware Loop-Counter)。环深度根据 initiation interval 和 stage count 为每个软件流水循环计算,而不是静态常量。


交叉引用

  • SPU / Scalar Slot — 通过 Compare* 族产生谓词并承载 PredicateOr/Negate/Move/Immediate op 的标量 ALU lane。
  • vcreate_mask / M-Register — 本页用作对比的向量掩码文件(Vmreg/MPR):逐 lane 掩码、CreateMask/CreateLaneMask/CreateSublaneMaskVectorMaskAnd/Or/Negate