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

ISA Batch 01 — Vector ALU (int / compare / logic core)

This is the first per-instruction batch of the Vision-Q7 Cairo (ncore2gp) ISA reference, and it covers the integer Vector-ALU core on the vec register file: the everyday element-wise add / sub / abs / neg, signed-and-unsigned min / max, the integer compares that produce a vbool predicate (eq / lt / le / neq, signed and unsigned), and the bitwise logic (and / or / xor / not). These are the 50 opcodes a kernel emits in its hottest inner loops; they own 894 of the 12 569 shipped placements (the coverage tally's certified-perfect denominator).

Everything below is re-grounded against the shipped binaries this pass: the encoding from libisa-core.so (the Opcode_<mnem>_Slot_<slot>_encode thunks and the Field_*_get operand accessors), the value semantics by executing the matching module__xdref_* leaves in libfiss-base.so live in-process (license-free), and an independent encode/decode oracle from the device-native xtensa-elf-as/xtensa-elf-objdump (XTENSA_CORE=ncore2gp). Confidence tags per the Confidence & Walls model: [HIGH/OBSERVED] = read-from-byte / proven-by-execution, [MED/INFERRED] = reasoned over OBSERVED, […/CARRIED] = re-used at a sibling page's confidence.

Scope split — read this before pairing a mnemonic to a batch. This batch is the integer, non-predicated, base core on vec. Three boundaries are enforced so the 30 batches don't double-count:

  • Float (…nxf16 / …n_2xf32) variants → B02. Every op here has a float sibling (ivp_addnx16ivp_addnxf16); the float ones are B02's, not ours.
  • Predicated …t (throttle/merge) and the b… B-variants → B03. 14 of our 50 base opcodes carry a …t predicated form (e.g. ivp_addnx16t), and the ivp_bmin*/ivp_baddnorm* "B-variant" family is wholly B03's. We document only the base.
  • Predicate-file (vbool/b32_pr) boolean logic (ivp_andb/ivp_andnotb/ivp_orb/…) → B11. Our logic ops (ivp_and2nx8/…) operate on vec storage bitwise; the …b ops operate on the predicate files and belong to B11.
  • Composites (ivp_abssub* abs-of-difference, ivp_avg* averaging, ivp_minormax*) → not here. They share this batch's datapath and are cited as adjacency where instructive, but are owned by B03 / B24.

1. Batch key facts

FactValueBinary source
Datapath register filevec (idx 2) — 32 × 512-bit, 5-bit index, ctype …vec2Nx8regfiles[] @ 0x74a800 (register-files §3)
Predicate sink (compares)vbool (idx 3) — 16 × 64-bit per-lane masksregfiles[] idx 3
Base mnemonics this batch50§2; nm libisa-core.so distinct Opcode_ivp_*
Placements this batch894nm | rg -c on the explicit 50-op glob (§6)
Functional unitthe alu slot class (+ mul for compares, +ldst for 1-source)slots[] (flix-encoding §5)
ALU result latencysource use_stage = 10 → dest def_stage = 11 (1-cycle ALU)opcodes[] use/def stages [HIGH/OBSERVED]
Lane geometry2nx8=64×8b · nx16=32×16b · n_2x32=16×32b (one 512-bit reg)xt_ivp32.h typedefs; value-leaf width suffix
Encode-thunk ABIC7 07 imm32 [C7 47 04 0] C3imm32 = the (opcode×slot) selectorflix-encoding §6.1
Value-leaf ABI (binary op)void leaf(uint64 ctx, uint32 a, uint32 b, uint32 *out)disassembled §4
Oraclextensa-elf-as/objdump, XTENSA_CORE=ncore2gpround-trips all 50 byte-exact (§5)

The whole batch issues on the ALU slot class of the FLIX grid. An integer ALU op is legal in the last-ALU slot of every wide format (F0_S3_ALU, F1_S3_ALU, F2_S3_ALU, F3_S3_ALU, F3_S4_ALU, F4_S3_ALU, F6_S3_ALU, F7_S3_ALU, F11_S3_ALU, F11_S4_ALU) and the narrow N0_S3_ALU; the dual-issue wide formats additionally fold an ALU op into the Mul-class slot (F1_S2_Mul, F2_S2_Mul, F7_S2_Mul, …) and F1's fused S0_LdStALU. That reach — the same mnemonic legal in 15–21 distinct slots — is what lets the scheduler co-issue two ALU ops per bundle (the 1+1 co-issue ceiling, coverage-tally §7).

The whole batch is one multiplexed reference datapath. All 50 opcodes (plus their float and predicated siblings) decode into a single semantic ALU slice (ivp_sem_vec_alu, ~244 member opcodes) selected by decoded OR-group signals — op_ADD, op_SUB (b XOR (~) +cin), op_MAX/op_MIN, op_GRP_SAT (the s-clamp), op_GRP_UNSIGN (zero- vs sign-extend), op_GRP_{8,16,32}BIT (the lane-width carry-break). The arithmetic core is a two's-complement carry-chain adder with per-lane carry-break masks (propagate_mask{p0..p3}) that wrap mod 2^w at each 8/16/32-bit lane edge. A reimplementation builds one parameterized lane ALU and drives it from the decoded opcode group, exactly as §4's leaves do. [HIGH/OBSERVED]


2. Batch roster — 50 base integer-ALU opcodes

Columns: mnemonic · lanes×width · representative FLIX slot and its opcode-selector imm (the Opcode_<mnem>_Slot_<slot>_encode thunk's movl $imm, disassembled) · the vec 5-bit operand field positions in the slot-word · device byte-size · one-line lane semantics · [conf]. Every selector imm below is for the F0_S3_ALU slot specifically and was disassembled this pass — see the GOTCHA after the tables: the selector is per-(opcode×slot); there is no global add-bit. The vec operand fields (slot-word bits [14:10]/[19:15]/[24:20]) are identical for every op in this batch — they are a property of the slot, not the opcode (§3.2).

2.1 Add / sub (wrapping and saturating)

mnemoniclanes×wF0_S3_ALU selopc#/iclass#bytessemanticsconf
ivp_add2nx864×80x80ad0000982 / 89516/8a = wrap8(b + c)[HIGH/OBSERVED]
ivp_addnx1632×160x80b50000422 / 33516/8a = wrap16(b + c)[HIGH/OBSERVED]
ivp_addn_2x3216×320x80bd00001025 / 93816/8a = wrap32(b + c)[HIGH/OBSERVED]
ivp_addsnx1632×160x80c50000450 / 36316/8a = satS16(b + c) (signed clamp)[HIGH/OBSERVED]
ivp_sub2nx864×80x86b10000983 / 89616/8a = wrap8(b − c)[HIGH/OBSERVED]
ivp_subnx1632×160x86b90000423 / 33616/8a = wrap16(b − c)[HIGH/OBSERVED]
ivp_subn_2x3216×320x86a180001026 / 93916/8a = wrap32(b − c)[HIGH/OBSERVED]
ivp_subsnx1632×160x86a98000451 / 36416/8a = satS16(b − c)[HIGH/OBSERVED]

2.2 Abs / neg (wrapping and saturating)

These take one vec source and so are also legal in the LdSt slot class (§6); the table shows the F0_S3_ALU selector.

mnemoniclanes×wF0_S3_ALU selopc#/iclass#semanticsconf
ivp_abs2nx864×80x64c05c001017 / 930a = wrap8(|b|) (wraps at INT8_MIN)[HIGH/OBSERVED]
ivp_absnx1632×160x64c85c001367 / 1280a = wrap16(|b|) (wraps at 0x8000)[HIGH/OBSERVED]
ivp_absn_2x3216×320x64d85c001018 / 931a = wrap32(|b|)[HIGH/OBSERVED]
ivp_abssnx1632×160x64e85c001368 / 1281a = satS16(|b|) (abs(0x8000)=0x7fff)[HIGH/OBSERVED]
ivp_neg2nx864×80x6488d400984 / 897a = wrap8(−b)[HIGH/OBSERVED]
ivp_negnx1632×160x6488d800424 / 337a = wrap16(−b) (neg(0x8000)=0x8000)[HIGH/OBSERVED]
ivp_negn_2x3216×320x6490d0001027 / 940a = wrap32(−b)[HIGH/OBSERVED]
ivp_negsnx1632×160x6490d800452 / 365a = satS16(−b)[HIGH/OBSERVED]

2.3 Min / max (signed and unsigned)

mnemoniclanes×wF0_S3_ALU selopc#/iclass#semanticsconf
ivp_min2nx864×80x80e58000985 / 898a = (sB ≤ sC) ? b : c (signed)[HIGH/OBSERVED]
ivp_minnx1632×160x80fd8000425 / 338signed min, 16-bit[HIGH/OBSERVED]
ivp_minn_2x3216×320x86a800001028 / 941signed min, 32-bit[HIGH/OBSERVED]
ivp_minu2nx864×80x86b80000986 / 899unsigned min, 8-bit[HIGH/OBSERVED]
ivp_minunx1632×160x86a08000426 / 339unsigned min, 16-bit[HIGH/OBSERVED]
ivp_minun_2x3216×320x86a880001029 / 942unsigned min, 32-bit[HIGH/OBSERVED]
ivp_max2nx864×80x80958000987 / 900signed max, 8-bit[HIGH/OBSERVED]
ivp_maxnx1632×160x80ad8000427 / 340signed max, 16-bit[HIGH/OBSERVED]
ivp_maxn_2x3216×320x80bd80001030 / 943signed max, 32-bit[HIGH/OBSERVED]
ivp_maxu2nx864×80x80cd8000988 / 901unsigned max, 8-bit[HIGH/OBSERVED]
ivp_maxunx1632×160x80d58000428 / 341unsigned max, 16-bit[HIGH/OBSERVED]
ivp_maxun_2x3216×320x80dd80001031 / 944unsigned max, 32-bit[HIGH/OBSERVED]

2.4 Integer compare → vbool predicate (signed and unsigned)

Compares write a vbool register (not vec): each lane sets an all-ones predicate field on true, zero on false. They issue on the Mul/compare slot class as well as the ALU class, hence 21 placements each (§6). The selector below is F0_S3_ALU; all share base 0x80870000, with the relation and dtype encoded in the low bits — within this one slot only.

mnemoniclanes×wF0_S3_ALU selopc#/iclass#predicate writeconf
ivp_eq2nx864×80x80870000991 / 904vb_lane = (b == c) (sign-irrelevant)[HIGH/OBSERVED]
ivp_eqnx1632×160x80870002434 / 347vb_lane = (b == c)[HIGH/OBSERVED]
ivp_eqn_2x3216×320x808700041037 / 950vb_lane = (b == c)[HIGH/OBSERVED]
ivp_neq2nx864×80x80870200992 / 905vb_lane = (b != c)[HIGH/OBSERVED]
ivp_neqnx1632×160x80870200435 / 348vb_lane = (b != c)[HIGH/OBSERVED]
ivp_neqn_2x3216×320x808702041038 / 951vb_lane = (b != c)[HIGH/OBSERVED] opc; [MED] imm
ivp_lt2nx864×80x80870102989 / 902vb_lane = (sB < sC) (signed)[HIGH/OBSERVED]
ivp_ltnx1632×160x80870104432 / 345signed <[HIGH/OBSERVED]
ivp_ltn_2x3216×320x808701061035 / 948signed <[HIGH/OBSERVED] opc; [MED] imm
ivp_ltu2nx864×80x80870108993 / 906vb_lane = (uB < uC) (unsigned)[HIGH/OBSERVED]
ivp_ltunx1632×160x8087010a436 / 349unsigned <[HIGH/OBSERVED]
ivp_ltun_2x3216×320x8087010c1039 / 952unsigned <[HIGH/OBSERVED] opc; [MED] imm
ivp_le2nx864×80x80870006990 / 903vb_lane = (sB ≤ sC)[HIGH/OBSERVED] opc; [MED] imm
ivp_lenx1632×160x80870008433 / 346signed [HIGH/OBSERVED]
ivp_len_2x3216×320x8087000a1036 / 949signed [HIGH/OBSERVED] opc; [MED] imm
ivp_leu2nx864×80x8087000c994 / 907vb_lane = (uB ≤ uC)[HIGH/OBSERVED] opc; [MED] imm
ivp_leunx1632×160x8087000e437 / 350unsigned [HIGH/OBSERVED]
ivp_leun_2x3216×320x80870…e1040 / 953unsigned [HIGH/OBSERVED] opc; [MED] imm

2.5 Bitwise logic (element-agnostic, full 512-bit)

These are width-agnostic full-register bitwise ops; the 2nx8 suffix is conventional spelling only (an AND over a 512-bit register is the same regardless of lane interpretation, §4.4). Note the contiguous opcode block (xor 418, and 419, or 420, not 421).

mnemoniclanes×wF0_S3_ALU selopc#/iclass#semanticsconf
ivp_and2nx8512-bit0x80cd0000419 / 332a = b & c[HIGH/OBSERVED]
ivp_or2nx8512-bit0x86a90000420 / 333a = b | c[HIGH/OBSERVED]
ivp_xor2nx8512-bit0x86b18000418 / 331a = b ^ c[HIGH/OBSERVED]
ivp_not2nx8512-bit0x6490dc00421 / 334a = ~b (1 source)[HIGH/OBSERVED]

= the low-bit step is extrapolated from the disassembled eq/lt/le nibble structure (§3.3); the base 0x80870000 and the opc#/iclass# are [HIGH/OBSERVED], the imm cells [MED/INFERRED]. = neqnx16 and neq2nx8 both show base 0x80870200 for the neq relation across the disassembled cells (the dtype nibble distinguishes width elsewhere in the field); [HIGH/OBSERVED] on neqnx16, neq2nx8.

GOTCHA — there is no global "add bit" or "subtract bit"; the selector is per-(opcode×slot). ivp_addnx16 encodes to 0x80b50000 in F0_S3_ALU, but 0x00090000 in F3_S3_ALU, 0x12938000 in F1_S0_LdStALU, 0x6c480000 in N0_S3_ALU, and 0x02a50000 in F7_S2_Mul (all disassembled this pass). The clean nibble pattern in §2.4 (compares stepping by 0x100 / 0x008 / a dtype low-nibble) holds only within the F0_S3_ALU slot. This is exactly the flix-encoding §6.2 "two-tier selector" fact: the selector bits are format-local opcode-packing, not a roster-wide bit. Independently, the s/u/width/t distinctions are distinct opcodes with their own iclass (an s op is op_GRP_SAT-tagged, a u op op_GRP_UNSIGN-tagged), never a runtime toggle of one base op — note each row above has a different opc#/iclass#. A reimplementation's encoder indexes the (opcode, slot) pair into opcodedefs[]; it never computes a selector by OR-ing a global "operation" field. [HIGH/OBSERVED]


3. Encoding — lane geometry, the dtype suffix, and the selector structure

3.1 The lane-geometry suffix is the whole datapath story

One 512-bit vec register is re-partitioned by the mnemonic suffix into a SIMD vector. The suffix is the lane count × element width — there is no separate dtype operand:

suffixelementlanes (N=16)C intrinsic typeexample
2nx88-bit64xb_vec2Nx8 / …Uivp_add2nx8
nx1616-bit32xb_vecNx16 / …Uivp_addnx16
n_2x3232-bit16xb_vecN_2x32v / …Uivp_addn_2x32

(N is the symbolic native half-width: 2N=64 8-bit lanes, N=32 16-bit lanes, N/2=16 32-bit lanes — all summing to the same 512 bits.) The C types are read from the device intrinsic header xt_ivp32.h (xb_vecNx16 signed, xb_vecNx16U unsigned, vboolN the compare result); the public IVP_ADDNX16 macro expands to the _TIE_xt_ivp32_IVP_ADDNX16 builtin. [HIGH/OBSERVED]

The signed/unsigned and saturating distinctions ride separate opcodes, not a flag:

  • u infix → unsigned comparison/order (ivp_minunx16, ivp_maxunx16, ivp_ltunx16, ivp_leunx16). Decoded op_GRP_UNSIGN selects zero-extend instead of sign-extend at the lane guard bits. For add/sub the wrap result is bit-identical signed-vs-unsigned, so there is no addu opcode — the C header exposes an …U-typed intrinsic over the same opcode.
  • s infix → signed-saturating (ivp_addsnx16, ivp_subsnx16, ivp_abssnx16, ivp_negsnx16); decoded op_GRP_SAT applies the output clamp. The un-s form wraps (two's-complement truncation by the lane carry-break mask).

3.2 The slot-word operand fields (5-bit vec indices)

Every integer ALU op in a given slot reads its three vec operands from the same slot-word bit positions — a property of the slot, decoded by the Field_fld_<slot>_<hi>_<lo>_Slot_<slot>_get accessors. For F0_S3_ALU, disassembled this pass:

// Field_fld_f0_s3_alu_14_10_get @ 0x314000 :  mov (%rdi),%eax; shl $0x11; shr $0x1b
//   == (slotword << 17) >> 27  == zero-extended bits [14:10]   -> 5-bit vec index
// Field_fld_f0_s3_alu_19_15_get @ 0x3142d0 :  (slotword << 12) >> 27  == bits [19:15]  (5-bit)
// Field_fld_f0_s3_alu_24_20_get @ 0x3140d0 :  (slotword <<  7) >> 27  == bits [24:20]  (5-bit)
// the matching _set thunks do  `and $0x1f; shl $0xN`  — 5-bit deposit, confirming the inverse.

The role→field mapping (ivp_sem_vec_alu_{vt,vr,vs}) sits above these raw fields and can scatter (the vt semantic field reads two disjoint ranges — Field_fld_ivp_sem_vec_alu_vt disassembles to shl $0x1c; shr $0x5; … and $0x18, two ranges OR-ed). A reimplementation deposits the 5-bit index into the raw [14:10]/[19:15]/[24:20] windows and lets the sem_* layer name the role. The compare ops additionally carry an ivp_sem_vec_alu_vbr 4-bit field (the vbool destination index, Field_fld_…_vbr, bits [18:15]-class). [HIGH/OBSERVED]

3.3 The compare selector nibble structure (F0_S3_ALU only)

Disassembled F0_S3_ALU compare selectors share base 0x80870000 and encode three independent sub-fields in the low 12 bits:

selector = 0x80870000
         | dtype_nibble    // bits[2:0]:  2nx8=0, nx16=2, n_2x32=4   (lane width)
         | relation        // eq=0x000, le=0x008, lt=0x100, neq=0x200
         | unsigned_bit     // signed lt 0x100 -> unsigned ltu 0x108 (+0x008 on lt);
                            // signed le 0x008 -> unsigned leu 0x00e (+0x006 on le)

Worked, all [HIGH/OBSERVED] (disassembled): eqnx16=0x80870002, lenx16=0x80870008, leunx16=0x8087000e, ltnx16=0x80870104, ltunx16=0x8087010a, neqnx16=0x80870200, eq2nx8=0x80870000, eqn_2x32=0x80870004. This nibble model is the §2.4 source and is sound within F0_S3_ALU; do not export it across slots (§2 GOTCHA). [HIGH/OBSERVED] on the disassembled cells; the model that interpolates the rest is [MED/INFERRED].


4. Lane value semantics — proven by execution

The module__xdref_* value leaves in libfiss-base.so are the per-element value functions and are callable in-process via ctypes with no license (coverage-tally §5). Each is the lane kernel: it computes one lane and writes the result to *out. Disassembly fixed the ABI; the runs below were executed live this pass.

Binary-op ABI (add, sub, min, max, compare): void leaf(uint64 ctx /*rdi, unused for these*/, uint32 a /*esi*/, uint32 b /*edx*/, uint32 *out /*rcx*/). Unary (abs, neg): drops the b argument. Full-register logic (and/or/xor/not): pointers in/out.

4.1 Add / sub — wrap vs saturate (live)

add_16_16_16 is literally add %esi,%edx; and $0xffff,%edx; mov %edx,(%rcx) — a two's-complement truncating add. adds_16_16_16 sign-extends, adds in 17 bits, detects the overflow sign-bit mismatch, and clamps. Executed:

adds_16_16_16 (saturating)            add_16_16_16 (wrapping)
  a=0x7000 b=0x2000 -> 0x7fff           -> 0x9000   (wrap goes negative; sat clamps to +max)
  a=0x4000 b=0x4000 -> 0x7fff           -> 0x8000
  a=0x8000 b=0x8000 -> 0x8000           -> 0x0000   (both -32768; sat clamps to -min)
  a=0x7fff b=0x0001 -> 0x7fff           -> 0x8000
  a=0x0001 b=0x0001 -> 0x0002           -> 0x0002   (no overflow: identical)

The clamp is asymmetric: positive overflow → 0x7fff (INT16_MAX), negative overflow → 0x8000 (INT16_MIN). A reimplementation that clamps both ways to 0x7fff is wrong on the 0x8000+0x8000 case. [HIGH/OBSERVED by execution]

// ivp_addsnx16 lane (signed-saturating 16-bit add), exact per the executed leaf:
int16_t adds16(int16_t b, int16_t c) {
    int32_t s = (int32_t)b + (int32_t)c;          // 17-bit headroom
    if (s >  32767) return  32767;                 // 0x7fff
    if (s < -32768) return -32768;                 // 0x8000
    return (int16_t)s;
}
// ivp_addnx16 lane (wrapping): return (int16_t)((uint16_t)b + (uint16_t)c);  // bit-truncate
// ivp_subnx16 lane: realized as  b + (~c) + 1  (op_GRP_A1_SUB), then lane-truncated.

4.2 Min / max — the signed-via-unsigned sign-flip (live)

minu_16_16_16 is a plain cmp %edx,%esi; cmova %edx,%esi (unsigned select). min_16_16_16 does not sign-extend; instead it toggles bit 15 of each operand (shr $0xf; sete; shl $0xf; or), turning a signed compare into an unsigned one, then cmovae — the classic bias trick. Executed, the polarity flips exactly with signedness:

a=0x8000(-32768/+32768u) b=0x0001 :  min=0x8000  minu=0x0001   max=0x0001  maxu=0x8000
a=0xffff(-1    /+65535u) b=0x0001 :  min=0xffff  minu=0x0001   max=0x0001  maxu=0xffff
a=0x7fff(+32767/+32767u) b=0x8000 :  min=0x8000  minu=0x7fff   max=0x7fff  maxu=0x8000
// ivp_minnx16 / ivp_minunx16 lane:
int16_t  min_s16 (int16_t  b, int16_t  c){ return (b <= c) ? b : c; }    // signed
uint16_t minu_u16(uint16_t b, uint16_t c){ return (b <= c) ? b : c; }    // unsigned
// (the binary realizes min_s16 by XOR-ing bit15 of each input and doing an unsigned compare —
//  bit-identical result, no sign-extend; reproduce the *semantics*, the trick is an impl detail.)

[HIGH/OBSERVED by execution]

4.3 Integer compare → 2-bit-per-lane vbool predicate (live)

The compare leaves are named <rel>_<predw>_<inw>_<inw> where predw is the predicate width in bits per lane: eq_2_16_16 = a 2-bit predicate from two 16-bit inputs. The leaf produces an all-ones predicate field on true (& 0x3 = 0b11), zero on false. eq_2_16_16 is xor %eax; cmp; sete %al; neg %eax; and $3 — i.e. (b==c) ? 0x3 : 0x0. The signed lt_2_16_16 uses the same bit-15 sign-flip as min, then sbb; and $3; ltu_2_16_16 skips the flip. Executed:

                                eq    lt(signed)  ltu(unsigned)  le(signed)
a=0x0005 b=0x0005           :   0x3      0x0           0x0          0x3
a=0x8000 b=0x0001 (-32768<1):   0x0      0x3           0x0          0x3      // signed lt TRUE, unsigned FALSE
a=0xffff b=0x0001 (-1    <1):   0x0      0x3           0x0          0x3
a=0x0001 b=0xffff (1 <u 65535): 0x0      0x0           0x3          0x0      // unsigned ltu TRUE

The 0x8000 lt 0x0001 row is the decisive edge: signed lt is true (−32768 < 1) but unsigned ltu is false (32768 < 1 is false) — the two opcodes diverge exactly here.

// ivp_eqnx16 / ivp_ltnx16 / ivp_ltunx16 lane → 2-bit vbool field per 16-bit lane:
//   per-lane predicate field (all-ones on true) written into the vbool destination register.
uint2_t eq16 (int16_t b, int16_t c){ return (b == c)                      ? 0b11 : 0b00; }
uint2_t lt16 (int16_t b, int16_t c){ return ((int16_t)b  < (int16_t)c)    ? 0b11 : 0b00; }  // signed
uint2_t ltu16(uint16_t b, uint16_t c){ return ((uint16_t)b < (uint16_t)c) ? 0b11 : 0b00; }  // unsigned
// vbool is 64-bit / register; a 16-bit lane (32 lanes) consumes 2 predicate bits per lane
// (2 * 32 = 64).  An 8-bit lane (2nx8, 64 lanes) consumes 1 bit/lane (eq_1_8_8).  A 32-bit
// lane (n_2x32, 16 lanes) consumes 4 bits/lane (eq_4_32_32).  predw scales so the mask always
// fills the 64-bit vbool register: predw = 64 / num_lanes.   [HIGH/OBSERVED by execution + naming]

That predicate-bits-per-lane = 64 / num_lanes rule (8b→1, 16b→2, 32b→4) is read straight off the leaf names (eq_1_8_8 / eq_2_16_16 / eq_4_32_32) and the & 0x3 / & 0x1 / & 0xf masks in their bodies. The downstream consumption of this mask (predicated select / throttle) is B11's subject. Signed lt/le flip the MSB ({~a[15],a[14:0]} < {~b[15],b[14:0]}) to map two's-complement order onto the native unsigned operator; there is no signed gt/ge opcode — GT/GE = LT/LE with the operands swapped. [HIGH/OBSERVED]

4.4 Bitwise logic — full 512-bit, lane-agnostic (live)

and_512_512_512 is four movdqu/pand pairs over the whole 512-bit register (rsi/rdx in, rcx out); not_512_512 is pcmpeqd(all-ones)+pxor. No element boundary is consulted — the 2nx8 spelling is cosmetic. Executed (first two bytes shown):

a = f00f   b = cc33
and = c003     or = fc3f     xor = 3c3c     not(a) = 0ff0

F0 & CC = C0, 0F & 33 = 03, etc. — byte-exact. ivp_not2nx8 is the only single-source op in the logic group (and gains 4 LdSt placements like abs/neg, §6). [HIGH/OBSERVED by execution]

4.5 Abs / neg — wrapping vs saturating (live)

        abs(wrap)  abss(sat)  neg(wrap)
0x8000:   0x8000     0x7fff     0x8000     // |INT16_MIN| and -INT16_MIN both overflow; wrap=self
0xffff:   0x0001     0x0001     0x0001
0x0005:   0x0005     0x0005     0xfffb
0x7fff:   0x7fff     0x7fff     0x8001

abs(0x8000) wraps to 0x8000 (|−32768|=32768 is unrepresentable → truncates to itself), while abss(0x8000)=0x7fff saturates. Same story for neg. The wrapping forms are pure two's-complement -b / (b<0?-b:b) with 16-bit truncation. [HIGH/OBSERVED by execution]


5. Device-assembler oracle — byte-exact round-trip

The strongest end-to-end check: feed the device-native xtensa-elf-as (XTENSA_SYSTEM=…/ncore2gp/config, XTENSA_CORE=ncore2gp) the mnemonics and disassemble back. All 50 base ops (sampled 20 below) assemble rc=0 and round-trip to the same lowercase mnemonic, each as an 8-byte FLIX bundle (the op lands in one slot, the rest nop). Verbatim bytes (LE):

mnemonicoperands8-byte bundledisasm bundle
IVP_ADDNX16v3,v1,v23251890020c1452f{ nop; nop; nop; ivp_addnx16 v3,v1,v2 }
IVP_ADD2NX8v3,v1,v23251490020c1452fivp_add2nx8 v3,v1,v2
IVP_ADDN_2X32v3,v1,v23251c90020c1452fivp_addn_2x32 v3,v1,v2
IVP_ADDSNX16v3,v1,v232500a0020c1452fivp_addsnx16 v3,v1,v2
IVP_SUBNX16v3,v1,v23252c71020c1452fivp_subnx16 v3,v1,v2
IVP_ABSNX16v3,v13252869800d3452fivp_absnx16 v3,v1
IVP_NEGNX16v3,v13252877800d6452fivp_negnx16 v3,v1
IVP_MINNX16v3,v1,v23252477020c1452fivp_minnx16 v3,v1,v2
IVP_MINUNX16v3,v1,v2325247c020c1452fivp_minunx16 v3,v1,v2
IVP_MAXNX16v3,v1,v232518b0020c1452fivp_maxnx16 v3,v1,v2
IVP_MAXUNX16v3,v1,v23252472020c1452fivp_maxunx16 v3,v1,v2
IVP_AND2NX8v3,v1,v232500b0020c1452fivp_and2nx8 v3,v1,v2
IVP_OR2NX8v3,v1,v23252c70020c1452fivp_or2nx8 v3,v1,v2
IVP_XOR2NX8v3,v1,v23252c63020c1452fivp_xor2nx8 v3,v1,v2
IVP_NOT2NX8v3,v13252877800db452fivp_not2nx8 v3,v1
IVP_EQNX16vb0,v1,v2029c58e49c04022f{ ivp_eqnx16 vb0,v1,v2; nop }
IVP_LTNX16vb0,v1,v2029c5a469c04022fivp_ltnx16 vb0,v1,v2
IVP_LTUNX16vb0,v1,v2029c5a849c04022fivp_ltunx16 vb0,v1,v2
IVP_LENX16vb0,v1,v2029c5a029c04022fivp_lenx16 vb0,v1,v2
IVP_NEQNX16vb0,v1,v2029c5ac29c04022fivp_neqnx16 vb0,v1,v2

Two structural facts the oracle pins:

  • Compares write vb (the vbool short-name), arithmetic/logic write v. The compares assemble into a different bundle shape (029c…2f, op in the first slot of a 2-op bundle) than the arithmetic ops (32…2f, op in the last slot of a 4-position bundle) — consistent with the compares riding the Mul/compare slot class (§6) and the arithmetic riding the trailing ALU slot. The 0x2f trailer (op0 nibble 0xF, even) is the N0/narrow framing (flix-encoding §4): {nop;nop;nop;<alu>} is exactly the N0 = LdSt + None + None + ALU profile.
  • b0/br0 are rejected — the boolean operand spells vb0. This is the vbool file's shortname (vb) from register-files §3, not the scalar BR file.

The device byte order is the assembler's packed-bundle layout and is not byte-identical to the libisa-core slot-normalized selector imm of §2 (which is the slot-word template, a different representation); they agree structurally — the placement exists, the mnemonic and register file round-trip — which is the property the oracle certifies. [HIGH/OBSERVED]


6. Batch coverage tally — 50 mnemonics / 894 placements

Re-counted this pass with nm libisa-core.so | rg -c 'Opcode_ivp_<glob>_Slot_…_encode' (never the decompile — coverage-tally §0 GOTCHA). Every one of the 50 grounds to ≥ 1 placement; none ungrounded.

sub-familymnemonicsplacementsplacements/opslot reach
add (wrap+sat)46015ALU class (10) + Mul (4) + F1_S0_LdStALU
sub (wrap+sat)46015same as add
abs (wrap+sat)47619add-set + 4 LdSt slots (1-source ⇒ rides LdSt)
neg (wrap+sat)47619same as abs
min (signed+unsigned)69015ALU+Mul class
max (signed+unsigned)69015ALU+Mul class
eq / neq612621ALU+Mul + Mul on every wide fmt + N1_S2_Mul + N2_S0_LdSt
lt (signed+unsigned)612621same as eq
le (signed+unsigned)612621same as eq
logic and/or/xor34515ALU+Mul class
logic not11919add-set + 4 LdSt (1-source)
TOTAL50894

The placements/op pattern is itself a recovered fact and a useful decode cross-check:

  • 15 — the canonical two-source ALU op (add/sub/min/max/and/or/xor): the 10 ALU-class slots + the 4 Mul-class slots the dual-issue wide formats fold ALU into + F1's fused S0_LdStALU.
  • 19 — single-source ops (abs/neg/not) gain the 4 *_s0_ldst slots (F0/F3/F6/F7): a one-operand op fits the LdSt slot's narrower operand budget where a two-source op cannot.
  • 21 — the compares ride the Mul/compare slot class on every wide format plus the narrow N1_S2_Mul and N2_S0_LdSt — the predicate-producing path has the widest slot reach because the compare unit is shared with the Mul pipe's flag-producing stage.

These 894 are a strict subset of the 12 569 certified-perfect placements; the float siblings (B02), the 14 …t predicated forms (each −2/−2 placements vs its base — e.g. addnx16t=13 vs addnx16=15, negnx16t=17 vs negnx16=19, since the t forms drop the S4-ALU slots; B03), and the …b predicate-file logic (B11) are counted in their batches, never here — no double-count. [HIGH/OBSERVED]

NOTE — adjacency, deferred by design. Three families share this datapath but are owned elsewhere: ivp_abssub{,u}{nx16,2nx8} (abs-of-difference — |b−c|, executed: abssub_16_16_16 negates the difference if the borrow bit is set), ivp_avg{,r,u}{nx16,2nx8} (averaging — avg=(b+c)>>1, avgr=(b+c+1)>>1 rounding, avgu unsigned, all executed live), and ivp_minormax2nx8 (combined min&max select). They are cited so a reader who greps the ivp_add/ivp_min neighbourhood knows where they went, not to claim them. → B03 / B24. [HIGH/OBSERVED] on the adjacency.


7. Adversarial self-verification — the five strongest claims

Each re-challenged against the binary this pass; failures fixed.

  1. "50 base integer-ALU mnemonics own 894 placements, none ungrounded." Re-run: nm libisa-core.so | rg -c 'Opcode_ivp_(add2nx8|…|not2nx8)_Slot_…_encode' over the explicit 50-op glob = 894; per-op loop shows 0 with zero placements. The per-family breakdown (§6) sums 60+60+76+76+90+90+126+126+126+45+19 = 894. ✓ [HIGH/OBSERVED]
  2. "Saturating add clamps asymmetrically (+→0x7fff, −→0x8000)." Re-challenged by executing adds_16_16_16(0x8000,0x8000) live → 0x8000 (not 0x7fff); (0x7000,0x2000)0x7fff. The asymmetry is real and would be a bug if both clamped to 0x7fff. ✓ [HIGH/OBSERVED by execution]
  3. "Signed and unsigned compare diverge; lt is signed, ltu unsigned." Executed lt_2_16_16(0x8000,0x0001)=0x3 but ltu_2_16_16(0x8000,0x0001)=0x0 — opposite results on the same bits. Initially I assumed a single lt with a sign flag; the binary shows two distinct opcodes with two distinct value leaves and two distinct iclass# (ltnx16=345 vs ltunx16=349). Fixed. ✓ [HIGH/OBSERVED by execution]
  4. "The opcode-selector is per-(opcode×slot), not a global add/sub bit." Re-challenged by disassembling ivp_addnx16 across 8 slots: 0x80b50000 (F0_S3) ≠ 0x00090000 (F3_S3) ≠ 0x12938000 (F1_S0_LdStALU) ≠ 0x6c480000 (N0_S3) ≠ 0x02a50000 (F7_S2_Mul). The clean nibble pattern is F0_S3_ALU-local only. The §2 imm column is correctly scoped to one slot and flagged; and the s/u/t/width variants each carry their own opc#/iclass# (§2). ✓ [HIGH/OBSERVED]
  5. "Compares write vbool, not vec, with 64/num_lanes predicate bits per lane." Re-checked the leaf names (eq_1_8_8/eq_2_16_16/eq_4_32_32) and the device oracle (ivp_eqnx16 vb0,… assembles, v0 as dest is the arithmetic form, b0 is rejected). The & 0x3 mask in eq_2_16_16 and & 0x1 in eq_1_8_8 confirm 2-bit / 1-bit fields; 2×32 lanes = 4×16 lanes = 1×64 = 64-bit vbool. ✓ [HIGH/OBSERVED]

Ungrounded / flagged items (honest residue): (a) the -marked compare imms in §2.4 are interpolated from the disassembled eq/lt/le nibble model ([MED/INFERRED] imm; the opc#, iclass#, and relation are [HIGH/OBSERVED]); (b) the role-field scatter of ivp_sem_vec_alu_vt/vbr is OBSERVED in disasm but its exact bit composition is [MED/INFERRED] beyond the clean [14:10]/[19:15]/[24:20] windows; (c) the 1-cycle ALU latency (use@10 → def@11) is [HIGH/OBSERVED] from opcodes[] use/def stages, but the full cycle-accurate interlock (the cas-core model — _IVP_ADDNX16_inst_stage* symbols resolve) is license-walled for retirement timing. None is a missing decode or a missing value semantics.


8. Cross-references


Provenance: encoding (Opcode_*/Field_* thunks, opc#/iclass#), placement counts, and operand bitfields are [HIGH/OBSERVED] — disassembled / nm-counted in-checkout from libisa-core.so (ncore2gp/config, not stripped). Lane value semantics are [HIGH/OBSERVED by execution] — the module__xdref_* leaves in libfiss-base.so were called live via ctypes (license-free value lane). The byte round-trip is [HIGH/OBSERVED] from the device-native xtensa-elf-as/objdump (XTENSA_CORE=ncore2gp); C intrinsic types from the shipped xt_ivp32.h. ALU use@10/def@11 latency is [HIGH/OBSERVED] from opcodes[] stages; cycle-accurate retirement is license-walled. All facts read as derived from shipped-artifact static analysis and in-process execution of license-free leaves (lawful interoperability RE).