Commit Graph
5 Commits
Author SHA1 Message Date
Joseph MattielloandClaude Opus 5.5 4bdc77972a docs(timing): S2 write-up -- report the CF canary failure, rerun disclosure, provenance tags
Records the Checkered Flag canary as a failed sanity check (knife edge kept
as a hypothesis), discloses the one re-run of the injection's low level,
scopes VJ_S2_INJECT to GPU waits, tags the ARB.NET excerpts as relayed by
a summarizing fetch (not verbatim, not simulated), and labels the 68K
overshoot mechanism as read from code.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 01:26:17 -04:00
Joseph MattielloandClaude Opus 5.5 7b5fa04124 docs(timing): S2 result -- traffic census, read-derived grant-latency function, NO-GO
Doom's critical (GPU) chain gains 0.31/0.46/0.68 field per flip from the
latency function x measured traffic (every bound < 1.0); injected into the
emulator it moves the present rate +0.01..+0.05 (stock) / +0.16..+0.46
(all timing options).  The function is assembled from JTRM rev 8 and the
ARB.NET state machine (not simulated: no Verilator rig or jag_sim checkout
on any host) and is written up as gpu-timing-spec.md section 15.  Also
corrects plan premises H-A (94% of GPU external traffic is spin polls),
H-B (68K work is 0.31 field/flip) and H-C (DSP traffic is large but its
chain has 1.25 fields of slack), and records the 6% 68K slice-overshoot.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 01:10:05 -04:00
Joseph MattielloandClaude Fable 5 ba4dc79453 feat(timing): Verilator-pinned GPU constants — write-back conflict, L=7+D
Simulated the jag_sim netlist GPU under Verilator to close the two open
items in docs/gpu-timing-spec.md section 12, and found a third rule the
netlist reading had missed entirely.

- External LOAD latency pinned: L = 7 + D sysclks (D = memory
  controller occupancy), slope exactly 1 across D=1..8 and identical
  for use-distance 0..5.  Page hit => 9.  The model's fixed overhead is
  retuned to match (EXT_RETURN 2 -> 3: issue 2 + grant 2 + return 3).
- Fetch starvation resolved as ~zero for load-dense code (+0.016
  cyc/instr): the real rule is that a run of g consecutive local loads
  costs g-1 extra clocks, which the existing local-engine model (one op
  per 2 ticks) already reproduces.  No code change.
- NEW: write-back port conflict.  Every register-writing instruction in
  a DISJOINT-register stream retires at 2.00 ticks (NOP streams: 1.00;
  a following NOP does not conceal it).  This is the JTRM 'Register
  Write-Back' dual-port rule -- concealment needs the next instruction
  to read the write-back target or read fewer than two registers.  Real
  GPU code therefore runs near 0.5 IPC, not the 1/tick the spec
  assumed.  Modeled: a two-read instruction whose reads miss the
  previous instruction's destination now stalls 1.

Validation: the independent-ALU microbench moves 22 -> 37 cyc/iter
(1.85 ticks/instr vs the netlist's measured 2.00 -- the remainder is
the loop's non-register-writing ops, as expected).  Dependency-heavy
code is largely unaffected because the RAW interlock already charged
those pairs, which is why Doom's GPU load barely moves (66% -> 69% of a
field) while the synthetic independent stream nearly doubles.  Hover
Strike 1.33 -> 1.43 fields/flip.  Full make test green; default-off
unchanged.

Verilator method and raw counts: docs/gpu-timing-verilator-results.md.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 15:57:37 -04:00
Joseph MattielloandClaude Fable 5 92a27a86b7 fix(timing): keep GPU pipeline costs in the core cycle domain
Copilot review on #407.

The model charged pipeline interlocks, MOVEI/indexed extras, jump
refill and delay-slot cycles through gpu_bus_stall -- which GPUExec
treats as WALL time and rescales when risc_clock_scale != 1x.  That is
right for DRAM latency (silicon does not speed up when the RISC is
overclocked) and backwards for anything internal to the RISC, which
scales WITH the processor: one interlock cycle became two at 2x.

Split the accumulators per the bus_arbiter.h cycle-domain contract:
gpu_bus_stall keeps external memory latency (rescaled), the new
gpu_pipe_core_stall carries RISC-internal cost (deducted unscaled).
Pending results now record their domain (gpu_reg_ready_ext), so a
scoreboard wait on an external load bills as wall time while a wait on
a local load or divide bills as core time -- local RAM is clocked at
the GPU clock (JTRM Memory Interface).

Verified: microbenches byte-identical at 1x (ALU 22, load-use 19,
stores 35 cyc/iter); the interlock-dominated ALU bench now scales
1.998x from 1x to 2x RISC clock, as a core-time-bound kernel must.
Full make test green.

Also from the same review:
- gpu_pipe_stall_total / gpu_pipe_ext_total documented as monotonic
  since GPUReset and reset there, NOT in GPUPipeTimingReset(): that
  runs on every interrupt bank switch, so clearing them there would
  blank the counters many times a frame and leave probes differencing
  against a moving zero.
- present_rate_probe header rewritten to describe what it actually
  does (frame-hash sampling, not OLP watching), and it now reports
  fields/flip only -- the hard-coded 5.006 s/window was wrong for PAL
  and approximate for NTSC.
- docs/gpu-timing-spec.md: replaced the local scratch path with the
  upstream jag_sim repository reference.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 15:27:40 -04:00
Joseph MattielloandClaude Fable 5 c62b97c675 feat(timing): netlist-grounded GPU pipeline/gateway cost model + synthetic timing rig
New core option virtualjaguar_gpu_pipeline_timing (EXPERIMENTAL,
default off; off = byte-identical).  Implements the instruction-level
GPU costs the emulator has always skipped, per the Flare netlist
sources (docs/gpu-timing-spec.md):

- register score-board: reading a pending load/divide result stalls to
  its completion; one-deep ALU result-use interlock (+1); flags
  interlock (+1 for CMP;JRcc / ADDC/SUBC after a flag-setter)
- single external gateway, split-transaction, two pending slots:
  back-to-back external loads/stores serialize at bus speed; a store
  drains pending loads; local loads have 3-tick latency on a
  one-op-per-2-ticks engine
- DIV 16 background ticks (use blocks); MOVEI 3 ticks; indexed
  load +2 / store +1; taken JUMP +2 / JR +3, and the inlined
  delay-slot instruction is now charged at all under the option
  (it was free)

Validated on the new synthetic acid ROMs (cyc/iter, off -> on):
external load-use 5 -> 19, external stores 7 -> 35, independent ALU
19 -> 22 (correctly ~unchanged), render-bound repro 6.5 -> 2.1
completions/field.  Full make test green; default-off untouched.

Doom findings (probes included: gpu_pipe_probe, present_rate_probe,
gpu_bench_read, counter_rate_scan): VI delivery and ticcount are
exactly 1/field (correct); the demo presents every 2.00 fields vs
hardware's ~4 -- the pass is gate-floored in our core because even the
charged render still fits inside 2 fields.  The model charges 65% of a
field per frame on Doom (16-21k external accesses); closing the last
~2x needs the two sim-pinned terms from the spec: fetch-starvation
bubbles and OP-vs-GPU bus grant contention during active display
(Verilator recipe in spec section 12).  Also adds INT1 per-source
perf counters (BENCH_PROFILE) used to falsify the VI-double-fire
theory.

Issue #401, continues #313.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 03:13:01 -04:00