Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ctype / coproc / funcUnit / bypass Tables

A FLIX bundle decoded by the libisa-core decode model hands the rest of the toolchain (opcode, slot, operand) triples; the register-files page binds each operand's (regfile, index). This page closes the loop on the last four metadata tables a reimplementer must model to type, enable, schedule, and time those operands:

  • ctypes[] — the 64 custom register-value types (TIE C-types). Every operand slot, and every register file, carries a ctype that fixes the operand's bit width, its byte alignment, the regfile it lives in, and how a value of that type is loaded, stored, moved, and converted.
  • ctype_protos[] — the 651 value-access protocols: for each (ctype, access-kind) pair the named instruction-sequence binding the C/TIE compiler expands to load/store/move/convert a value of that type. This is the per-value-type counterpart of the per-state transfer protos.
  • coprocs[] — the single coprocessor, {name="Vision", number=1} (CP1). The one IVP32 SIMD coprocessor that owns the six SIMD register files and the floating-point control state.
  • funcUnits[] — the single pipeline functional-unit hazard object, {name="XT_LOADSTORE_UNIT", num_copies=2}. The dual-copy load/store unit is the only structural port hazard the ISA declares; it is the silicon basis for the 1 + 1 co-issue bound.
  • bypass groups / chunks / entrystructurally absent in this config (all three count accessors return 0). The forwarding network is not a table here; the authoritative timing model is the per-operand use_stage / def_stage fields embedded in opcodes[].

Every count, base VMA, stride, field offset, and decoded string below was re-read from the shipped libisa-core.so this sessionobjdump -d on the accessor bodies (count immediates, RIP-relative lea table bases, struct field offsets and load widths), nm -S for symbol geometry, and a mmap/struct.unpack parse of the raw .data.rel.ro table bytes with the name pointers resolved into .rodata. The host-side C-stub implementation of the ctype get/set/move/ convert accessors lives in a separate binary, libctype.so, re-read the same way; its deep treatment is the cross-linked libctype CSTUB page. No external or vendor source tree was consulted — this is binary-derived prose only, lawful-interoperability RE.

Confidence tags follow the Confidence & Walls Model: OBSERVED = a byte/string/immediate read from a shipped artifact; INFERRED = reasoned over OBSERVED facts; crossed with HIGH/MED/LOW. Callouts: QUIRK (counter-intuitive but real), GOTCHA (a reimplementation trap), CORRECTION (overturns a naive reading), NOTE (orienting context). Where folklore disagrees with the binary, the binary wins.

GOTCHA — the .data.rel.ro VMA↔file-offset delta is 0x200000 in libisa-core.so. All four tables on this page live in .data.rel.ro (readelf -SW: VMA 0x67bb00 ↔ file 0x47bb00). Their name/regfile/ctype pointer fields are link-time-absolute VMAs into .rodata, which is VMA == file offset (delta 0). Before xxd/objdump -s-ing a table struct, subtract 0x200000; resolving a name pointer out of it needs no adjustment. The delta is per-binarylibctype.so happens to share 0x200000 for its .data/.bss, but never generalize a delta across binaries. [HIGH/OBSERVED]

This page deliberately does not restate the table-ABI export list or the (opcode × slot) encode matrix — those are the libisa Table Schema — nor the headline funcUnit/coproc one-liners of the Config Reference Sheet §5. It owns the interior of these four tables.


1. Key facts

The five count accessors and the four table geometries, each re-read this session. Every count is a literal mov $imm,%eax; ret (or xor %eax,%eax; ret for the three empty bypass accessors); the table symbol sizes (nm -S) confirm count × stride byte-exact.

Table / accessorValueSource (re-read this session)Tag
num_ctypes64 (0x40)@ 0x3b67d0b8 40 00 00 00 (mov $0x40,%eax)HIGH/OBS
ctypes[] tablebase VMA 0x6cbb00, stride 64, 64 entriesctype_name @ 0x3b67e0 lea …<ctypes>; shl $6HIGH/OBS
ctypes symbol size0x1000 = 64 × 0x40nm -S libisa-core.soHIGH/OBS
num_ctype_protos651 (0x28b)@ 0x3b6d30mov $0x28b,%eaxHIGH/OBS
ctype_protos[] tablebase VMA 0x67bb40, stride 32, 651 entriesctype_proto_name @ 0x3b6d40 lea …; shl $5HIGH/OBS
ctype_protos symbol size0x5160 = 651 × 0x20nm -SHIGH/OBS
num_coprocs1@ 0x3b6dc0mov $0x1,%eaxHIGH/OBS
coprocs[] tablebase VMA 0x67bb00, stride 16, 1 entrycoproc_name @ 0x3b6dd0 lea …<coprocs>; shl $4HIGH/OBS
coprocs symbol size0x10 = 1 × 0x10nm -SHIGH/OBS
num_funcUnits1@ 0x3b5bd0mov $0x1,%eaxHIGH/OBS
funcUnits[] tablebase VMA 0x74a9c0, stride 16, 1 entryfuncUnit_name @ 0x3b5be0 lea …<funcUnits>; shl $4HIGH/OBS
funcUnits symbol size0x10 = 1 × 0x10nm -SHIGH/OBS
num_bypass_groups0@ 0x3b61a031 c0 (xor %eax,%eax)HIGH/OBS
num_bypass_group_chunks0@ 0x3b61b0xor %eax,%eaxHIGH/OBS
bypass_entry0 (const-0 stub)@ 0x3b61c0xor %eax,%eaxHIGH/OBS
num_opcodes (timing carrier)1534 (0x5fe)@ 0x3b61d0mov $0x5fe,%eaxHIGH/OBS

