Skip to content

ResultFifo 和 ArchRegister 枚举

本页中的每个偏移、值和地址都按字节精确读取自 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(BuildID md5 89edbbe81c5b328a958fe628a9f2207d)。其他版本会有所不同。

摘要

TensorCore LLO 层命名了两个扁平枚举,供每操作码元数据表(opcode_info_big)和跨 lane 单元(XLU)调度器持续引用:ResultFifo,即 bundle 槽可以推入或弹出的 25 个硬件结果 FIFO;以及 ArchRegister,即每操作码读/写寄存器列表所表达的 50 成员物理架构寄存器编号。二者都不同于虚拟 LLO 寄存器空间。ResultFifo 是硬件资源枚举(matmul 暂存 bank、transpose bank、EUP 和跨 lane drain);ArchRegister 是实例解析后的物理槽索引,位于 ArchRegno 编号 中记录的按世代架构寄存器编号之下的最底层。

如果你熟悉 LLVM,ArchRegister 最接近的类比是 MCRegister,即目标物理寄存器编号;而 ResultFifo 则像一组架构可见队列的固定池,例如 x87 栈或 SIMD 累加器文件,只不过这里有若干 FIFO 是 banked 的(多个物理实例由运行时实例索引选择)。枚举本身不携带每目标尺寸;把 ArchRegister 序号转换成可打印 v3 / s12 / vm5 / p2 的编号是在 Target 构造时建立的(参见 ArchRegno 编号)。本页按名称固定这两个枚举,解码 banked 与 single 的划分,并追踪同时读取它们的一个消费者:LloXluGraphOptimizer::ComputeXluOperations,它遍历依赖图,并为每个跨 lane 操作码发出一个 XLU 操作,同时附带其读取和写入的 ArchRegister 集合。

对重实现而言,契约是:

  • 25 个 ResultFifo 序号及每个 FIFO 的含义,加上把 (ResultFifo, instance) 对映射到扁平物理 FIFO id 的 FifoInstance bank 算术,以及每个 FIFO 在各版本的深度(ResultFifoEntryCount)。
  • 5 成员 RegisterType 枚举和 50 成员 ArchRegister 物理编号:哪 12 个序号是多实例 bank,哪 38 个是单寄存器,以及每个解析到的 (RegisterType, regno) 对。
  • ComputeXluOperations:拓扑遍历、21 个操作码的 XLU 选择、opcode_info_big +0x08 读取列表 / +0x14 写入列表构造,以及它为每个操作发出的 variant<TransposeTile, RpuOperation, XluControlOperation>
ResultFifo 字符串化ResultFifoToString @ 0x14441340 — 25 个内联立即数分支
ResultFifo 实例解析器internal::FifoInstance @ 0x14446040 — 5 个 banked 分支 + 直通
ResultFifo 深度ResultFifoEntryCount @ 0x1d631520 — 25 个分支,TpuVersion < 6 门控
RegisterType 字符串化器RegisterTypeToString @ 0x1d640560RegisterTypeToMnemonic @ 0x1d640600
ArchRegister 实例解析器internal::ArchRegisterInstance @ 0x126b3240 — 12 个 banked 分支 + 默认直通
每操作码元数据opcode_info_big @ 0x227b5570LloOpcodeBigInfo[461](符号大小 0x326c = 12908 B = 461 × 28),28 字节步幅
XLU 消费者LloXluGraphOptimizer::ComputeXluOperations @ 0x126d9780

RegisterType

RegisterType 是 5 成员寄存器文件类别枚举。两个字符串化器逐字节精确地拼写它,二者都是普通 switch 表:

  • RegisterTypeToString @ 0x1d640560 把长名称写入小字符串缓冲区(长度存储在 buffer[23])。
  • RegisterTypeToMnemonic @ 0x1d640600 写入构建架构寄存器名称所用的一或两个字符前缀。
序号ToStringToMnemonic长度含义操作码计数
0"none"""4 / 0无 GPR 结果216
1"pregs""p"5 / 1谓词寄存器11
2"sregs""s"5 / 1标量寄存器58
3"vmregs""vm"6 / 2向量掩码寄存器19
4"vregs""v"5 / 1向量寄存器157

字符串立即数已按字节确认:case 1 存储 DWORD "preg" 再存 's',case 2 存储 "sreg"+'s',case 4 存储 "vreg"+'s';case 0 和 3 使用 strcpy 写入 "none" / "vmregs";助记符分支使用两字节立即数 'p''s''v' 以及 strcpy("vm")。“操作码计数”列是每个 LLO 操作码产生哪个类别的按 RegisterType 直方图。

