Skip to content

硬件循环计数器

地址适用于来自 libtpu-0.0.40-cp314 wheel 的 libtpu.so。其他版本会不同。

摘要

TPU 循环在两个位置之一计数,且该选择按 sequencer 决定,而不是按 program 决定。在 BarnaCore(以及 Jellyfish / Pufferfish AddressHandler)上,循环是真正的硬件循环:专用 silicon register 保存 trip count,硬件递减并测试它,bundle stream 包含显式的 loop-setup / loop-start / loop-end ops,但没有 software induction variable、没有 compare、没有 branch。AddressHandler 硬件循环只在 v3/v4 上有效——ViperfishTarget override 是一个 dead stub(如下),v5p/v6e 完全没有 override。在 TensorCoreSparseCore 上,循环是由 scalar-ALU op set 构成的 software back-edge——ADDri IV update、一个 CMPxx 和一个 BRcond——同时硬件 Loop Counter (LCC) register 镜像迭代计数,使 body 可以读取它(用于依赖迭代次数的寻址),而不需要保留自己的 counter。LCC 不驱动任何东西;它是一个可读快照。

loop-counter register 在整个 binary 中名为 LCC,是一个以两个 32-bit halves 读取的 64-bit value,并与 global time counter (GTC) 统一在同一个 enum(CycleCounterType { kLCC, kGTC })下。它不是可分配 register——register file 中没有与 S/V/M/P0..P31 并列的 LCC class;和 GTC 一样,它是一个读入 scalar destination 的特殊 control register。可寻址 LCC registers 的数量是本页固定的 per-generation count:Jellyfish 的 TensorCore 不暴露任何 LCC(循环只存在于 AddressHandler Loop slot 中);Pufferfish 通过 indexed read-register enum 暴露两个(LCC0、LCC1);Viperfish、Ghostlite 和 Trillium 通过一对专用 dest-only opcodes 暴露一个 implicit counter。

本页把 loop counter 作为重实现目标来记录:per-generation count 和 read mechanism、两种 loop mechanisms(hardware-counted 与 software-counted-with-readback)的 encoding、带有 even-offset / minimum-length 约束的 AddressHandler single-active-loop builder、证明 hardware-vs-software split 的两个 LLVM PipelinerLoopInfo subclasses,以及 sequencer 如何驱动 back-edge。op inventory 及其所在 lane 见 Sequencer Slot 页面;本页负责 counter 本身。

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

  • LCC 是一个 64-bit 特殊 control register,以 low+high 32-bit halves 读入 5-bit-selected scalar destination;最大可表示 trip count 为 2^64 − 1。它不在 allocatable register file 中。
  • 每代 count:JF TC = 0(仅 AddressHandler Loop slot),PF = 2(LCC0/LCC1,通过 Tcs/Bcs read-register enum),V5+ = 1 implicit(通过 ReadRegisterLcc{Low,High},一个无 index operand 的 dest-only opcode)。
  • 两种机制:hardware-counted(BarnaCore / AddressHandler——getIVUpdate/getCmp 为 NULL,getTripCount−1)与 software-counted(TC / SparseCore——真实的 IV/cmp/trip-count MIs),由 analyzeLoopForTPUPipelining 基于 subtarget feature bit 和 terminator opcode 选择。
  • AddressHandler 硬件循环是 single-active(一个 loop_start_ field,不是 stack),body length ≥ 2 instructions,并带有 mandatory non-loop preheader instruction。
  • Nesting 受约束:每个 sequencer 一个 active hardware loop;PF 的两个 LCC registers 允许读取 depth-2 nest;outer loops 是 software back-edges。