[HIGH/OBSERVED] on every row. The coprocs[] base (0x67bb00) and ctype_protos[] base (0x67bb40) are adjacent — coprocs is the single 16-byte record that opens .data.rel.ro, ctype_protos follows immediately at +0x40.


2. The ctypes[] table — 64 custom register-value types

A ctype (TIE C-type) is the codec's value-type descriptor: it is what types an operand slot. When the decoder produces an operand, the operand's ctype fixes (a) the storage/value width in bits, (b) the byte alignment, (c) the regfile the value lives in, (d) the regfile view (for the boolean packs), (e) how many underlying regfile entries the value spans, and (f) whether it is a core scalar C-type or a SIMD/boolean value. The register-files table's ctype field names the default ctype of each file; this table holds the full roster of 64 ctypes, of which a single regfile carries many (e.g. vec is the regfile for 27 different ctypes — vec2Nx8, vecNx16, vecN_2x32, int8, … — all different lane interpretations of the same 512-bit storage).

2.1 Accessor-proven struct layout

Each ctype field has a one-line accessor; the offset and load width come straight out of the mov OFF(%rax,%rdi,1) in each. The ctype_name accessor proves the geometry:

ctype_name @ 0x3b67e0:
  movslq %edi,%rdi
  lea    0x315316(%rip),%rax        # 6cbb00 <ctypes>     base VMA 0x6cbb00
  shl    $0x6,%rdi                  # idx * 0x40          stride 64
  mov    (%rax,%rdi,1),%rax         # +0x00  name ptr
struct xtensa_ctype_internal {     // 64 bytes (stride 0x40), 64 entries
    const char *name;              // +0x00  e.g. "_TIE_xt_ivp32_xb_vec2Nx8"
    const char *package;           // +0x08  "xt_core" | "xt_booleans" | "xt_ivp32" | "VectraFP"
    int32_t     num_bits;          // +0x10  storage/value width in bits
    int32_t     alignment;         // +0x14  byte/bit alignment (== num_bits for the SIMD types)
    const char *regfile;           // +0x18  regfile this value type lives in (one of the 8, §register-files)
    const char *regfile_view;      // +0x20  regfile VIEW (BR2/BR4/BR8/BR16 for the sub-bool packs)
    int32_t     num_regs;          // +0x28  # of underlying regfile entries the value spans
    uint32_t    flags;             // +0x2c  0x0001 = core scalar C type ; 0x0000 = SIMD/bool
    char      **field_types;       // +0x30  field_types[j] (struct decomposition) or NULL
    char      **field_names;       // +0x38  field_names[j] or NULL
};  // symbol size 0x1000 = 64*0x40 confirms exactly 64 entries, no 65th.

The field accessors (all OBSERVED this session, addresses in libisa-core.so):

AccessorVMAFieldOffset / width
ctype_name0x3b67e0name+0x00 ptr
ctype_package0x3b6800package+0x08 ptr
ctype_num_bits0x3b6820num_bits+0x10 int32
ctype_alignment0x3b6840alignment+0x14 int32
ctype_regfile0x3b6860regfile+0x18 ptr
ctype_regfile_view0x3b6880regfile_view+0x20 ptr
ctype_num_regs0x3b68a0num_regs+0x28 int32
ctype_flags0x3b68c0flags+0x2c uint32
ctype_field_type0x3b68e0field_types[j]+0x30 ptr → ptr[]
ctype_field_name0x3b6900field_names[j]+0x38 ptr → ptr[]

2.2 The full 64-ctype roster

Byte-exact mmap parse of file 0x4cbb00, name/package/regfile pointers resolved into .rodata, this session. The four packages partition cleanly: 13 core scalar C-types (flags=0x0001, regfile AR), 5 core booleans (regfile BR), and 46 Vision IVP32 SIMD value types (flags=0x0000, the six SIMD regfiles). [HIGH/OBSERVED]