注意 — 寄存器分配器把这五个类别分成两组,可由二进制中的两条断言字符串见证:"type == RegisterType::kPreg || type == RegisterType::kVmreg"(不可 spill 的谓词/掩码组)和 "type == RegisterType::kSreg || type == RegisterType::kVreg"(可 spill 的标量/向量数据组)。kNone 从不被分配。


ArchRegister 及其物理编号

ArchRegister 是一个 50 成员枚举(序号 1..0x32),它不是一组名称。它是物理编号:internal::ArchRegisterInstance(ArchRegister, optional<int> instance) @ 0x126b3240(enum, instance-index) 对映射到扁平物理架构寄存器槽。该函数是一个 switch,在值空间 1..0x32 上有十二个显式分支,每个分支对应一个 banked 序号;其他所有序号命中默认分支并原样返回。

Banked 与 single 寄存器

十二个序号是多实例 bank,即存在多个物理副本并需要运行时实例选择器的寄存器。它们的 switch 分支会验证实例索引(*unit_id < count),然后返回 ordinal + instance,因此每个 bank 占据一段连续物理范围 [ordinal .. ordinal + count - 1]。其他 38 个序号落入默认分支并直接返回该序号。

Banked 序号实例数物理基址槽范围
0x01311..3
0x05355..7
0x0b41111..14
0x0f41515..18
0x1341919..22
0x1742323..26
0x1b42727..30
0x1f43131..34
0x2643838..41
0x2a44242..45
0x2e44646..49
0x3245050..53

两个 count-3 bank(0x01、0x05)断言 *unit_id < 3;十个 count-4 bank 断言 *unit_id < 4。基址始终等于序号(return ordinal + instance)。

不存在静态的 ArchRegister 到名称表:没有静态 ArchRegister->ToString,因此单靠这个枚举无法解析每序号的符号名(循环计数器、同步标志 bank 等)。

陷阱 — 不存在静态的 ArchRegister 到名称表。任何架构寄存器的可打印名称都由上一层的 RegisterNumbering::ToArchRegString @ 0x1275e2a0 产生,它会在通过 Target::InitRegisterNumbering 构建的每 Target 编号表解析槽之后,打印 "<RegisterTypeToMnemonic(type)><regno>"(例如 v3s12vm5p2)。所以 “ArchRegister 23” 没有固定名称;只有绑定目标编号后,它才会打印为 vN / sN / 等。参见 ArchRegno 编号

MRB 伪寄存器命名空间

在约 50 个真实架构寄存器之上,是用于 matmul-result-buffer(MRB)条目的伪寄存器命名空间。PseudoArchRegisterFromMrbEntry @ 0x1443c860 返回 (mrb_id << 9) + mrb_entry + 0x36,其中 mrb_entry < 0x200mrb_id < 4,即四个 512 条目的 bank,以物理槽 0x36 为基址,紧接在真实架构寄存器之上。这些是调度器为结果缓冲区活跃性所跟踪的 FIFO 支持伪寄存器槽。


ResultFifo

ResultFifo 枚举 25 个硬件结果 FIFO(序号 0..0x18)。权威名称来源是 ResultFifoToString @ 0x14441340,一个纯 switch,每个分支把名称写作内联 ASCII 立即数,并把长度存储在 buffer[23]。计数由 ResultFifoEntryCount @ 0x1d631520 独立交叉确认,后者拥有相同的 25 个有效分支。

序号名称FIFO 类别
0x00–0x03kMsrA0..kMsrA3Matmul 暂存结果,bank A,实例 0..3
0x04–0x07kMsrB0..kMsrB3Matmul 暂存结果,bank B,实例 0..3
0x08–0x0bkMrf0..kMrf3Matmul 结果 FIFO,实例 0..3
0x0c–0x0ekTsf0..kTsf2Transpose 暂存 FIFO,实例 0..2
0x0f–0x11kTrf0..kTrf2Transpose 结果 FIFO,实例 0..2
0x12kErfEUP 结果 FIFO(超越/激活 drain)
0x13kV2sf向量到标量 FIFO(vector→scalar bridge)
0x14kSfrf同步标志结果 FIFO(同步标志读回)
0x15kCrf跨 lane 结果 FIFO(XLU permute/reduce 结果)
0x16kDrfDivRem 结果 FIFO(标量除法/余数)
0x17kSccf跨 core / channel 结果 FIFO
0x18kCcrfCmem / 跨 core 结果 FIFO