Counter registerLCC ("Loop Counter") — 64-bit,以 lo+hi 32-bit halves 读取
Counter enumLloInstruction::CycleCounterType { kLCC, kGTC }(与 GTC 共享 read datapath)
PF read mechanismindexed TcsReadRegister / BcsReadRegister enum(LCC0, LCC1, …)
V5+ read mechanismReadRegisterLccLow / ReadRegisterLccHigh — dest-only opcodes,无 index
V5+ dest encoderTensorCoreScalarAlu0Compact_ReadRegisterLccLow::set_dest @ 0x1f62ff60BitCopy(buf,467,&dest,0,5)
HW-vs-SW selectorTPUInstrInfo::analyzeLoopForTPUPipelining @ 0x13b804c0(feature bit [subtarget+0x158]&1 + terminator opcode)
HW loop-info class(anon)::TPUBarnaCorePipelinerLoopInfogetIVUpdate=NULL @ 0x13b86560, getTripCount=−1 @ 0x13b865a0
SW loop-info class(anon)::TPUSparseCorePipelinerLoopInfogetTripCount=[this+0x28] @ 0x13b86260
AddressHandler builderAddressHandlerProgramBuilder::BeginLoop @ 0xfa90d40 / EndLoop @ 0xfa91300
AddressHandler per-gen insertJellyfishTarget::InsertAddressHandlerLoop @ 0x1d490e00(live)/ PufferfishTarget @ 0x1d495340(live)/ ViperfishTarget @ 0x1d49b980__noreturn stub)
BarnaCore HW opcodesbcLOOP_SETUP (0x194), bcLOOP_START (0xf8a), bcLOOP_END (0x193)
LLO loop kindsLloLoopKindProto { LOOP_KIND_NONE, LOOP_KIND_WHILE, LOOP_KIND_DOWHILE }

LCC Register 及其宽度

硬件 loop counter 在 binary 中处处命名为 LCC("Loop Counter"),是一个 64-bit value。它从不整值读取:program 将其作为两个 32-bit halves 读取(TensorCore 上的 rdreg.lcc.lo + rdreg.lcc.hi;V5+ 上的 ReadRegisterLccLow + ReadRegisterLccHigh),这与 global time counter (GTC) 完全镜像,后者也以相同的 lo+hi 方式读取。LLO IR 将二者统一在单个 enum 下——LloInstruction::CycleCounterType { kLCC, kGTC }——并共享 read-register datapath。LCC 是 per-loop iteration counter;GTC 是 global time counter。

关键的重实现事实:LCC 不是 allocatable register。 TPU register file 有 S / V / M / P0..P31 / XRF / DRF / ERF / SFRF / V2SF / CB / VAGG classes,但没有 LCC class。LCC 和 GTC 一样,是 sequencer 读入 scalar destination S0..S31 的特殊 control register。每个 half 落入一个 32-bit scalar register;destination 是 5-bit-selected scalar index,这一点从 V5+ encoder 中逐字节精确确认:

c
// gfc::isa::TensorCoreScalarAlu0Compact_ReadRegisterLccLow::set_dest(unsigned)  @ 0x1f62ff60
int dest = a2;                               // the scalar destination index
return BitCopy(buf, /*dst_bit=*/467, &dest, /*src_bit=*/0, /*width=*/5);   // 5-bit dest, abs bit 467
```text

`BitCopy(buf, 467, &dest, 0, 5)` 将 5-bit destination 写到 absolute bundle bit 467(`0x1d3`)——而且这是该 op 写入的**唯一** field。本页所有 bit positions 都是 **LSB-first**(bit 0 = byte 0 的 least-significant bit),与通用 `BitCopy(dst, dst_bit, src, src_bit, nbits)` packer(`0x1fa0a900`)以及 [Bundle Model §bit-numbering](bundle-model-overview.md) 中固定的约定一致。这也是 [Sequencer Slot](slot-sequencer.md#branch--jump--call-encoding) call/return-address encoder 写入的 bit 467 处同一个 5-bit dest。这里没有 loop-counter-index operand,这是 V5+ 暴露单个 implicit counter 的结构性证明(见 [Per-Generation Count](#per-generation-count-and-read-mechanism))。counter 可表示的最大 trip count 是 `2^641`;64-bit readback(lo+hi)是固定的,而 silicon down-counter 是否完整 64 bits 或更窄,无法从 binary 中分离判断。

> **注意 —** LLVM-generic `hardware-loop-counter-bitwidth` `cl::opt`("Set the loop counter bitwidth")属于 target-independent `HardwareLoops` pass,而不是 silicon LCC。按 lo/hi read structure,TPU LCC 固定为 64-bit;不要混淆二者。

---

## 每代 Count 和 Read Mechanism {#per-generation-count-and-read-mechanism}

“count”是 program 可读取的*可寻址* loop-counter registers 数量。binary 中存在两种不同的 register-naming conventions,它们直接揭示 count。

**Pufferfish (v4) — 两个 loop counters,显式 indexed。** Pufferfish read-register set 是一个 enum,即 `TcsReadRegister`(TensorCore Sequencer)和 `BcsReadRegister`(BarnaCore Sequencer),其值命名两个不同的 loop counters 和两个不同的 time counters:

```text
 TcsReadRegister:                  BcsReadRegister:
   TCS_READ_REGISTER_LCC0            BCS_READ_REGISTER_LCC0
   TCS_READ_REGISTER_LCC1            BCS_READ_REGISTER_LCC1
   TCS_READ_REGISTER_GTC0            BCS_READ_REGISTER_GTC0
   TCS_READ_REGISTER_GTC1            BCS_READ_REGISTER_GTC1
   TCS_READ_REGISTER_TAG_REGISTER    BCS_READ_REGISTER_TAG_REGISTER
   TCS_READ_REGISTER_TRACEMARK_REG   BCS_READ_REGISTER_TRACEMARK_REG / FSR / HDR