idxnamepkgbitsalignregfileviewnum_regsflagsfields
0_TIE_uint8xt_core88ARAR10x0001
1_TIE_int8xt_core88ARAR10x0001
2_TIE_uint16xt_core1616ARAR10x0001
3_TIE_int16xt_core1616ARAR10x0001
4_TIE_uint32xt_core3232ARAR10x0001
5_TIE_int32xt_core3232ARAR10x0001
6_TIE_uint64xt_core6464ARAR20x0001{hi,lo}
7_TIE_int64xt_core6464ARAR20x0001{hi,lo}
8_TIE_charxt_core88ARAR10x0001
9_TIE_shortxt_core1616ARAR10x0001
10_TIE_unsignedxt_core3232ARAR10x0001
11_TIE_intxt_core3232ARAR10x0001
12_TIE_voidxt_core3232ARAR10x0001
13_TIE_xtboolxt_booleans88BRBR10x0000
14_TIE_xtbool2xt_booleans88BRBR220x0000{b1,b0}
15_TIE_xtbool4xt_booleans88BRBR440x0000{b3..b0}
16_TIE_xtbool8xt_booleans88BRBR880x0000{b7..b0}
17_TIE_xtbool16xt_booleans1616BRBR16160x0000{b15..b0}
18_TIE_xt_ivp32_valignxt_ivp32512512valignvalign10x0000
19_TIE_xt_ivp32_vboolNxt_ivp323232vboolvbool10x0000
20_TIE_xt_ivp32_vbool2Nxt_ivp326464vboolvbool10x0000
21_TIE_xt_ivp32_xb_vec2Nx8xt_ivp32512512vecvec10x0000vec default
22_TIE_xt_ivp32_xb_vec2Nx8Uxt_ivp32512512vecvec10x0000
23_TIE_xt_ivp32_xb_vecN_2x16xt_ivp32256256vecvec10x0000
24_TIE_xt_ivp32_xb_vecN_2x16Uxt_ivp32256256vecvec10x0000
25_TIE_xt_ivp32_vboolN_2xt_ivp321616vboolvbool10x0000
26_TIE_xt_ivp32_xb_vecN_2x64wxt_ivp321024512wvecwvec10x0000
27_TIE_xt_ivp32_xb_int64wxt_ivp326464wvecwvec10x0000
28_TIE_xt_ivp32_xb_int16xt_ivp321616vecvec10x0000
29_TIE_xt_ivp32_xb_int16Uxt_ivp321616vecvec10x0000
30_TIE_xt_ivp32_xb_vecNx16xt_ivp32512512vecvec10x0000
31_TIE_xt_ivp32_xb_vecNx16Uxt_ivp32512512vecvec10x0000
32_TIE_xt_ivp32_xb_int24xt_ivp322432wvecwvec10x0000
33_TIE_xt_ivp32_xb_vec2Nx16wxt_ivp321024512wvecwvec10x0000
34_TIE_xt_ivp32_xb_vec2Nx16Uwxt_ivp321024512wvecwvec10x0000
35_TIE_xt_ivp32_xb_vec2Nx24xt_ivp322048512wvecwvec10x0000
36_TIE_xt_ivp32_xb_int256xt_ivp32256256vecvec10x0000
37_TIE_xt_ivp32_xb_vecN_16x256xt_ivp32512512vecvec10x0000
38_TIE_xt_ivp32_xb_vecNx32xt_ivp321024512wvecwvec10x0000
39_TIE_xt_ivp32_xb_vecNx32Uxt_ivp321024512wvecwvec10x0000
40_TIE_xt_ivp32_xb_vecNx48xt_ivp322048512wvecwvec10x0000
41_TIE_xt_ivp32_xb_int48xt_ivp324864wvecwvec10x0000
42_TIE_xt_ivp32_xb_int128xt_ivp32128128vecvec10x0000
43_TIE_xt_ivp32_xb_vecN_8x128xt_ivp32512512vecvec10x0000
44_TIE_VectraFP_xtfloatVectraFP3232vecvec10x0000scalar fp in vec lane
45_TIE_xt_ivp32_xb_vecN_2xf32xt_ivp32512512vecvec10x0000
46_TIE_xt_ivp32_xb_vecN_2xf16xt_ivp32256256vecvec10x0000
47_TIE_VectraFP_xb_f16VectraFP1616vecvec10x0000
48_TIE_xt_ivp32_xb_vecNxf16xt_ivp32512512vecvec10x0000
49_TIE_xt_ivp32_vbool1xt_ivp3218vboolvbool10x0000
50_TIE_xt_ivp32_xb_int8xt_ivp3288vecvec10x0000
51_TIE_xt_ivp32_xb_int8Uxt_ivp3288vecvec10x0000
52_TIE_xt_ivp32_xb_vecNx8xt_ivp32256256vecvec10x0000
53_TIE_xt_ivp32_xb_vecNx8Uxt_ivp32256256vecvec10x0000
54_TIE_xt_ivp32_xb_int32vxt_ivp323232vecvec10x0000
55_TIE_xt_ivp32_xb_int32Uvxt_ivp323232vecvec10x0000
56_TIE_xt_ivp32_xb_vecN_2x32vxt_ivp32512512vecvec10x0000
57_TIE_xt_ivp32_xb_vecN_2x32Uvxt_ivp32512512vecvec10x0000
58_TIE_xt_ivp32_xb_int64xt_ivp326464vecvec10x0000lane int64
59_TIE_xt_ivp32_xb_vecN_4x64xt_ivp32512512vecvec10x0000
60_TIE_xt_ivp32_xb_int32prxt_ivp323232b32_prb32_pr10x0000
61_TIE_xt_ivp32_xb_int64prxt_ivp326464b32_prb32_pr10x0000b32_pr default
62_TIE_xt_ivp32_xb_gsrxt_ivp32512512gvrgvr10x0000gvr default
63_TIE_xt_ivp32_xb_wvecspillxt_ivp321536512wvecwvec10x0000wvec default/spill

