Skip to content

IndirectVregStream — 由 VREG 驱动的 Gather 循环

本页中的每个函数地址、操作数索引、结构体偏移、移位/掩码以及 opcode 值,都读取自 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d;build libtpu_lts_20260413_b_RC00)。.text VA 等于文件偏移 0xe63c000.rodata 位于 0x84a0000,两者都是恒等映射。除非另有标注,地址来自 gfc(6acc60406)实例;glc(Ghostlite)和 vfc(Viperfish)同级实现以不同地址承载相同 schema。其他版本会有差异。

摘要

IndirectVregStream 是 SparseCore Stream 的一种形式,它不是从内存 id-list 读取逐元素 offset 流,而是从向量寄存器读取。它是 SparseCoreStream 的第四个 oneof 形式(slot opcode 0x38),除了开头四个 proto 字段之外,结构上与 IndirectStream0x39)完全相同:两个 SREG 操作数(indirect_offsetindirect_size_and_hbm4b_offset)被替换为一个 VREG 选择器(indirect_offsets)、一个 lane 掩码(valid_offset_mask)和一个显式 base(off_tile_start_offset)。从 proto 字段 #5 往后,也就是整个 stream-control 尾部,都与 IndirectStream 逐字节相同。

本页负责说明 VREG 读取循环和逐索引 DMA 发起:offset 流如何来自向量寄存器和 lane 掩码,SC 侧 MLIR op 如何把 VREG 操作数作为普通 SSA Value(而不是 memref index group)携带,索引展开如何只展开三个 index group(并且明显展开 offset group),以及 LLVM lowering 如何根据 (hbm4b, src-space, dst-space) 三元组选择九个逐路由 DMA-issue intrinsic 之一。slot 位编码和真正的 indirect-DMA 描述符位于 Stream Gather/Scatter,本页只链接过去而不重新推导。将此形式固定到 TEC 的 engine-assignment 策略位于 getSequencerType

重新实现契约:

  • offset 来源是寄存器,而不是内存列表。 IndirectStream 从两个 5-bit SREG selector 读取 offset/size;IndirectVregStream 从 6-bit VREG selector(indirect_offsets)读取 offset ,并用另一个独立的 6-bit VREG selector 读取逐元素访问长度(indirect_access_lengths),由 valid_offset_mask 门控。id 流位于 TEC 向量寄存器文件中,这正是该形式仅限 TEC 的原因。
  • 循环体相同;只有索引供给不同。 逐元素地址算术、row-stride 乘法、accumulate StreamOpcode、filter、sliding-window post-update,以及所有公共控制尾部字段,都与 IndirectStream 拥有相同的 proto 布局。重新实现者应从一个 struct 编码两种形式;形式判别器只决定每个 lane 的 offset 如何获取。
  • SC op 携带三个 index group,而不是四个。 IndirectVectorStreamStartOp 通过 expandTiledIndices 展开 {Src, Dst, Sflag}Indices;没有 OffsetIndices group(offset 是 VREG 操作数),也没有 getIndirectListType 属性。这是相对于 IndirectStreamStartOp 的结构性差异,已在索引展开和 LLVM lowering 主体中确认。
  • 逐索引发起是 9 路 intrinsic dispatch。 LLVM lowering 构建九个候选 emitter lambda,并通过匹配运行时 (hbm4b-flag, src-memory-space, dst-memory-space) 三元组来选择。未匹配的三元组会发出 "IndirectVectorStreamStartOp doesn't support transfer from source memory space … to destination memory space …"