active counter 由 scalar read-register op 的 reg enum field 选择,而不是由 dedicated opcode 选择——这就是 binary 中不存在 pxc::isa::*ReadRegisterLcc* function 的原因(nm set 中没有)。读取 LCC0 与 LCC1 是选择 enum value,而不是选择不同 instruction。

Viperfish (v5p) / Ghostlite (v6e) / 6acc60406 (TPU7x) — 一个 loop counter,implicit。 V5+ 将 indexed read 替换为每个 engine 两个 dedicated opcodes:ReadRegisterLccLow 读取 LCC[31:0]ReadRegisterLccHigh 读取 LCC[63:32],各自读入 scalar dest。vxcgxc::glcgxc::gfc 的 encoders 同时存在于 ScalarAlu0ScalarAlu1 和 SparseCore 上。如上所示,该 op 的唯一 operand 是 5-bit destination——没有 index field,因此 program 每个 sequencer 正好能寻址一个(implicit)loop counter。

Jellyfish (v2) / Dragonfish (v3) — TensorCore 上没有 LCC read。 不存在 jellyfish::isa::*ReadRegisterLcc* symbol,也没有 Jellyfish read-register enum 命名 LCC;JF TensorCore 只有 cycle-counter reads。Jellyfish 只通过 AddressHandler Loop slot(如下)暴露 hardware loop,从不通过 TensorCore 上的 LCC read 暴露。

GenTC LCC regsSC/BCS LCC regsRead mechanism
Jellyfish (v2)0n/a(BCAH Loop slot)TC 上仅 cycle-counter
Dragonfish (v3)0(alias JF)n/a继承 Jellyfish
Pufferfish (v4)2(LCC0/LCC1)2(BCS LCC0/LCC1)Tcs/Bcs read-register enum
Viperfish (v5p)1(implicit)1(implicit)ReadRegisterLcc{Low,High}(dest-only)
Ghostlite (v6e)1(implicit)1(implicit)ReadRegisterLcc{Low,High}(dest-only)
6acc60406 (TPU7x)1(implicit)1(implicit)ReadRegisterLcc{Low,High}(dest-only)

易错点 — Pufferfish 确实没有 V5+ ReadRegisterLcc{Low,High} opcode form,但它仍然有两个 LCC registers:它通过 indexed TcsReadRegister / BcsReadRegister enum 读取它们。若重实现只根据 V5+ opcode 形态驱动,会完全漏掉 PF loop counters。


两种 Loop Mechanisms

循环使用哪种机制由 sequencer 决定,且该 split 由两个——也只有两个——LLVM PipelinerLoopInfo subclasses 证明。不存在 TensorCorePipelinerLoopInfo;TensorCore software loop 使用 SparseCore(software)info。