Regfile-binding census (the ctype.regfile column, OBSERVED): vec 27, AR 13, wvec 11, BR 5, vbool 4, b32_pr 2, valign 1, gvr 1 — total 64. The eight regfile names are exactly the eight register files: {AR, BR, vec, vbool, valign, wvec, b32_pr, gvr}. The flags census is 13 × 0x0001 (the core scalar C-types) + 51 × 0x0000 (SIMD/bool). The per-regfile default ctype that the register-files table cites is present at the expected index (vec → idx 21, wvec → idx 63, gvr → idx 62, b32_pr → idx 61, valign → idx 18, vbool → idx 20, AR → idx 4 _TIE_uint32, BR → idx 13 _TIE_xtbool). This is the reverse ctype → regfile map the register-files page cross-links to here.

NOTE — alignment is in bits for the wide SIMD types and equals num_bits for the others; the wide wvec ctypes pin alignment = 512. For the scalar/boolean types alignment == num_bits (8, 16, 32, 64). For the SIMD types alignment == num_bits except the wvec ctypes (idx 26, 33, 34, 35, 38, 39, 40, 63) and int24/int48, where alignment is a coarser grain than the value width: wvecspill is num_bits=1536 but alignment=512 (the 512-bit machine register the 1536-bit accumulator spills through). A reimplementer aligns wide accumulator spills to 512 bits, not 1536. [HIGH/OBSERVED]

2.3 Composite ctypes — the field_types / field_names decomposition

Only 6 of the 64 ctypes carry non-NULL field arrays — the decomposable aggregate types. The array lengths are confirmed by the Ctype__TIE_*_fieldNames / _fieldTypes symbol sizes (nm -S, this session): each pointer is 8 bytes, so size ÷ 8 is the field count.

ctypesym sizefield countfield_namesfield_types
_TIE_uint64 (idx 6)0x102{hi, lo}{uint32, uint32}
_TIE_int64 (idx 7)0x102{hi, lo}{int32, uint32}
_TIE_xtbool2 (idx 14)0x102{b1, b0}{xtbool ×2}
_TIE_xtbool4 (idx 15)0x204{b3..b0}{xtbool ×4}
_TIE_xtbool8 (idx 16)0x408{b7..b0}{xtbool ×8}
_TIE_xtbool16 (idx 17)0x8016{b15..b0}{xtbool ×16}

The 64-bit AR types decompose into {hi, lo} 32-bit halves — matching num_regs = 2, the two-AR pair — and the boolean packs decompose into their N xtbool bits. The other 58 ctypes are atomic (both arrays NULL). A reimplemented C/TIE front-end uses this aggregate descriptor to spill/reload a uint64 as two AR words and to address an individual boolean inside a pack. [HIGH/OBSERVED]


3. The ctype_protos[] table — 651 value-access protocols

ctype_protos[] is the codec's value-access binding: for each (ctype, access-kind) pair it names the protocol whose proto_TIE_* operand/tmp/insn-arg symbol set the C/TIE compiler expands when it must load, store, move, or convert a value of that ctype. It is the per-value-type analogue of the per-state transfer protos that handle the two wide save/restore user-states ([states / sysregs page] — the two proto families are orthogonal: state-transfer vs value-codec).

3.1 Struct layout

ctype_proto_name @ 0x3b6d40:
  lea    …<ctype_protos 0x67bb40>; shl $5  (idx*0x20); mov (%rax,%rdi,1)    +0x00 ptr
struct xtensa_ctype_proto_internal {   // 32 bytes (stride 0x20), 651 entries
    const char *name;        // +0x00  "_TIE_<pkg>_<ctype>_<kind>[_<other>]"
    const char *ctype;       // +0x08  the source/owning ctype name
    const char *kind;        // +0x10  protocol-kind STRING (see §3.2)  — a char*, NOT an int!
    const char *other_type;  // +0x18  the target ctype (convert protos), else ""
};  // symbol size 0x5160 = 651*0x20 confirms 651 entries.

CORRECTION — ctype_proto.kind is a char* string, not an enum int. The ctype_proto_kind accessor (0x3b6d80) does mov 0x10(...),%rax — a full 8-byte pointer load. Reading the field as int32 yields garbage (the low 32 bits of a .rodata pointer). The kind is one of the twelve strings {rtor, mtor, rtom, loadi, storei, move, loadip, storeip, loadx, storex, loadxp, storexp}. [HIGH/OBSERVED]

3.2 The kind census

mmap parse of all 651 records this session (kind and other_type resolved into .rodata):

countkindmeaning
205rtorregister→register move/convert (cross-ctype when other_type is set)
71mtormemory→register convert (widen/pack on load)
64rtomregister→memory convert (narrow/unpack on store)
47loadiimmediate-offset load
47storeiimmediate-offset store
47movesame-ctype move
31loadipload + post-increment-immediate
31storeipstore + post-increment-immediate
27loadxindexed (register-offset) load
27storexindexed store
27loadxpindexed load + post-increment
27storexpindexed store + post-increment
651(sum, OBSERVED)

340 of the 651 protos carry a non-empty other_type (the convert protos — rtor/mtor/rtom between two ctypes, e.g. vbool1_rtor_uint32, vecNx32U_mtor_xb_vecNx48, vec2Nx24_rtom_xb_wvecspill); the remaining 311 are the same-type access protos (load/store/move). [count HIGH/OBSERVED; the convert-direction reading is MED/INFERRED from the name + other_type.]

