THE bug that silently culled all non-VSTRIP geometry in every capture. In DIM
the chip executes FP+core instruction PAIRS, and a delayed branch's slot is the
whole NEXT PAIR (2 words). The serial interpreter executed one delay word,
silently skipping the pair's core half -- e.g. the corner transform's final
fst.d z,w (0xf04213a4, in the delay pair of its bri): the last bbox corner kept
stale z/w, the in-place buffer decayed across passes (stale w=0 loses the
translation), every object classified fully-outside, and the classify->clip-draw
path emitted nothing while the VSTRIP path (different codegen) worked.
Fix: DIM state machine (_dim/_dim_on/_dim_exit/_dim_half) per i860 PRM ch.8 --
entry d.fpop -> one more serial instr -> DIM; exit pair-with-D=0 -> one more
pair -> serial; pair halves tracked positionally (reset at control transfers);
fnop/d.fnop (0xb0000000/0xb0000200, the shrd-encoded FP-slot filler whose 0x200
bit IS the D bit) recognized as FP halves -- missing it misaligned the halves,
missed the fnop(D=0) exit markers, and leaked DIM into serial code. Delay-slot
width decided from DIM state at branch FETCH time.
Acceptance: (1) corner transform now writes all 8 corners, w=1.0, and the
model-view matrix gains its real translation row (was zeros -- the concat had
the same bug); (2) cap7 regression clean, now 90 verts/frame vs 45 (a second
instance survives the no-longer-false cull); (3) klngvid runs past its draws
cleanly (previously wandered into the data segment).
Debug chain: flowtrace/cliptrace/planecheck/xformcheck2/wandertrap.py
(scratchpad) -- plane test hand-verified correct 10/10, inputs proven stale,
final store traced to the skipped delay pair.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- pfgt/pfle/pfeq are always pipelined: fdest <- A-pipe retire, push undefined;
bit7 selects pfle (inverted CC sense) -- was misread as result precision.
316 pipelined compares in the firmware previously desynced the A-pipe.
- h_igcwait now models full consumption: done-status nonzero AND write-index
reset (+0x7f8=0), otherwise queue pages saturate at 0x7f0 across frames and
enqueueing silently stops (observed: even the per-frame marker item stopped
binning by cmd ~4300).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The frame-1 extent bug traced to four `pftrunc.sd fN,f0,fX` ops (P=1)
converting the region bounds through the A-pipe -- our fix/ftrunc was
scalar-only (MAME's core never implemented the pipelined form: "pipelined
not functional yet"). The drains then retired neighboring values (the
homogeneous w=1.0f as raw bits -> y1=0x3F800000>>7=8323072 -> ~8.3M phantom
tile rows = the 49K uniform bins / 4096 same-page descriptors).
Pipe stages now carry (value, rp, raw): pipelined ftrunc/fix push the
truncated integer BITS (64-bit pair for .sd results), and _retire() writes
raw entries back verbatim instead of re-encoding floats. Extent probe after
fix: x0=y0=x1=y1=0 (sane empty bound), single visit instead of a hot loop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
unhooked; draw_scene #1 runs with 41K IGC board accesses
The functional pipe model is replaced with exact i860 semantics, validated
against MAME's i860 core (src_opers[] and insn_dualop/insn_fadd_sub verbatim):
- Precision bits: SOURCE = bit8, RESULT = bit7 (swapped from our old reading;
.ds is invalid except famov). For DUAL ops: sp = multiplier source prec,
rp = adder source prec AND all results.
- Dual ops (sub 0x00-0x1f): bit10 selects PFAM (fdest <- A-pipe retire) vs
PFMAM (fdest <- M-pipe retire) -- NOT "pipelined". FLAGM operands (the
A-pipe entries in the DPC table) read the M-pipe in the PFMAM family.
T-loads on DPC 2,3,6,7,8,0xb,0xc; K-loads on DPC 1,3,5,7 (from fsrc1 at
mul precision; T from the M-pipe last stage).
- fdest BYPASS: pipelined ops whose source register equals fdest read the
retiring pipe value instead of the stale register (dual mul: op2 only;
dual add + scalar pipelined fadd/fmul: both operands).
- Pipe stages carry (value, result-precision); push rounds to single when
rp=0 (exact magic-constant float->int bit games); retire encodes with the
pusher's precision. Adder = 3 stages; multiplier = 2 (double) / 3 (single);
pfld = 3-stage load pipe; graphics fiadd/fisub = 1-stage pipe, 64-bit .dd.
ACCEPTANCE: (1) the firmware's own Newton-Raphson integer divide now computes
16/16 = 1 through the pipes -- the intdiv hook is retired (authentic
execution); (2) draw_scene #1 from the snapshot executes real render phases:
coefficient fld.d bursts, 0x800-byte queue-page initialization, tile enqueue,
and the first region flush -- 40,959 IGC/board MMIO accesses (previously 0).
Frame still running at the profiling time budget (~240M steps in).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
processes the full wire boot incl. the downloaded PAZ/sfx module
ISA fixes, all derived from the toolchain's own .S<->.O pairs (AS860.ZIP:
OPTFLOAT/TRISTRIP/ZBUF32, plus DNC.O) and the firmware's linked COFF header:
- DATA_BASE = 0x1000 DEFINITIVE: VREND.MNG carries its original COFF header in
the file tail (.data vaddr 0x1000, .bss 0x1f940, entry 0xf0400000).
- Integer loads: even opcodes are register-indexed (EA = src2 + src1); op 4/5
size flag = instr bit0 (0 = ld.s 16-bit, 1 = ld.l 32-bit); ld.b/ld.s
sign-extend.
- Integer stores: st.s/st.l selected by offset bit0, same split-offset rule.
- FP loads/stores: FP register lives in the DEST field for both fld and fst
(fst does NOT use the integer split-store encoding); flag bits: bit0 =
auto-increment (base <- EA), bit1 1=.l/0=.d, bit2 = .q; .d/.q span register
pairs/quads. ~450 fld.d + ~300 fst.d were previously read/written 32-bit.
- bla (op 0x2d, was misdecoded as shrd): branch-on-LCC-and-add with the
sign-dependent LCC rule (src1<0 -> signed sum >= 0), so spent countdown
loops terminate. 335 bla instructions in the firmware.
- CORE ESCAPE (op 0x13): sub-op 1 = lock, 2 = calli, 7 = unlock. Previously
everything decoded as calli, so every spinlock acquire jumped to address 0 --
this was the phantom "exit stub" behind most earlier derails.
- f2b: IEEE overflow -> +/-inf instead of raising.
emu_main.py (new): runs the firmware's OWN main() (0xf0403f10) and feeds real
wire captures through a hooked dN_receive, so init/do_init/dispatch/handlers
all execute authentically. Provides the transputer-monitor environment
(processor id, DRAM region descriptors in the shared control block, sbrk/
shared-block seed slots) and hooks only the link primitives (bla busy-wait,
dN_mynode/dN_nodes/dN_receive/dN_send, putchar path, spinlocks, page allocator
+ virt->phys translator pending Tier-2 VRENDMON).
KEY DISCOVERY: the capture's args860/code860/data860/bss860 preamble is the
host DOWNLOADING an additional i860 module (the PAZ/sfx renderer layer, banner
"i860 50MHz") which installs the runtime handler tables and system objects.
Feeding it through the firmware's own handlers, the module loads and makes the
first IGC board-register writes. State: 834+ wire commands processed (module
download + init + create); first draw_scene sits at command 1568.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Env-gated hooks to continue the create()/jump-table investigation without
changing default behavior:
- EMU_DATA_BASE (default 0): overrides I860.DATA_BASE for the .data link base.
- EMU_INDEXED=1 (default off): makes EVEN integer load opcodes register-indexed
(EA = base + index), per the ground-truth `ld.l r30(r31),r31` in VR_REMOT.S.
Sweep finding (EMU_INDEXED=1): create() RETURNS at DATA_BASE=0xff0 making real
subroutine calls, confirming register-indexed loads + a ~0x1000 data base are
correct. Residual: do_init derails to (DATA_BASE+0x30) for base >= 0xfe0 while
create needs >= ~0xfe0 -- a single flat base can't satisfy both yet, pointing
to a segment/relocation nuance (or a separate do_init jump-table derail).
Details + repro in the tier-0 memory. Default behavior unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Worked the full-render marathon by tracing faults in the real VREND.MNG
firmware and matching them to the ground-truth AS860 assembly (VR_REMOT.S)
and C source (VR_REMOT.C). Six interpreter bugs found and fixed:
1. subs/subu direction: computed src2-src1; must be src1-src2 with
CC=(src1<src2). Corrupted every subtraction, compare and bounds-check.
2. logical CC: all i860 logicals (and/andnot/or/xor + .h) set CC=(result==0).
Had wrongly limited it to the AND family, so the `xor 0x0,rN,r0; bnc`
zero-test idiom spun forever.
3. STORE encoding (the big one): i860 stores encode the SOURCE register in
bits 15:11 (src1), not the dest field, and SPLIT the 16-bit offset across
bits 20:16 (high) + bits 10:0 (low). The old decode saved the wrong
register to the wrong offset, so function prologues never stored r1 and
every `bri r1` return jumped to 0.
4. ld.b/st.b: op 0x00/0x01 = ld.b, op 0x03 = st.b (byte). Proven by byte-scan
loops (r5 advanced by 1) and a save/restore pair at 0xf042b418 -> b430.
5. Mem page-span: r16/r32/w16/w32 crashed on 64KB page boundaries; now fall
back to byte access across the boundary.
6. fmlow.dd (FP subop 0x21): the i860 has no integer multiply, so ints are
ixfr'd into FP regs, multiplied via fmlow.dd, and fxfr'd back. Implemented
as a 32x32 -> 64 multiply across the destination register pair.
emu_replay.py now runs the source-accurate init sequence: velocirender_init
on the init(0) command, then do_init before the first real command. Result:
boot idles at 0xf0400590; velocirender_init returns; do_init runs to
completion (~9200 steps of real allocator / name-table / scene-root setup).
Remaining blocker (documented in the tier-0 memory): create()'s switch needs
register-indexed integer loads (VR_REMOT.S: `ld.l r30(r31),r31`) together
with the correct .data link base (~0x1000, not 0) -- the two errors currently
cancel for the immediate paths but break the indexed jump-table dispatch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
emu860.py -- an i860 interpreter (reuses the dis860 decoder) that executes the
real Division firmware. This is the foundation of the preservation-grade
emulator (run the board's own code, vs the GL bridge which interprets the wire).
- Boots VREND.MNG cleanly: i860 init (psr/dirbase/fsr), enables the MMU, then
idle-spins at 0xf0400590 waiting for a transputer interrupt (init complete).
Board-config regs modelled (0xfffff720=2, 0xfffff70c=1) via map_control().
- Call harness (cpu.call): invoke firmware functions directly, bypassing the
(un-emulated) transputer that normally drives the CCB.
- Correct memory map: .data/.bss linked LOW (DATA_BASE=0), so globals resolve.
- FP unit with double precision (register pairs): fadd/fsub/fmul/famov/frcp/
frsqr/fix/ftrunc/fgt/feq/fxfr/fiadd/fisub.
- Delay slots, control regs, sparse MMIO memory with trap/logging, tail-trace
and stopat debug aids.
draw_scene now runs real code (incl. a double int->float conversion) until it
needs accumulated scene state -- next step is replaying a captured wire command
sequence through the command dispatcher (jump table @0x47cb8) so the scene
builds and draw_scene emits the IGC coefficient stream (Tier-1 handoff).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>