text
 llvm::TPUPipelinerLoopInfo (base)
   ├─ (anon)::TPUBarnaCorePipelinerLoopInfo   — hardware-counted (counter is in silicon)
   └─ (anon)::TPUSparseCorePipelinerLoopInfo  — software-counted (IV in the scalar register file)
```text

BarnaCore subclass 对每个 software-loop component 都返回 null,这是硬件执行计数的最清晰证据:

```c
// (anon)::TPUBarnaCorePipelinerLoopInfo  — decoded byte-exactly
getIVUpdate()    @ 0x13b86560  ->  return 0;     // NULL — no software induction variable
getCmp()         @ 0x13b86580  ->  return 0;     // NULL — no software compare
getTripCount()   @ 0x13b865a0  ->  return -1;    // -1 = "no software trip count"
adjustTripCount  @ 0x13b86500  ->  ret;          // no-op

SparseCore subclass 为每项返回真实 machine instructions:

c
// (anon)::TPUSparseCorePipelinerLoopInfo  — decoded byte-exactly
getIVUpdate()    @ 0x13b86220  ->  return *((void**)this + 3);   // [this+0x18] = ADDri MI
getCmp()         @ 0x13b86240  ->  return *((void**)this + 2);   // [this+0x10] = CMP/BRcond MI
getTripCount()   @ 0x13b86260  ->  return *((void**)this + 5);   // [this+0x28] = trip-count MI
```text

选择发生在 `analyzeLoopForTPUPipelining`(`0x13b804c0`)。它首先测试 subtarget feature bit(`[*subtarget + 0x158] & 1`——byte offset `0x158` = 344;BarnaCore hardware-loop mode);然后遍历 loop 的 terminators,寻找**三个** loop-terminator opcodes 之一(`325`、`403`、`328`)。匹配时,它读取 `[loop_header + 0x122]`(= 290)处的 per-loop HW-mode marker byte;当该 byte 为 `1` 时,它通过 `operator new(8u)` / `vtable = off_2192D4E8` arm 分配 BarnaCore info(函数中部,仅在 terminator match 后到达);否则它检测 IV 和 compare,并构建 48-byte SparseCore info(`operator new(0x30u)`):

```c
// llvm::TPUInstrInfo::analyzeLoopForTPUPipelining  @ 0x13b804c0  (decoded byte-exactly)
subtarget = loop_header->parent_subtarget;             // [a3+4]→[+32]
if ((subtarget->vtable_field[0x158] & 1) == 0) return 0;   // not pipelineable on this target
for (term in loop terminators) {                       // walk the terminator chain
    if (term.opcode == 325 || term.opcode == 403 || term.opcode == 328) {
        if (loop_header.flags[+0x122] == 1)             // BarnaCore HW-mode marker byte == 1
            return new TPUBarnaCorePipelinerLoopInfo;   // operator new(8); no IV/cmp/trip
        // else: find predicate operand, detect IV update + compare (analyzeIVUpdateforPipelining)
        ...
        return new TPUSparseCorePipelinerLoopInfo(iv, cmp, ...);  // operator new(0x30)
    }
}

注意 — 三个 terminator opcodes(325403328)和 predicate-operand opcode 540analyzeLoopForTPUPipelining 匹配的 raw MC opcode integers;它们的 symbolic names 尚未解析(整数是从 decompile 中逐字节精确得到的——名称不是)。该函数在提取 predicate operand index 时还 special-cases opcode 540

因此 BarnaCore loop body 是:bcLOOP_SETUP(加载 count)→ bcLOOP_START(标记 body head;一个 value operand = bound)→ body → bcLOOP_END(硬件 decrement+test+branch-back terminator)。TC/SC loop body 是:preheader(MOV/ADDri 初始化 IV)→ body → ADDri(IV += stride)→ CMPxxBRcond 回到 head,LCC register 镜像 count 供 in-body reads 使用。

特性 — BarnaCore loop index 本身是一个可读值,不同于 LCC snapshot。intrinsics bc.extractvalue.loopindex(读取 current index)和 bc.insertvalue.loopindex(seed 它)暴露 bcLOOP_END 递减的 live counter——这是不同于 software-counted engines 上 ReadRegisterLcc mirror 的 datapath。重实现不能假定“read the loop count”在两种机制上会映射到相同 op。