The rtor 205 / loadi 47 / storei 47 triple is the authoritative link to the host C-stub library: libctype.so implements exactly these three kinds as 299 Function_TIE_* stubs (the other 9 kinds are table-only, synthesized caller-side). See §6 and the libctype CSTUB page.


4. The coprocs[] table — the single Vision coprocessor

The config declares exactly one coprocessor. Its 16-byte record opens .data.rel.ro.

4.1 Struct + decoded entry

struct xtensa_coproc_internal {    // 16 bytes (stride 0x10), 1 entry
    const char *name;    // +0x00
    int32_t     number;  // +0x08
};  // symbol size 0x10 = 1*0x10 confirms exactly ONE coproc.

Raw bytes coprocs @ VMA 0x67bb00 (file 0x47bb00), this session:

0047bb00:  0b b8 3b 00 00 00 00 00   01 00 00 00 00 00 00 00
           ^^^^^^^^^^^ name ptr      ^^^^^^^^^^^ number = 1
           = 0x003bb80b -> ".rodata" -> "Vision\0"
coprocs[0] = { name = "Vision", number = 1 }    (CP1)

[HIGH/OBSERVED]. Vision is the IVP32 SIMD coprocessor. It is the coproc tag carried on all six SIMD register files (vec, vbool, valign, wvec, b32_pr, gvr — see register-files §3) and on the 19 IVP32 floating-point control/status states (RoundMode, the sticky flags + enables, the IVP_FS* saved-state). AR and BR are the core files (coproc = "", the empty string — not NULL); they belong to no coprocessor.

4.2 coprocCPENABLE ↔ state — the 1-vs-7 reconciliation

The config has two different "coprocessor" counts that measure two different things; they do not conflict:

CountValueWhat it measures
num_coprocs (this table)1named coprocessors that own state / a regfile in this config
CPENABLE width / XCHAL_CP_MAXCFG7the architectural coprocessor-enable register width (CP0..CP6)

The Xtensa core option fixes 7 coprocessor-ID slots — the CPENABLE enable bitmask has seven bits (CP0..CP6). But in the Vision-Q7 ncore2gp config exactly one of those slots is populated with a named coprocessor that defines state and owns register files: Vision, number = 1, i.e. it occupies CP slot 1 of the 7-bit CPENABLE. The other six bits are unallocated/reserved (no named coproc, no state, no regfile).

So a reimplemented CPENABLE model needs exactly one meaningful enable bit (CP1): a vector op faults or stalls if CPENABLE bit 1 (Vision) is clear. (coproc 0 is conventionally the core/no-coproc slot; Vision is number = 1.) [name/number HIGH/OBSERVED; the exact bit index 1 ↔ Vision is MED/INFERRED from number = 1 + the 7-bit width + the single populated entry.]

GOTCHA — do not infer seven coprocessors from XCHAL_CP_MAXCFG = 7. Six of the seven CPENABLE slots are empty in this config. The shipped coprocs[] table has one entry, and num_coprocs is the arbiter: model one coprocessor. The seven is the enable-register width, not the coprocessor count. [HIGH/OBSERVED]


5. The funcUnits[] table — the dual-copy load/store unit

The config declares exactly one pipeline functional-unit hazard object.

5.1 Struct + decoded entry

struct xtensa_funcUnit_internal {  // 16 bytes (stride 0x10), 1 entry
    const char *name;       // +0x00
    int32_t     num_copies; // +0x08
};  // symbol size 0x10 = 1*0x10 confirms exactly ONE funcUnit.

Raw bytes funcUnits @ VMA 0x74a9c0 (file 0x54a9c0), this session:

0054a9c0:  d3 d0 3c 00 00 00 00 00   02 00 00 00 00 00 00 00
           ^^^^^^^^^^^ name ptr      ^^^^^^^^^^^ num_copies = 2
           = 0x003cd0d3 -> ".rodata" -> "XT_LOADSTORE_UNIT\0"
funcUnits[0] = { name = "XT_LOADSTORE_UNIT", num_copies = 2 }

[HIGH/OBSERVED]. The only structural functional-unit hazard the ISA models is the load/store unit, with two copies — the two parallel memory pipes (the _0/_1 dual LSU). There is no separate ALU / Mul / Ld funcUnit object: ALU, Mul, Ld, LdSt are FLIX issue slots (flix-encoding), not funcUnit hazard records. The num_copies = 2 is the silicon basis for the 1 + 1 co-issue bound (§7).

5.2 Per-opcode funcUnit binding — who uses the LSU

Each opcode in opcodes[] (base 0x6ce6c0, stride 72) carries a num_funcUnit_uses count (+0x30) and a funcUnit_use[] array (+0x38), each use a {const char* unit; int32 stage} record of stride 16. Sweeping all 1534 opcodes this session:

num_funcUnit_usesopcode count
01291
1238
25

Every one of the 248 funcUnit_use records names the same unit, XT_LOADSTORE_UNIT, every one at stage 0. So:

  • 1291 opcodes use no functional unit — the ALU / Mul / predicate ops. Their hazard is the FLIX issue grid + the per-operand stage scoreboard (§6), not a funcUnit object.

  • 238 opcodes occupy one LSU copy — the scalar + vector loads/stores (l32i.n, s32i.n, l16ui, l32r, l32ex, s32ex, the ivp_l* / ivp_s* vector loads/stores, …).

  • 5 opcodes occupy both LSU copies — the dual-pumped 2-vector (l2*) loads that consume both memory pipes in one issue:

    ivp_l2a4nx8_ip   ivp_l2au2nx8_ip   ivp_l2au2nx8_ipi   ivp_l2u2nx8_xp   ivp_l2au2nx8_xp
    