名称逐字节精确:分支 0..7 是 strcpy("kMsrA0")strcpy("kMsrB3");分支 8..17 存储 DWORD 前缀("kMrf""kTsf""kTrf")加单个数字字节;分支 18..24 存储 "kErf""kV2sf""kSfrf""kCrf""kDrf""kSccf""kCcrf"。前十个暂存/结果 FIFO 的类别说明同时由名称前缀和 FifoInstance bank 算术支撑;kSccf / kCcrf 类别遵循名称前缀。

怪异点 — 同级字符串化器 MsrToString @ 0x1d629720 确认两个 matmul 暂存结果 bank 是 "msra" / "msrb",匹配 kMsrA* / kMsrB*。另一个 XmrToString @ 0x1d629740 把矩阵寄存器文件命名为 "gmra"(gain-matrix register)和 "lmr"(latch-matrix register);这些是不同于 ResultFifo另一种寄存器文件,见 Slot: MXU

FifoInstance — bank 算术

internal::FifoInstance(ResultFifo, optional<int> instance) @ 0x14446040(base-FIFO, instance) 对折叠成扁平物理 FIFO id。只有多实例暂存/结果 FIFO 拥有专门的 switch 分支;其他所有值都原样直通。

基础序号FIFO实例数返回值
0x00kMsrA04(*unit_id < 4instance
0x04kMsrB04instance + 4
0x08kMrf04instance + 8
0x0ckTsf03(*unit_id < 3instance + 12
0x0fkTrf03instance + 15
(other)序号(直通)

因此,带实例的物理域是 matmul + transpose 的暂存/结果 bank;更高序号(作为具名枚举值的 kTrf1/2kErfkV2sfkSfrfkCrfkDrfkSccfkCcrf)是单实例 FIFO,不通过 bank 算术。

陷阱 — FifoInstancecmp edi,0xf 不是枚举大小:那个 0..0xf 边界是物理实例子域(接受实例索引的 bank)。ResultFifoToStringResultFifoEntryCount 都显示 25 个成员(0..0x18):枚举宽度为 25,只有前 16 个序号参与 FifoInstance 算术。

ResultFifoEntryCount — 每 FIFO、每版本深度

ResultFifoEntryCount(ResultFifo, TpuVersion) @ 0x1d631520 返回 FIFO 的缓冲深度。它是在 FIFO 序号上的 25 分支 switch;每个分支都受 TpuVersion < 6 门控(版本 6 及以上会命中 "invalid platform type" fatal)。深度解析分为两种模式:

  • 常量深度,与版本无关:kTsf0/kTsf1/kTsf2(case 12–14)都返回 16kSfrf(case 19)返回 128
  • 按版本索引的表:其他所有 FIFO 都用 TpuVersion 索引每 FIFO 的 int[] 表。若干分支共享一张表(例如 case 0–2 读取一张表,4–6 读取另一张),反映深度随硅片世代一起变化的 FIFO 组。
c
function ResultFifoEntryCount(fifo, version):        // sub_1d631520
    switch (fifo):
        case kTsf0..kTsf2:                           // cases 12-14
            if (version >= 6) Fatal("invalid platform type")
            return 16                                // depth fixed across gens
        case kSfrf:                                  // case 19
            if (version >= 6) Fatal("invalid platform type")
            return 128
        case <grouped FIFOs>:
            if (version >= 6) Fatal("invalid platform type")
            return depth_table_for_group[version]    // version-indexed int[]
```text

> **注意 —** `TpuVersion < 6` 门控意味着此构建的深度表覆盖 `kJellyfish`(0) 到版本 5 代号;版本 6+ 是尚未支持的平台。这里没有逐格枚举完整的 25 × `TpuVersion` 深度矩阵。

---

## opcode_info_big — 枚举的消费位置

两个枚举都从每操作码元数据表 `opcode_info_big` @ `0x227b5570` 引用(`LloOpcodeBigInfo[461]`,符号大小 `0x326c` = 12908 B = 461 × 2828 字节步幅,按 `LloOpcode` 索引;`ComputeXluOperations` 用 `opcode < 0x1CE` 约束索引并在其他情况下陷阱,因此该边界允许越过 461 项表一个索引)。每条记录携带三个以哨兵结尾的 `int8` 列表:

```c
struct LloOpcodeBigInfo {              // sizeof 28 (0x1c)
    int8_t result_fifos[8];            // +0x00 : ResultFifo 0..0x18, neg-terminated (forward reader)
    int8_t arch_registers_read[12];    // +0x08 : ArchRegister 1..0x32, neg-terminated (-12 counter reader)
    int8_t arch_registers_written[8];  // +0x14 : ArchRegister 1..0x32, neg-terminated (forward reader)
};

+0x08 的读取列表由一个循环读取,该循环从计数器 -12 开始并索引 record[counter + 0x14],因此扫描偏移 +0x08..+0x13(12 项),并在第一个负字节处停止。+0x14 的写入列表和 +0x00 的结果 FIFO 列表则向前读取。每个 ArchRegister 代码都通过 ArchRegisterInstance 解析为物理槽,实例索引取自 LLO 值的元数据字(见下文)。完整描述符参见 opcode_info_big

注意 — +0x08..+0x13 字段是 arch_registers_read[12] 列表。它的三个读取者,即 ComputeXluOperations 和两个 GetPseudoArchRegistersRead<…> 实例化,都从 -120xfffffffffffffff4)开始循环计数器,并把它加到 +0x14 位移上,从而落在 +0x08,不是 +0x14。名为 GetPseudoArchRegistersRead 的消费者确认该字段是寄存器读取列表。


LloXluGraphOptimizer::ComputeXluOperations

目的

ComputeXluOperations @ 0x126d9780 构建跨 lane 单元(XLU)操作列表,并为每个操作附加读取和写入的 ArchRegister 集合。该每操作寄存器数据流随后被 XLU 调度器转换成跨 XLU 依赖图(参见 ArchRegno 编号)。这是读取两个 ArchRegister 列表并发出 XLU 操作 variant 的唯一消费者。

算法

c
function ComputeXluOperations(this):                 // sub_126d9780
    nodes = LloDependencyGraph::NodesInTopologicalOrder(false)  // sub_1442b8c0
    for node in nodes:
        op = WORD[node.value]                         // LloOpcode
        // dispatch: lea ecx,[op-0x8b]; cmp 0xca; ja skip → JT[op-0x8b]
        if not (0x8b <= op <= 0x155) or JT[op-0x8b] == skip_arm:
            continue                                  // 182 of 203 band opcodes are non-XLU
        // ---- this is one of the 21 XLU opcodes ----
        instance = nullopt
        meta = WORD[node.value + 0xb]
        if (meta & 0x400):                            // explicit-instance flag
            instance = ((meta >> 8) & 3) | has_value  // optional<int>

        // READ set: opcode_info_big +0x08 list, -12 loop, neg-terminated
        record = &opcode_info_big[28 * op]
        for c = -12; c < 0; ++c:
            code = record[c + 0x14]                   // offsets +0x08..+0x13
            if code < 0: break
            read_set.emplace(ArchRegisterInstance(code, instance))   // InlinedVector<ArchRegister,2>

        // WRITTEN set: forward +0x14 list
        written_set = LloOpcodeArchRegistersWritten(op, instance)    // sub_126b2ea0

        // emit one variant per op (stride 0x48, discriminant at +0x40)
        emit variant<TransposeTile, RpuOperation, XluControlOperation>(op, read_set, written_set)
    return StatusOr<vector<variant<...>>>
```text

分发是位于 `0xadf5504` 的跳转表,按 `op - 0x8b` 索引(203 项),只有两个分支:XLU 分支(21 个操作码)和 skip 分支(182 个操作码)。范围外操作码(`< 0x8b` 或 `> 0x155`)也跳过。

### 21 个 XLU 操作码

| 操作码 | 名称 | Variant |
|---|---|---|
| 0x08b | `kVectorSetPermutePattern` | `XluControlOperation` |
| 0x08c | `kVectorSetSegmentPattern` | `XluControlOperation` |
| 0x0a6 | `kVectorTranspose` | `TransposeTile` |
| 0x0a7 | `kVectorTransposeBinary` | `TransposeTile` |
| 0x0f5 | `kVectorMinReduceF32` | `RpuOperation` |
| 0x0f6 | `kVectorMaxReduceF32` | `RpuOperation` |
| 0x0f7 | `kVectorAddReduceF32` | `RpuOperation` |
| 0x0f8 | `kVectorMaxIndexReduceF32` | `RpuOperation` |
| 0x0f9 | `kVectorMinIndexReduceF32` | `RpuOperation` |
| 0x0fa | `kVectorMaxSegmentReduceF32` | `RpuOperation` |
| 0x0fb | `kVectorMinSegmentReduceF32` | `RpuOperation` |
| 0x0fc | `kVectorAddSegmentReduceF32` | `RpuOperation` |
| 0x0fd | `kVectorMinReduceBf16` | `RpuOperation` |
| 0x0fe | `kVectorMaxReduceBf16` | `RpuOperation` |
| 0x0ff | `kVectorAddReduceBf16` | `RpuOperation` |
| 0x100 | `kVectorMaxIndexReduceBf16` | `RpuOperation` |
| 0x101 | `kVectorMinIndexReduceBf16` | `RpuOperation` |
| 0x14f | `kVectorXlaneResult` | `XluControlOperation`(弹出 `kCrf`) |
| 0x150 | `kVectorPermuteResult` | `XluControlOperation` |
| 0x154 | `kVectorTransposeResult` | `TransposeTile`(弹出 `kTrf*`) |
| 0x155 | `kVectorTransposeClear` | `TransposeTile` |

Variant 分配在发射时由四个逐字节精确的分类器决定(`LloOpcodeUsesTranspose`、`LloOpcodeUsesRpu`、`LloOpcodeIsRpuControl`、`LloOpcodeIsRpuResult`);每操作码到 variant 的单元格已在 [ArchRegno 编号](archregno-numbering.md#per-opcode-variant-classification) 中确认。编码侧的 opcode→factory 映射参见 [XLU 操作清单](xlu-op-roster.md)。

> **注意 —** reduce 家族(0xf5..0x101)是连续的:F32 min/max/add reduce、F32 max/min index-reduce、F32 max/min/add segment-reduce,然后是 bf16 镜像(min/max/add reduce + max/min index-reduce)。这种连续性正是 `LloOpcodeUsesRpu` 用 `(op - 0xf5) < 0xd` 测试的范围。

### 一个工作示例 — kVectorAddReduceF32 (0xf7)

1. 拓扑遍历到达节点;操作码为 `0xf7`。分发索引为 `0xf7 - 0x8b = 0x6c`;跳转表选择 XLU 分支。
2. 读取集合:`opcode_info_big[0xf7] + 0x08` 列表,其中每个代码通过 `ArchRegisterInstance(code, instance)` 解析;如果 bit `0x400` 已置位,则 `instance = (WORD[value+0xb] >> 8) & 3`,否则为 `nullopt`。该操作读取保存 reduce 源的 vreg。
3. 写入集合:`LloOpcodeArchRegistersWritten(0xf7)` 读取 `+0x14` 向前列表,也就是结果落入的跨 lane 结果 FIFO(`kCrf`)支持寄存器。
4. 向 XLU-op 向量发出 `RpuOperation`。读/写集合供给跨 XLU 依赖跟踪器,因此后续*读取* `kCrf` 的 `kVectorXlaneResult`(0x14f)会被排在这个*写入*它的 reduce 之后。

---

## 相关组件

| 组件 | 关系 |
|---|---|
| `opcode_info_big`([记录格式](record-format.md)) | 保存由这些枚举作为键的 `result_fifos` / `arch_registers_read` / `arch_registers_written` 列表 |
| `RegisterNumbering`([archregno 编号](archregno-numbering.md)) | 按目标把 `ArchRegister` 物理槽转换成可打印的 `(RegisterType, regno)` |
| XLU 调度器([archregno 编号](archregno-numbering.md#crossxlu-data-dependency-tracker)) | 消费 `ComputeXluOperations` 构建的每操作读/写 `ArchRegister` 集合 |

## 交叉引用

- [ArchRegno 编号](archregno-numbering.md) — `ArchRegister` 序号如何成为按世代的 `(RegisterType, regno)` 架构寄存器编号,以及消费读/写集合的跨 XLU 依赖跟踪器
- [XLU 操作清单](xlu-op-roster.md) — 此处命名的跨 lane 单元操作的 opcode→factory 表
- [Slot: VPU](slot-vpu.md) — 其操作产生这些 FIFO 所 drain 的 vreg 的向量处理槽
- [Slot: MXU](slot-mxu.md) — 填充 `kMsr*` / `kMrf*` 暂存和结果 FIFO 的 matmul 槽
- [MC 发射器](mc-emitter.md) — 把这些操作布置进 bundle 槽的机器码发射器