Sequencer 如何驱动 Loop

loop-control op 始终占用 scalar sequencer slot——scalar-ALU sub-bundle 的 lane 0,也是唯一能改变 program counter 的 lane(见 Sequencer Slot)。两种机制在那里放置不同的 ops。

Software loop(TC / SparseCore)。 back-edge 是 BRcond(conditional branch);IV update(ADDri)和 compare(CMPxx)是同一 lane family 中的 scalar-ALU ops。branch target 是 immediate slot 0 中的 signed 20-bit field,而不在 sequencer slot bytes 内;absolute-vs-relative 区分纯粹是该 field 之上的 opcode discriminator。CMPxx family 覆盖 register-immediate(CMPxxri)和 register-register(CMPxxrr)形式中的 signed/unsigned comparisons——immediate form 编码 compile-time-constant trip bound,register form 编码 dynamic / SPU-computed bound。因为 back-edge 是 branch,loop body 的最后一个 bundle 是 branch-terminator bundle,这会与 branch-delay-slot packing 交互。

Hardware loop(BarnaCore)。 bcLOOP_SETUP(opcode 0x194)在 preamble 中将 trip count 加载到 loop register;bcLOOP_START(0xf8a)标记 body head,并携带一个 value operand(bound——可能来自通过 SETUP 的 immediate 的 register-materialized value);bcLOOP_END(0x193)是硬件用于 decrement、test 并 branch back 的 back-edge terminator。BarnaCore channel-scalar slot 携带 loop bit 和 branch bit,同时设置二者是 encoding error——诊断 "Invalid Barnacore Channel Instruction with both Loop and Branch bits set" 强制一个 slot 要么是 loop control,要么是 branch,不能两者都是。

trip count 作为 canonical index space 进入 LLO loop:LloLoopProto.LoopIndexSpaceProto = { start, limit, step }limit 可以是 constant 或 dynamic value;binary 跟踪 num_dynamic_loop_bounds_(每个 dynamic bound 占用两个 slots,offset < num_dynamic_loop_bounds_ * 2),且 num_dynamic_loop_bounds_ == 0 的 loop 完全 static。XLA-side WhileLoopBackendConfig { KnownTripCount, KnownInitStep, KnownInductionVariable } 将 trip count 向下传播到这个 index space。LLO pass (anon)::UpdateLoopCounter(LloRegion*) 遍历 region,找到 loop,并将 trip counter 物化到 loop 的 carried tuple 中,使 hardware LCC 或 software IV 可以被 seeded;当 CFG 过于纠结而无法识别单个 trip counter 时,它以 UnknownLoopCountComplexCFG 放弃。

zero-trip 在 loop-kind level 处理,而不是由 counter 处理:LloLoopKindProto = { LOOP_KIND_NONE, LOOP_KIND_WHILE, LOOP_KIND_DOWHILE }WHILE 在 body 前测试,可以执行零次;DOWHILE 在之后测试,至少运行一次。MLIR property verify_non_zero_trip 断言 loop 运行 ≥ 1 iteration,因此它可以 lowered 为 DOWHILE / hardware-counted loop 而无需 guard。小的 constant-trip loops 完全绕过 counter——"Loops with a constant trip count smaller than this value will not use the count register"——并改为 unrolled 或 peeled。


AddressHandler 硬件循环(Jellyfish / Pufferfish)

Jellyfish 没有 TensorCore LCC,因此它的硬件循环完全存在于 BarnaCore AddressHandler sequencer 中,由 AddressHandlerProgramBuilder::BeginLoop0xfa90d40)/ EndLoop0xfa91300)构建。builder 跟踪一个单一 loop-region field,loop_start_(位于 this+0x18,第 7 个 int),带有 kNoLoopActive = −1 sentinel——不是 stack——这是 AddressHandler loops 不能 software-nest 的结构性证明。CHECKs 逐字节精确如下:

c
// AddressHandlerProgramBuilder::BeginLoop  @ 0xfa90d40  (decoded)
CHECK(loop_start_ == kNoLoopActive);          // -1; fails if a loop is already active  (line 903)
loop_start_ = instructions_.size();           // record the body head
CHECK(loop_start_ >= 1);                       // "Code must start with one non-loop instruction"  (line 905)

// AddressHandlerProgramBuilder::EndLoop  @ 0xfa91300  (decoded)
CHECK(loop_start_ != kNoLoopActive);          // (line 910)
CHECK(loop_start_ >= 1);                       // "Code must start with one non-loop instruction"  (line 911)
CHECK(loop_start_ < instructions_.size());    // (line 912)
loop_length = instructions_.size() - loop_start_;
CHECK(loop_length >= 2);                       // "Jellyfish spec requires that loop must have at least two instructions"  (line 915)
insn = instructions_[loop_start_ - 1];        // the preheader instruction
CHECK(!insn.scalar.loop_start);               // (line 917)
insn.scalar.loop_start = 1;
insn.scalar.loop_count = loop_length - 1;      // body length minus the preheader slot
loop_start_ = kNoLoopActive;                   // reset; loop is closed
```text

因此 AddressHandler loop 要求 mandatory non-loop preheader instruction(`loop_start_ >= 1`)和至少两个 instructions 的 body(`loop_length >= 2`)。`EndLoop` 将 loop-start flag 和 loop-count stamp 到 preheader instruction record 中,然后重置 `loop_start_`。相同的 Pufferfish check string——*"Pufferfish spec requires that loop must have at least two instructions"*——确认同样的 minimum-length rule 延续到 v4。

loop body **不是** offset field——per-generation `Target::InsertAddressHandlerLoop` overrides 将一个 **count** 写入专用的 `BarnaCoreAddressHandlerScalarSlot_Loop` proto sub-message。`JellyfishTarget::InsertAddressHandlerLoop`(`0x1d490e00`)重新检查 `program_in_loop.bundles_size() >= 2`(同一 *"Jellyfish spec requires that loop must have at least two instructions"* string,`target_jellyfish.h:90`),然后 default-construct `Loop` sub-message 并存储 `loop_count = bundles − 1`(`*(loop + 24) = v30 − 1`)——body-bundle count 减去 preheader,与 `EndLoop` stamp 的 `loop_count = loop_length − 1` 相同。`PufferfishTarget::InsertAddressHandlerLoop`(`0x1d495340`)形态相同,带有 *"Pufferfish spec requires that loop must have at least two instructions"* string,并写入相同的 `bundles − 1` count。

> **易错点 —** 诊断 *"loop end is out of range or not a positive multiple of 2"* / *"loop start is out of range or not a negative multiple of 2"* 属于 LLVM bundled **ARM** backend(`(anon)::ARMAsmParser::matchAndEmitInstruction` @ `0x15185a20`,Armv8.1-M low-overhead-loop `WLS`/`LE` validation),****属于任何 TPU `InsertAddressHandlerLoop` path。TPU AddressHandler loop 携带的是 iteration **count**(`bundles − 1`),不是 signed even byte-offset;TPU encode path 中没有 even-multiple constraint。

AddressHandler loop 只跨 **v3/v4** 持续存在(`JellyfishTarget` / `PufferfishTarget` overrides,二者都 live 且实际构建 proto)。`ViperfishTarget::InsertAddressHandlerLoop` override(`0x1d49b980`)存在,但它是一个 `__noreturn` stub,会 fatal *"Deepsea version not supported"*(`target_viperfish.h:320`)——因此 AddressHandler-style hardware loop 在 **Viperfish (v5)** 被丢弃;Ghostlite 或 6acc60406 完全没有 override。

| Element | BarnaCore / AddressHandler(HW loop) | TC / SparseCore(SW loop) |
|---|---|---|
| Loop begin | `bcLOOP_SETUP`(load count)+ `bcLOOP_START`(1 bound operand);BCAH `BeginLoop` 设置 `loop_start_` | preheader:初始化 scalar IV(`MOV` / `ADDri`) |
| Body length | `ScalarSlot_Loop` proto 中的 `loop_count = bundles − 1`;body `≥ 2` instr | implicit(basic-block span) |
| Loop end | `bcLOOP_END` — HW decrement+test+branch-back | `ADDri` + `CMPxx` + `BRcond` back-edge |
| Counter | dedicated HW loop register1;PF 上为 LCC0/LCC1) | scalar-reg IV;HW LCC 镜像 count(可读) |
| Trip source | bound operand(reg / 通过 SETUP 的 immediate) | `CMPxxri`(imm)或 `CMPxxrr`(reg,dynamic) |
| Live index read | `bc.extractvalue.loopindex` | `ReadRegisterLcc{Low,High}` / indexed `Tcs`/`Bcs` enum |
| Nesting | single active loop(`loop_start_ != kNoLoopActive` CHECK) | software IVs 可自由 nest;只有 innermost 是 HW-counted |

---

## Nesting Model

hardware-loop nesting 被有意限制:

- **AddressHandler / BarnaCore**:一次只有一个 active hardware loop,由单个 `loop_start_` field 和上面的 `BeginLoop` CHECK 强制执行。此层级没有 loop-counter stack——没有 nested AddressHandler / BarnaCore hardware loops。
- LLVM-generic option strings *"force-nested-hardware-loop"* / *"nested hardware-loops not supported"* 确认默认 TPU `HardwareLoops` path 不支持 nested hardware loops;常见情形是一个 hardware-counted innermost loop,outer loops 作为 software back-edges 处理。
- **Pufferfish 的两个 LCC registers(LCC0, LCC1)**允许读取两个不同的 loop counters——支持最多 depth-2 nest,其中 inner 和 outer 各有不同 counter,由 read-register enum value 选择。LCC0/LCC1 对应的是(outer, inner)nest levels 还是(TC-issued, BC-issued)loops,此处未追踪。
- **V5+ 有单个 implicit LCC**,因此只有 innermost hardware-counted loop 的 count 可直接读取;outer loops 使用 software IVs。
- LLO loop region(`LloRegionMember::kLoop`)可以在 IR 中 nest——一个 `kLoop` member 的 sub-region 可以包含另一个 `kLoop`——但只有 innermost 获得 hardware counter;compiler 会 flatten、unroll 或 software-count 其余 loops。

---

## 尚未固定的内容

- **bcLOOP field bit positions。** `bcLOOP_SETUP` 的 trip-count immediate field,以及 BarnaCore bundle 内 `bcLOOP_START` / `bcLOOP_END` 的 body-offset fields 由 LLVM MC encoder(`TPUMCCodeEmitter`)路由;per-opcode `InstBits` records 此处尚未 byte-decoded。ops 及其角色已固定;精确 field widths 尚未固定。
- **silicon counter width。** 64-bit readback(lo+hi)是固定的;down-counter 是完整 64 bits 还是更窄,无法从 binary 中分离判断。
- **PF LCC0 vs LCC1 assignment policy。** 存在两个 counters;哪个 compiler pass 选择 LCC0 vs LCC1,以及该选择是否跟踪 nest level 或 issuing engine,此处未追踪。
- **三个 loop-terminator MC opcodes(`325`, `403`, `328`)和 predicate opcode `540`。** `analyzeLoopForTPUPipelining` 匹配的 integers 逐字节精确;它们的 symbolic LLVM-MC names(分别对应 `bcLOOP_END` / branch terminators 中的哪一个)此处尚未逐一解析。

---

## 交叉引用

- [Sequencer Slot](slot-sequencer.md) — loop-control ops(`BRcond`、`bcLOOP_*`、`ReadRegisterLcc`)占用的 lane-0 scalar slot,以及 JF software-bundle-index loop 与 V5+ LCC read。
- [Immediate Slot](slot-immediate.md) — immediate slot 0signed-20-bit branch / back-edge target 和 loop bound 落入的位置。
- [Bundle Model](bundle-model-overview.md) — per-generation bundle widths,以及承载这些 slots 的、由 `(TpuVersion, TpuSequencerType)` keyed 的 codec。