A reimplemented hazard model treats the funcUnit as pure load/store-port occupancy on the 2-copy LSU: a single load/store takes one copy; the five dual loads need both copies free. [HIGH/OBSERVED]


6. The bypass table is structurally absent — timing lives per-operand

6.1 The bypass-group table is empty

num_bypass_groups       @ 0x3b61a0 :  xor %eax,%eax ; ret   -> 0
num_bypass_group_chunks @ 0x3b61b0 :  xor %eax,%eax ; ret   -> 0
bypass_entry            @ 0x3b61c0 :  xor %eax,%eax ; ret   -> 0

libisa-core declares no bypass groups and no group chunks; bypass_entry() is a constant-0 stub. The forwarding/bypass network as an explicit group table is structurally absent in this config — the same dead-stub pattern as the (empty) interface table.

GOTCHA — there is no forwarding-network table to parse; do not invent per-port reservation data. Because the bypass-group table is empty, the per-port functional-unit reservation matrix a scheduler would read (the MODULE_SCHEDULE data) is not populated in this corpus. With the reservation data absent, the per-cycle co-issue ceiling is not recoverable beyond the structural bound the format roster + the 2 LSU copies give (§7). The 1 + 1 co-issue is the sound, non-speculative bound. This is the empty-MODULE_SCHEDULE wall. [HIGH/OBSERVED on the empty table; the absence of a tighter per-port model is OBSERVED-negative.]

6.2 The authoritative timing is the per-operand use_stage / def_stage

The real latency model lives in the opcodes[] operand-use sub-array (opc+0x20, stride 6):

struct operand_use {     // 6 bytes per operand
    uint8_t  use_stage;     // +0x00  stage the source operand is READ   (0xFF = not a source)
    uint8_t  def_stage;     // +0x01  stage the dest operand is WRITTEN  (0xFF = not a def)
    uint16_t byp_use_grp;   // +0x02  bypass-group index — 0xFFFF (no group; table is empty)
    uint16_t byp_def_grp;   // +0x04  bypass-group index — 0xFFFF
};

Accessors (this session): opcode_operand_use_stage @ 0x3b6260 (movzbl (.)), opcode_operand_def_stage @ 0x3b6290 (movzbl 0x1(.)), opcode_bypass_use_group_idx @ 0x3b6490 (movzwl 0x2(.)), opcode_bypass_def_group_idx @ 0x3b6460 (movzwl 0x4(.)). State-operand uses (opc+0x28, stride 3) carry use_stage / def_stage / allow_reorder.

Sentinels (OBSERVED): use_stage == 0xFF means "not a source", def_stage == 0xFF means "not a def"; byp_use_grp == byp_def_grp == 0xFFFF means "no bypass group" — every operand carries the null 0xFFFF index because there are zero groups to index into.

Worked example — add.n (opcode idx 4), the three operand-use records, re-read this session:

op0 (dst):  use_stage=0xFF  def_stage=4     byp=0xFFFF/0xFFFF   -> AR result ready @ STAGE 4
op1 (src):  use_stage=4     def_stage=0xFF  byp=0xFFFF          -> reads its source @ stage 4
op2 (src):  use_stage=4     def_stage=0xFF  byp=0xFFFF

The def_stage of the destination operand is the producer result latency, and the use_stage of a source operand is the consumer read latency. The forwarding rule a reimplemented scoreboard applies is purely stage arithmetic: a value is forwardable from its def_stage onward; a consumer at use_stage stalls iff (def_stage − elapsed) > use_stage. No group/chunk indirection is used. [HIGH/OBSERVED]

6.3 The per-unit latency table (operand-0 def_stage distribution)

Sweeping the destination-operand def_stage over all opcodes this session yields the authoritative per-class result latency:

def_stageopcode countclass
115def-post / post-increment address update
32(CPENABLE-check class)
480scalar AR result (add.n, addi.n)
595scalar (load-use / 2nd-stage scalar)
68
96valign / align result (ivp_la_pp, lalign_i)
10106vector result @ execute stage (vec / vbool / gather)
11346vector add (int) / many SIMD
12485MUL / MAC (wvec) / DSEL — the deepest common vector retire
13115FP-vector add / MAC — one stage deeper than the integer pipe

Representative ops, (use, def) of the destination operand, re-read this session:

opdst (use, def)latency class
add.n / addi.n(0xFF, 4)AR scalar, LAT 4
ivp_addnx16(0xFF, 11)int vector add
ivp_dselnx16(0xFF, 12)DSEL / 2-cycle vec
ivp_mul2nx8(0xFF, 12)int MUL → wvec, LAT 12
ivp_mulan_2xf32(10, 13)FP MAC, LAT 13 (accumulator read @10)
ivp_addn_2xf32(0xFF, 13)FP vector add, LAT 13
ivp_la_pp(0xFF, 9)valign result, LAT 9
ivp_packvrnx48(0xFF, 12)wvec → vec pack readout, LAT 12