Proto 形式IndirectVregStreamSparseCoreStream oneof 成员,slot opcode 0x38
同级形式IndirectStream0x39);逐字节共享 proto 字段 #5…#22
VREG offset selectorindirect_offsets — 6-bit VREG selector,slot +0x28 >> 27 & 0x3F
VREG length selectorindirect_access_lengths — 6-bit VREG selector,slot +0x30 >> 2 & 0x3F
Lane 门控valid_offset_mask — 本轮哪些 VREG lane 携带有效 offset
Gather baseoff_tile_start_offset[_valid] — 显式 base(替代 SREG indirect_offset
SC 侧 opsc_tpu.indirect_vector_stream_start / .indirect_vector_stream_add_start
Op traitsZeroResults, AtLeastNOperands<6>, AttrSizedOperandSegments
Index groups{Src, Dst, Sflag}Indices没有 OffsetIndices
Index expansionexpandSCStreamStart<IndirectVectorStreamStartOp> @ 0x134eb880(Add @ 0x134ebd20
Builderlowering_util::InitiateIndirectVectorStreamOperation @ 0x13d870e0 / 0x13d866c0
逐索引发起IndirectVectorStreamStartOpLowering::rewriteSparseCoreStreamOpToLLVM @ 0x13560ce0
Engine仅 TEC(VREG 操作数需要 TEC 向量寄存器文件)
置信度CONFIRMED(由反编译/accessor/symbol 锚定),除非行内另有说明

此形式的位置

SparseCoreStream 是一个共享 header 加上四路 oneof 寻址形式。完整 message、其共享控制尾部以及各形式的 opcode 记录在 Stream Gather/Scatter。四种形式及其 offset 来源如下:

形式Opcode逐元素 offset 来源
LinearStream0x3b单个 linear base,没有逐元素 offset
StridedStream0x3a一个 stride 维度,没有逐元素 offset
IndirectStream0x39内存 offset id-list(两个 5-bit SREG 操作数)
IndirectVregStream0x38向量寄存器 offset 流(一个 6-bit VREG selector + lane mask)

IndirectStreamIndirectVregStream 是两种间接(gather/scatter)形式:二者都会逐元素遍历 offset 流,并为每个元素发起一次 off-tile transfer。它们只在 offset 流从哪里读取 上不同。IndirectStream 通过 SREG 寻址的内存列表往返读取 id;IndirectVregStream 直接从 TEC 向量寄存器消耗它们。本页说明第二种情况。

NOTE — 这是 variable-length / minibatch inner-loop gather。 当 embedding-id 流是在寄存器中产生时,VREG 形式是自然选择,例如 TEC 对一个 minibatch 做 scan/uniquify 的输出,并且必须在不先物化成内存 id-list 的情况下按 lane 并行 gather。valid_offset_mask 门控哪些 lane 触发,因此 ragged minibatch 可以直接映射到固定宽度 VREG sweep。这与 per-physical-core minibatch 模型相关(见 Embedding Minibatching);它是唯一 offset 操作数来自寄存器文件的 Stream 形式,这也正是它不能位于 SCS 或 TAC bundle 中的原因。(用例推断:HIGH。)


Proto 差异 — 字段 #1…#4

IndirectVregStreamIndirectStream 完全共享 proto 字段 #5…#22sync_flag_core_type, sync_flag_count_type, set_done_bit, tile_local_stride, post_update_circular_buffer, indirect_list_type, indirect_list_stride, indirect_filter_en, trace_en, stream_opcode, tile_local_memory_type, tile_local_stream_type, off_tile_memory_type, gather_scatter_add_is_b16, s0_x, s0_y, s1_x, s1_y)。这些字段及其含义记录在公共控制尾部。两种形式在开头四个字段不同:

text
IndirectStream (#1…#4) — offset stream from MEMORY (an SREG-addressed id-list):
  #1 indirect_offset_valid                 : bool
  #2 indirect_offset                        : uint32 (5-bit SREG)   ← base/list address
  #3 indirect_size_and_hbm4b_offset_valid  : bool
  #4 indirect_size_and_hbm4b_offset         : uint32 (5-bit SREG)   ← lookup size / HBM_4B suboffset

IndirectVregStream (#1…#4) — offset stream from a VECTOR REGISTER:
  #1 indirect_offsets                       : uint32   ← a VREG selector (the offset stream)
  #2 valid_offset_mask                      : uint32   ← which VREG lanes carry a valid offset
  #3 off_tile_start_offset_valid            : bool
  #4 off_tile_start_offset                  : uint32   ← explicit gather BASE (like LinearStream)
```text

因此替换关系很精确:两个用于*命名保存列表地址和 size 的寄存器*的 SREG 操作数,被替换为 (a) 一个用于*命名保存 offset 流本身的寄存器*的 VREG selector,(b) 一个 lane mask,以及 (c) 一个替代 SREG 来源 base 的显式 base offset。硬件随后逐 lane 读取 `indirect_offsets[lane]`,而不是遍历内存 id-list。

> **GOTCHA — base 从 SREG 移到 immediate 风格字段。** 在 `IndirectStream` 中,gather base 是两个 SREG 操作数之一;在 `IndirectVregStream` 中,base 是 `off_tile_start_offset`,与 `LinearStream` 使用相同字段名。把 `IndirectStream` 描述符移植到 VREG 形式的重新实现者,必须把 base 从 SREG 操作数移入 `off_tile_start_offset`,并把 id 流从内存移入 VREG。`IndirectStream` 打包在 `indirect_size_and_hbm4b_offset` 中的 size/length,在 slot 中变成独立的逐 lane `indirect_access_lengths` VREG selector(见下文)。

---

## VREG 读取循环 — Slot Selector

在 bundle 层面,两个 VREG 操作数是 6-bit *selector*,即命名硬件从哪个向量寄存器读取 offset 流和逐 lane 访问长度的小索引。它们由 gfc TEC-stream `…Field::GetConcatenatedValue()` accessor 解码,直接读取如下:

```c
// asic_sw::deepsea::gxc::gfc::isa::
//   SparseCoreTecStreamIndirectVregStreamIndirectOffsetsField::GetConcatenatedValue  @0x1ebe30a0
return (*((u64*)this + 5) >> 27) & 0x3F;          // struct +0x28, shift 27, width 6

//   …IndirectAccessLengthsField::GetConcatenatedValue  @0x1ebe30c0
return (*((u32*)this + 12) >> 2) & 0x3F;           // struct +0x30, shift 2, width 6
Slot fieldStruct wordShiftWidthAccessor (gfc)作用
IndirectOffsets+0x282760x1ebe30a0VREG selector,offset 流
IndirectAccessLengths+0x30260x1ebe30c0VREG selector,逐 lane 访问长度
OffTileStartOffset+0x104150x1ebe3100gather base
OffTileStartOffsetValid+0x104610x1ebe30e0base-present bit
OffTileMemoryType+0x104730x1ebe3340HBM / HBM_4B / SPMEM pool
StreamOpcode+0x18930x1ebe32c0accumulate mode(共享尾部)
TileLocalStride+0x182930x1ebe3160dst write granule(共享尾部)

两个 VREG selector 是读取侧的全部差异。其他所有内容(StreamOpcodeTileLocalStrideOffTileMemoryType、sync flag、filter、list stride)都是共享的 IndirectStream decode map,见 Stream Gather/Scatter § Decode Side;只有开头操作数字 +0x28/+0x30 携带 VREG selector,而不是 SREG 操作数。

NOTE — 6-bit selector 命名 VREG,而不是 lane 数。 indirect_offsetsindirect_access_lengths 是 6-bit 寄存器 selector;它们索引 TEC 向量寄存器文件(≤64 个可寻址 selector)。被 sweep 的 lane 数,也就是每轮 id 数,是 VREG 宽度,属于芯片几何,而不是 slot 字段。掩码 valid_offset_mask 随后门控这些 lane 中哪些在本轮携带有效 offset。6-bit selector 背后的物理 VREG-file 大小尚未枚举(LOW,见 Limits)。

Encode 侧

gfc TEC-stream encoder 写入 opcode 0x38,并把两个 VREG selector 操作数放到 bundle 的高位区域:

text
SparseCoreTecStreamEncoder::Encode  @0x1ebe33e0   (oneof switch on *(u32*)(stream+88))
  case 8   → opcode 0x3b (59)  LinearStream
  case 9   → opcode 0x3a (58)  StridedStream
  case 10  → opcode 0x39 (57)  IndirectStream
  case 0xB → opcode 0x38 (56)  IndirectVregStream   ← case-11 branch
  opcode            @ bundle bit 181, width 6  = 0x38 (56)   BitCopy(a3,181,…,6)
  vreg sel #1 (msg member +6) @ bundle bit 283, width 6      BitCopy(a3,283,…,6)
  vreg sel #2 (msg member +7) @ bundle bit 322, width 6      BitCopy(a3,322,…,6)
```text

(TEC bundle 为 64 B / 512 bits;两个 VREG selector 宽度都是 6,并落在高位区域。member +6 先序列化,member +7 后序列化。)

> **GOTCHA — TEC encoder 的 `oneof` 上界是 `0xb`;SCS encoder 的上界是 `0xa`。** `SparseCoreStreamEncoder::Encode`(SCS,`@0x1eb9b4c0`)在 `cmp rax, 0xa` 处限制其 `oneof` switch(最大 case 10),它只有三种形式,**没有** `IndirectVreg` 分支。TEC encoder 的上界是 `cmp rax, 0xb`(case 11)。这个额外 case 是 `IndirectVregStream` 仅限 TEC 的全部结构性原因:SCS 和 TAC stream encoder 无法到达第四种形式。任何 generation 中都没有 `SparseCoreStreamIndirectVregStream*` 和 `SparseCoreTacStreamIndirectVregStream*` field accessor;该形式的 field accessor 只存在于 `SparseCoreTecStream` 前缀下(vfc `0x1e936240`、glc `0x1ea7be20`、gfc `0x1ebe30a0`)。形式清单见 [Stream Gather/Scatter](stream-gather-scatter.md)。

---

## SC 侧 op 及其操作数组

VREG gather 由 MLIR op 对 `sc_tpu.indirect_vector_stream_start`(gather)和 `sc_tpu.indirect_vector_stream_add_start`(带 accumulate 的 gather/scatter)表达。该 op 携带与 `IndirectStreamStartOp` 相同的 traits:`ZeroResults`、`AtLeastNOperands<6>`、`AttrSizedOperandSegments`,但操作数和属性集合不同:

```text
sc_tpu.indirect_vector_stream_start / .indirect_vector_stream_add_start
  Traits: ZeroResults, AtLeastNOperands<6>, AttrSizedOperandSegments
  Memref index groups (MutableOperandRange getters):
    getSrcIndicesMutable()   @ 0x145d7860   — off-tile (HBM table) source memref + indices
    getDstIndicesMutable()   @ 0x145d7920   — TILE_SPMEM destination memref + indices
    getSflagIndicesMutable() @ 0x145d7a00   — completion sync-flag operand(s)
    (NO getOffsetIndicesMutable — the offsets are a VREG operand, not a memref index group)
  Attributes (getters):
    getHbm4b()                    @ 0x145d9120   — 4-byte-granule HBM addressing
    getTileLocalLengthPerStride() @ 0x145d9160   — destination granule per stride
    getUpd()                      @ 0x145d9100   — post-update (advance circular buffer)
    getEnableTrace()              @ 0x145d9140   — hardware trace enable
    (NO getIndirectListType — the VREG form omits it)
  Operand-segment table:  getODSOperandIndexAndLength @ 0x145d7720 (op)
                                                       @ 0x145d7620 (adaptor base)

相对于 IndirectStreamStartOp 的结构差异是两个省略项,二者都由反编译中相应 symbol 的缺失确认:没有 getOffsetIndicesMutable(没有逐元素 id-list memref),也没有 getIndirectListType 属性(内存 id-list 需要的 word-vs-row 解释)。_add_start 变体选择 accumulate StreamOpcode*_FLOAT_ADD / *_INTEGER_ADD)。

AttrSizedOperandSegments 意味着操作数列表是多个可变长 segment 的拼接,其大小位于每个 op 的数组中;getODSOperandIndexAndLength(i) 是该数组上的标准 MLIR prefix-sum(@0x145d7620this[16..16+i] 求和,对于 i ≥ 4 使用 vpaddd 向量化,并返回 (start << 0) | (len << 32))。VREG offset/length/mask 操作数和 memref group 都位于这个分段列表中;上面的 getter 命名这些 slice。


Index Expansion — 三个 Group,没有 Offset Group

在 LLVM lowering 之前,ExpandTiledMemRefsPass 会把每个 tiled-layout memref index 展开为 flat word offset。对于 IndirectVectorStreamStartOp,这是 expandSCStreamStart<IndirectVectorStreamStartOp>@0x134eb880…AddStartOp 实例为 @0x134ebd20)。反编译主体 clone 该 op,然后将恰好三个 index group 送入 expandTiledIndices

c
// expandSCStreamStart<IndirectVectorStreamStartOp>  @0x134eb880  (reconstructed)
started = cloneWithSubstOperands<IndirectVectorStreamStartOp>(op, ...);

// group 1 — Src  (ODS operand index 1, adaptor segment 2)
if (layoutIsTiled(getMemRefType(operand[1]))) {
    range  = getSrcIndicesMutable();
    seg    = adaptor.getODSOperandIndexAndLength(2);
    flat   = expandTiledIndices(seg, getTiles(layout), builder);  // 0x134e1f20
    range.assign(ValueRange(flat));                                // MutableOperandRange::assign
}
// group 2 — Dst  (ODS operand index 3, adaptor segment 4)
if (layoutIsTiled(getMemRefType(operand[3]))) {
    getDstIndicesMutable().assign(expandTiledIndices(adaptor.getODSOperandIndexAndLength(4), ...));
}
// group 3 — Sflag (ODS operand index 6, adaptor segment 7)
if (layoutIsTiled(getMemRefType(operand[6]))) {
    getSflagIndicesMutable().assign(expandTiledIndices(adaptor.getODSOperandIndexAndLength(7), ...));
}
rewriter.replaceOp(op, started);
return success();
```text

三个 group,三次 `expandTiledIndices` + `MutableOperandRange::assign` 调用,并且**没有 offset list 的第四次 pass**。对比 `expandSCStreamStart<IndirectStreamStartOp>`(`@0x134ead00`),它会处理 `{Src, Dst, Offset, Sflag}Indices`(四个 group)。缺失的第四次 pass 是 VREG 形式在索引展开层面的签名:逐元素 offset 从不进入 memref index 机制,因为它们是向量寄存器操作数。`ExpandTiledMemRefsPass::addPattern<IndirectVectorStreamStartOp>`(`@0x134fc680`)注册该展开。

> **NOTE — 操作数索引 `1 / 3 / 6` 是 memref group;VREG 操作数位于 scalar segment。** 展开只触碰 *memref* segment(Src/Dst/Sflag,ODS operands 1/3/6)。VREG offset/length/mask 操作数是其他 segment(5/8/9,见下面的 LLVM lowering)中的普通 SSA `Value`,不会进行 tiled-memref-expanded;它们原样通过。重新实现者不得把 VREG 操作数送入 tiled-index expander。

---

## 逐索引 DMA 发起 — `rewriteSparseCoreStreamOpToLLVM`

最终 lowering `IndirectVectorStreamStartOpLowering::rewriteSparseCoreStreamOpToLLVM`(`@0x13560ce0`)把 op 转换成实际的 SparseCore stream-start intrinsic。这里会把 VREG 操作数作为 value 读取,并选择逐索引发起 intrinsic。恢复出的流程如下:

```c
// rewriteSparseCoreStreamOpToLLVM  @0x13560ce0  (reconstructed)

// 1. Dereference the VREG operands as scalar SSA Values (NOT memref index groups):
v_off  = adaptor.getODSOperandIndexAndLength(5); offVal  = ValueRange(...).dereference_iterator(0);
v_len  = adaptor.getODSOperandIndexAndLength(8); lenVal  = ValueRange(...).dereference_iterator(0);
v_mask = adaptor.getODSOperandIndexAndLength(9); maskVal = ValueRange(...).dereference_iterator(0);
hbm4b  = op.getHbm4b();

// 2. Read src/dst element types + shapes from the Src (operand 1) and Dst (operand 3) memrefs
srcShape = getShape(getMemRefType(operand[1])); srcElem = getElementType(...);
dstShape = getShape(getMemRefType(operand[3])); dstElem = getElementType(...);

// 3. HBM_4B address adjustment (when hbm4b set): scale the Src/Dst offset operands
//    via getHbm4bOffset(...) + adjustOffsetForHbm4b(...) — adaptor operands 2 (src) / 4 (dst)
if (hbm4b) { srcOff = adjustOffsetForHbm4b(getHbm4bOffset(operand[2], srcElem)); ... }

// 4. Build NINE candidate issue lambdas (#3…#11), one per (hbm4b, srcSpace, dstSpace) route.
//    Each lambda closes over {offVal, lenVal, maskVal, base, sync, ...} and emits the
//    sc-stream-start intrinsic for that specific memory-space route.
cand[0..8] = { lambda#3, …, lambda#11 };   // each: {flag, srcSpace, dstSpace} + emitter

// 5. Select the lambda whose (flag,srcSpace,dstSpace) triple matches the runtime triple:
for k in 0..8:
    if cand[k].flag == hbm4b && cand[k].src == srcSpace && cand[k].dst == dstSpace:
        // optional: if a filter value operand (operand 0) is present, emit
        //   sc_tpu.set_indirect_filter_value first
        if (operand[0] present) builder.create<SetIndirectFilterValueOp>(filterVal);
        cand[k].emit();          // ISSUE the per-route stream-start
        return success();
// 6. No match → diagnostic:
return op.emitError(
    "IndirectVectorStreamStartOp doesn't support transfer from source memory space: %d"
    " to destination memory space: %d", srcSpace, dstSpace);

九个候选 emitter 是 SparseCore stream-start 的逐路由特化。dispatch key 是 (hbm4b-flag, src-memory-space, dst-memory-space) 三元组,也就是 GetTransferKind 上游用来把它分类为 Stream 而非 DMA 的同一对 (srcSpace, dstSpace)。因此,getSequencerType lowering 中做出的 kStream 决定,在这里转化为具体的逐路由 intrinsic。不支持的 pair 会落入 emitError,其消息通过反编译中看到的 LLVM SmallVector<u32> 插值构建。

Lowering 元素证据 (gfc)
VREG offset 操作数位于 adaptor segment 5,并作为 Value 解引用getODSOperandIndexAndLength(5) + dereference_iterator(0)
VREG length 操作数位于 adaptor segment 8getODSOperandIndexAndLength(8) + dereference_iterator(0)
VREG mask 操作数位于 adaptor segment 9getODSOperandIndexAndLength(9) + dereference_iterator(0)
getHbm4b() 门控 HBM_4B offset scalinggetHbm4b @ 0x145d9120getHbm4bOffset / adjustOffsetForHbm4b 调用
HBM_4B offset 操作数位于 adaptor segments 2 (src) / 4 (dst)hbm4b 分支中的 getODSOperandIndexAndLength(2) / (4)
对 emitter lambda 的 9 路 (hbm4b, src, dst) 三元组 dispatch九个 operator new(0x50/0x58) emitter closure + 对 (flag, *(+1), *(+2)) 的 match chain
operand 0 存在时可选 SetIndirectFilterValueOp匹配分支上的 OpBuilder::create<SetIndirectFilterValueOp>
不支持路由的 diagnostic"IndirectVectorStreamStartOp doesn't support transfer from source memory space: … to destination memory space: …"
Lane 并行循环体 / 硬件逐 lane 地址算术不是软件循环,而是作为一个 intrinsic 发起;HW 遍历 VREG

GOTCHA — 没有软件逐元素循环;“循环”是硬件 Stream engine。 lowering 不会 对 offset lane 发出 scf.for 和逐 lane imul/DMA。它发出单个 stream-start intrinsic,把 VREG selector、length selector、lane mask 和 base 交给硬件 Stream engine,然后硬件自行遍历 lane,并应用 IndirectStream 中记录的同一逐元素 row-stride 乘法(addr_i = base + offsets[lane] * indirect_list_stride)。编译器的工作是操作数分配和路由选择,而不是发出循环。建模 lowering 的重新实现者必须为每个 stream-start 发出一个 intrinsic,而不是展开的循环。


Builder — InitiateIndirectVectorStreamOperation

从 target 和一组 ValueRange 操作数构造 op 的高层 builder 是 lowering_util::InitiateIndirectVectorStreamOperation@0x13d870e0;另一个同级 overload 在 @0x13d866c0)。恢复出的主体把 VREG 操作数作为 ValueRange 传递,并在构建 op 之前校验 transfer geometry:

c
// InitiateIndirectVectorStreamOperation(Target&, OpBuilder, LocationGenerator,
//     Value, ValueRange, ValueRange, …, StreamOptions)  @0x13d870e0  (reconstructed)
srcSpace = GetMemorySpace(srcMemref);
srcGran  = xla_mlo_util::TransferGranularityInBytes(target.chip_parts, srcSpace, ...);
dstSpace = GetMemorySpace(dstMemref);
dstGran  = xla_mlo_util::TransferGranularityInBytes(target.chip_parts, dstSpace, ...);

// transfer byte-count must be a multiple of each address space's granularity
if (bytes % srcGran) return InvalidArgument(
    "Bytes transferred by IndirectStreamOperation must be a multiple of "
    "src address space %d granularity %d. Got %d", srcSpace, srcGran, bytes);
if (bytes % dstGran) return InvalidArgument( /* … dest address space … */ );

if (!target.SupportsSparseCore()) LOG(FATAL) << "SparseCore is not supported by this target";

minGran = target.chip_parts->sc_min_granularity;          // chip_parts +164
if (bytes % minGran) return InvalidArgument(
    "Bytes transferred by IndirectStreamOperation must be a multiple of %d. Got %d", minGran, bytes);

// then build sc_tpu.indirect_vector_stream_start with the VREG operands as a ValueRange

```text

有两点值得保留:(1) builder 校验每个 stream 的 byte count 都必须是**两个**端点 transfer granularity 以及芯片 SparseCore minimum granularity 的倍数,这是约束单次 stream pass 的 granularity 模型;(2) 它的 diagnostic string 使用 `"IndirectStreamOperation"`(通用名称),与 SREG 形式共享。该 builder 是 VREG 形式复用的公共入口,由携带 offset 的操作数参数化。

> **NOTE — 这里的 `SupportsSparseCore()` 是硬 `LOG(FATAL)`,不同于 `SupportsScVar`。** 此 builder 断言 target 整体支持 SparseCore(`target.h:1757`);它不是 [`GetTransferKind`](getsequencertype.md#layer-1-gettransferkind--stream-vs-dma) 用来门控 kStream 路由的 `SupportsScVar` capability bit。重新实现者必须区分这两个 target predicate:`SupportsSparseCore()` 是 build-time invariant(对每个 SC generation 都为 true),`SupportsScVar` 是每 generation capability,在该 wheel 中为 0

---

## 各 Generation 的存在性

| 机制 | VF (vfc, Viperfish) | GL (glc, Ghostlite) | GF (gfc, 6acc60406) |
|---|:---:|:---:|:---:|
| `IndirectVregStream` 形式(opcode `0x38`) | yes | yes | yes |
| 位于 **TEC** Stream slot | yes | yes | yes |
| 位于 SCS Stream slot | **no** | **no** | **no** |
| 位于 TAC Stream slot | **no** | **no** | —(无 TAC) |
| `IndirectOffsets` field accessor | `0x1e936240` | `0x1ea7be20` | `0x1ebe30a0` |
| TEC IndirectVreg field-accessor 数量 | 21 | 22 | 26 |
| SCS / TAC IndirectVreg field-accessor 数量 | 0 | 0 | 0 |
| `sc_tpu.indirect_vector_stream_start` op + 3 个 index group | yes | yes | yes |

判别依据是 field-accessor namespace 前缀:每个 `IndirectVregStream` field accessor 都位于 `SparseCoreTecStream…` 下;任何 generation 中,在 `SparseCoreStream…`(SCS)或 `SparseCoreTacStream…`(TAC)下都**没有**。每代 TEC accessor 数量(21/22/26)的增长,是因为更多共享尾部字段获得了显式 accessor,而不是因为形式改变。`jxc`/`pxc` namespace 下没有 `SparseCoreStream` schema(这些 generation 没有 SparseCore)。

---

## 限制和待解决项

| 项目 | 状态 |
|---|---|
| Proto 差异 `#1…#4`(VREG offsets + mask + base 替代 2 个 SREG 操作数) | 从 descriptor 解码;共享 `#5…#22` 与 `IndirectStream` 相同 |
| `IndirectOffsets` / `IndirectAccessLengths` slot selector(6-bit,`+0x28>>27` / `+0x30>>2`) | 已读取 accessor body(`0x1ebe30a0` / `0x1ebe30c0`) |
| Opcode `0x38` @ bundle bit 181;两个 width-6 VREG selector @ bits 283 / 322 | TEC encoder `@0x1ebe33e0` 的 `case 0xB`:`BitCopy(…,181,…,6)`、`(…,283,…,6)`、`(…,322,…,6)` |
| 仅 TEC(SCS bound `0xa`,无 SCS/TAC field accessor) | encoder bound + 零 accessor 计数 |
| Op:3 个 index group,无 `OffsetIndices`,无 `getIndirectListType` | 已定位 getter;offset/list-type getter 缺失 |
| Index expansion 运行 3 个 group(Src/Dst/Sflag),没有第 4| `expandSCStreamStart<IndirectVectorStreamStartOp>` `@0x134eb880` 主体 |
| VREG 操作数位于 adaptor segments 5/8/9,并作为 `Value` 解引用 | `rewriteSparseCoreStreamOpToLLVM` `@0x13560ce0` 主体 |
| 9 路 `(hbm4b, src, dst)` 逐路由 intrinsic dispatch + diagnostic | 九个 emitter closure + match chain;`emitError` string |
| Builder 校验 transfer byte-count 与 src/dst/min granularity | `InitiateIndirectVectorStreamOperation` `@0x13d870e0` 主体 |
| 逐 lane HW 地址算术(`base + offsets[lane] * list_stride`) | lowering 中无软件循环;HW intrinsic |
| Minibatch / variable-length 用例 | 由 `valid_offset_mask` + register-sourced ids 推断 |
| 6-bit selector 背后的物理 VREG-file 大小 | selector width 已知;file size 是 chip_parts geometry,尚未枚举 |
| 9 个 emitter lambda 到具体 `(src,dst)` memory-space pair 的映射 | dispatch 结构已确认;每个 lambda 的 space triple 未穷尽解码 |
| 512-bit bundle 中 TEC Stream slot 的绝对 bit base | 已知 opcode @ 181 / `indirect_offsets` @ 322;完整 slot-base partition 未映射 |

---

## 交叉引用

- [Stream Gather/Scatter](stream-gather-scatter.md) — `SparseCoreStream` proto、共享控制尾部、slot 位编码,以及此形式镜像的 `IndirectStream` indirect-DMA 描述符;逐元素地址公式。
- [getSequencerType](getsequencertype.md) — 将此形式固定到 TEC 并提供逐索引 dispatch 所用 `(src,dst)` 三元组的 SCS/TAC/TEC engine-assignment 策略(`GetTransferKind` kStream-vs-kDma)。
- [VectorLoad Slot](vectorload-slot.md) — 消耗 streamed rows 的 TILE_SPMEM gather-load 对应项。
- [SparseCore Architecture](architecture.md) — engine 角色和完整 embedding datapath。
- [SparseCore Overview](overview.md) — 此形式从寄存器执行的 host-table → HBM → SC gather 路径。
- [Embedding Minibatching](embedding-minibatching.md) — register-sourced id stream 服务的 per-physical-core minibatch 模型。
- [CBREG Circular-Buffer Register](cbreg.md) — 共享尾部 offset source 可推进的 sliding-window register
- **Binary:** `extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so`(build-id `89edbbe81c5b328a958fe628a9f2207d`)
- **Index entry:** Part IX — SparseCore & BarnaCore / SparseCore pointers & DMA — [返回索引](../index.md)