The recurring source use_stage = 10 across the SIMD ops is the execute stage where the vector value is read; the recurring def_stage = 4 for scalar AR is the early scalar result. These per-operand stages are the authoritative source the cycle-accurate ISS turns into interlocks — see the cas timing-model page for the reconstructed scoreboard; this page is the table it reconstructs from. [stage numbers HIGH/OBSERVED; the 1/3 auxiliary operand role-labels MED/INFERRED.]


7. The 1 + 1 co-issue bound (why the LSU copies matter)

A wide FLIX format declares up to 5 slots, but the scheduler's real question is how many ops co-issue per functional unit per cycle. The per-port reservation that would answer this — the MODULE_SCHEDULE matrices — is empty (§6.1). With the reservation data absent, the sound bound is the structural one: the single XT_LOADSTORE_UNIT with num_copies = 2 caps the bundle at two memory ops (the LdSt + Ld slots), plus the format's Mul + ALU slots — 1 load/store-class op + 1 of each other class, capped by the 2 LSU copies. The five dual l2* loads consume both copies, so they cannot co-issue with another memory op. Do not infer a tighter per-cycle throughput than the format roster + the 2 LSU copies allow; the empty MODULE_SCHEDULE makes a per-port hazard model unrecoverable from this corpus. This bound is shared with the Config Reference Sheet §4.1. [HIGH/OBSERVED on the slot rosters and the 2 LSU copies; the absent tighter model is OBSERVED-negative.]


8. The host-side codec — libctype.so get/set/move/convert (cross-reference)

The four tables above are the declarations. The host-callable implementation of the ctype value protocols — the C-stub get (loadi), set (storei), move, and convert (rtor) functions the native TIE simulator and xt-gdb invoke — lives in a separate binary, libctype.so (388,648 bytes, not stripped, VERS_1.1). It is an independent host value-marshalling layer: its only dynamic imports are libc (malloc, fprintf, exit, stderr); it does not link libisa-core, libcas-core, or libfiss-base. The full treatment is the libctype CSTUB page; the binding facts that anchor it to this page's tables:

  • The proto-kind match is byte-exact. libctype ships 299 Function_TIE_* stubs: 205 _rtor + 47 _loadi + 47 _storeiexactly the rtor 205 / loadi 47 / storei 47 counts of §3.2. The other 9 ISA-06 kinds (mtor, rtom, move, loadip/storeip, loadx/storex/loadxp/storexp) have zero host stubs — they are table-only, synthesized caller-side from base_addr arithmetic. [HIGH/OBSERVED]
  • The dispatch tables are ctype-id-indexed. Three .bss ftables, each 0x200 = 64 × 8 (cstub_loadi_ftable @ 0x2562c0, cstub_storei_ftable @ 0x2564c0, cstub_rtor_ftable @ 0x256700), indexed by ctype id 0..63 — the same 64-row index space as ctypes[]. The populated loadi entries decode byte-exact to the §2.2 indices (idx4 = uint32, idx18 = valign, idx21 = vec2Nx8, idx49 = vbool1, idx58 = int64, idx60/61 = int32pr/int64pr, idx62 = gsr, idx63 = wvecspill). [HIGH/OBSERVED]
  • The single-vs-multi-word flag. cstub_ctype_multi_word[64] (.data @ 0x256080, 64 bytes) marks idx {6, 7} ∪ {18..63} (48 entries) as multi-word — exactly the AR uint64/int64 pair (num_regs = 2, §2.3) and every Vision SIMD type (all > 32 bits). Re-read this session: a byte-exact 01 at those indices, 00 elsewhere. [HIGH/OBSERVED]

The pseudocode for the host get/set/convert dispatch, naming the real symbols:

// ── libctype.so : the 6-export VERS_1.1 dispatch surface (host value marshalling) ──
//
// One-time host pointer to the simulated target-memory window:
void cstub_set_base_address(void *p);              // @0x43850 : base_addr = p

// GET / SET : pure ctype-id table lookups returning the marshalling stub.
typedef void (*loadi_fn)(reg_struct *dst, uint32_t base, uint32_t off);   // reads *dst
typedef void (*storei_fn)(const reg_struct *src, uint32_t base, uint32_t off); // writes mem
loadi_fn  cstub_loadi_function (int ctype_id);     // @0x43860 : cstub_loadi_ftable[ctype_id]
storei_fn cstub_storei_function(int ctype_id);     // @0x43870 : cstub_storei_ftable[ctype_id]

// CONVERT (rtor) : per-source sorted singly-linked list keyed on the TARGET ctype id.
//   24-byte node { int32 other_ctype_id @0x00; void *fn @0x08; node *next @0x10 }.
typedef void (*rtor_fn)(reg_struct *dst, /* scalar value OR reg_struct* */ uintptr_t src);
rtor_fn cstub_rtor_function(int src_ctype_id, int other_ctype_id) {        // @0x43880
    cstub_rtor_node *n = cstub_rtor_ftable[src_ctype_id];   // list head
    for (; n; n = n->next)                                  // kept sorted on other_ctype_id
        if (n->other_ctype_id == other_ctype_id)            // (cmp/je/ja early-out)
            return (rtor_fn)n->fn;     // Function_TIE_<src>_rtor_<other>
    return NULL;                       // no such convert
}

// SIZE (single/multi-word) : the per-ctype byte flag.
uint8_t cstub_ctype_size_function(int ctype_id) {  // @0x438c0
    return cstub_ctype_multi_word[ctype_id];       // 1 = spans >1 host word
}

// Build all 3 ftables at sim startup (4715 instrs); 265 (src,other) convert edges, 205 fns.
void cstub_ctypes_init(...);                        // @0x438d0

// ── representative stub bodies (the get/set/move/convert bit-layout) ──
//
// GET scalar AR (ctype idx 4):  Function_TIE_xt_core_uint32_loadi @0x3d4a0
//   vaddr = base + off;  if (vaddr & 3) cstub_vaddr_not_aligned(vaddr); // exit(1) on misalign
//   *(uint32*)dst = *(uint32*)(base_addr + (vaddr & ~3));               // 4-byte, align-checked
//
// GET 512-bit vec (ctype idx 21): Function_TIE_xt_ivp32_xb_vec2Nx8_loadi @0x3e7f0
//   memcpy *dst <- base_addr+vaddr, 64 bytes via 4x movdqu  (NO alignment check; unaligned moves)
//
// CONVERT scalar->vec widen (idx 0 -> 21): Function_TIE_xt_ivp32_uint8_rtor_xb_vec2Nx8 @0x3070
//   broadcast scalar src across SIMD lanes (movd; pshufd $0; byte-lane LUT + por), write 64 bytes
//
// CONVERT scalar->predicate (idx 5 -> 60): int32_rtor_xb_int32pr @0x113b0
//   *(uint32*)dst = (int32)src;  *(uint32*)(dst+4) = (int32)src >> 31;  // {value, sign} 64-bit pr

NOTE — move is realized as a trivial copy, not a stub. libctype has no Function_TIE_*_move symbol even though ctype_protos[] lists 47 move protos. A same-type move is the identity rtor or a loadi/storei round-trip / flat struct copy — the native sim needs no dedicated function. The 9 table-only kinds (move, mtor, rtom, and the indexed/post-inc load/store modes) are computed by the caller from base_addr arithmetic before calling loadi/storei. [count HIGH/OBSERVED; the caller-synthesis mechanism MED/INFERRED.]


9. Provenance & confidence ledger

ClaimTagWitness (re-read this session)
num_ctypes = 64; ctypes sym size 0x1000; stride 64HIGH/OBS0x3b67d0 mov $0x40; nm -S; ctype_name shl $6
The 64-ctype roster + regfile census vec27/AR13/wvec11/BR5/vbool4/b32_pr2/valign1/gvr1HIGH/OBSmmap parse of 0x4cbb00, names into .rodata
6 composite ctypes + field-array sizes {2,2,2,4,8,16}HIGH/OBSnm -S Ctype__TIE_*_fieldNames/Types
num_ctype_protos = 651; kind census; 340 convertsHIGH/OBS0x3b6d30 mov $0x28b; mmap parse 0x47bb40
kind is a char* (not int)HIGH/OBSctype_proto_kind @ 0x3b6d80 mov 0x10(.),%rax
coprocs[0] = {"Vision", 1}; sym size 0x10HIGH/OBS0x47bb00 bytes 0b b8 3b 00 … 01; → "Vision\0"
funcUnits[0] = {"XT_LOADSTORE_UNIT", 2}; sym size 0x10HIGH/OBS0x54a9c0 bytes d3 d0 3c 00 … 02; → "XT_LOADSTORE_UNIT\0"
funcUnit-use sweep {0:1291,1:238,2:5}; 248 LSU@stage0; 5 dual l2*HIGH/OBSopcodes[] sweep 0x6ce6c0
bypass groups/chunks/entry all 0 (const-0 stubs)HIGH/OBS0x3b61a0/b0/c0 xor %eax,%eax
add.n operand (use,def) = dst(FF,4) / src(4,FF); byp 0xFFFFHIGH/OBSoperand-use array of opc idx 4
dst def_stage distribution 4:80 … 11:346 12:485 13:115HIGH/OBSoperand-0 sweep over 1534 opcodes
Vision = CP slot/bit 1 of the 7-bit CPENABLEMED/INFnumber = 1 + 7-bit width + single entry
convert-direction semantics of rtor/mtor/rtomMED/INFname + other_type, corroborated by §8
1/3 auxiliary operand role-labels (post-incr / CPENABLE)MED/INFstage values OBSERVED; roles inferred

Structurally absent (do not parse as tables): bypass groups / chunks / entry (count 0); the timing is per-operand (§6). Empty-MODULE_SCHEDULE wall: the 1 + 1 co-issue is the sound bound; a tighter per-port reservation model is not recoverable from this corpus.


10. Cross-references


Provenance: the ctypes[] (0x6cbb00), ctype_protos[] (0x67bb40), coprocs[] (0x67bb00), funcUnits[] (0x74a9c0) tables, the count accessors, the opcodes[] operand-stage and funcUnit-use sub-arrays, and the empty bypass accessors were re-disassembled, nm -S-sized, and mmap-parsed in-checkout from the shipped libisa-core.so (.data.rel.ro delta 0x200000; .rodata delta 0). The libctype.so cross-reference (§8) was re-read the same way. All facts read as derived from shipped-artifact static analysis (lawful interoperability RE); no external or vendor source tree was consulted.