cpu: calibrate 68010 MOVES/MOVE-from-CCR/RTE/dispatch cycles and fix STOP semantics#143
Merged
Merged
Conversation
The 68000 interrupt microcode spends 6 idle clocks before the PC-low frame write, runs the 4-clock interrupt-acknowledge bus cycle next, and spends 4 more internal clocks before the SR and PC-high writes (Moira execInterrupt, same sequence as yacht). The 68010 spends 12 leading idle clocks with the IACK at their end and writes its format-0 frame in the order PC low, SR, PC high, vector word, for 46 clocks total. The core previously ran the IACK and all frame writes back to back and paid every idle clock after the handler-entry prefetch, so the handler's first instruction started ~14 clocks early on every dispatch - visible as a uniform early shift of all interrupt-handler activity in the vAmigaTS CPU/IPL family (446 cases) against the vAmiga reference. The idle periods are now billed in place through the pending-sync mechanism, landing each frame write, the IACK, and the vector fetch at their hardware bus-time offsets; the dispatch totals stay 44 (68000) and 46 (68010) clocks.
The 68000/68010 sample their IPL pins at ONE microcode-determined point per instruction and the boundary interrupt decision consumes that sample (Moira's POLL placement; pasti 68kPrefetch). The host samples at the start of every bus access and by default consumes the LAST access's sample, which is correct for the majority class whose poll rides the final prefetch, but wrong wherever the poll point is not the last access: under bus contention those instructions recognized (or missed) an interrupt one boundary off. Mechanism: AddressBus::ipl_hold_sample marks the instruction's poll point right after the access that carries it; the host pins that sample until the boundary decision consumes it. Exception dispatch releases a pending hold (ipl_release_sample) because the vector jump's handler-entry refill is a fresh poll point. 68000 placements (68010 where the microcode is shared): - RMW instructions poll during the final prefetch that precedes the writeback: ANDI/ORI/EORI, ADDQ/SUBQ, CLR/NEG/NEGX/NOT, BCHG/BSET/ BCLR, NBCD, Scc, AND/OR/EOR/ADD/SUB Dn,<ea>, memory shifts, and MOVE to -(An). ADDI/SUBI and MOVE from SR keep polling during the writeback itself (their microcode differs). - CMPM, ABCD/SBCD and byte/word ADDX/SUBX -(Ay),-(Ax) poll at the start of the destination read, between the two operand reads (CMPM 68000 only; the 68010 polls at the final prefetch). ADDX/SUBX.L poll at the low-word write of the interleaved writeback. - MOVEP memory-to-register polls at the final byte read. - MOVE to (An)+ polls during the destination write for every source and size; MOVE.L with a register or immediate source to (An), d16(An) or d8(An,Xn) polls before the write's low word (the write is split into its two word cycles to keep the per-word sample). - LINK polls right before the An push; UNLK before the low word of its stack read; PEA polls on the prefetch that precedes the push, except absolute modes which push FIRST and prefetch last (order fix). - TAS runs read, 2 internal clocks, write, then the final prefetch (the indivisible RMW cycle precedes the poll-carrying prefetch), instead of borrowing the ordinary RMW prefetch-before-write order. - DIVU/DIVS issue the final prefetch BEFORE the division's internal clocks (the poll rides it, so an interrupt rising during the division is taken one instruction later); division by zero spends 8 internal clocks before the exception frame. - The taken 68000 TRAPV performs its dummy program-space read before the exception frame; the trace exception spends 4 internal clocks before its first stack write. The vAmigaTS CPU/IPL family (446 cases, each raising interrupts on a 2-cck grid across every instruction boundary) drops with this and the dispatch-idle fix combined; per-case scores also carry a chaotic mainloop-phase component, so family sums are the meaningful metric. The TAS arbitration test gains the read-to-write internal cck.
MOVES billed a flat 4 clocks: the extension word, the mode-dependent internal period, and the data cycle all ran unbilled, so every MOVES finished 6-8 clocks early and shifted all later bus activity (the vAmigaTS CPU/IPL/MOVES cases, whose whole screen is a CPU-phase pattern, sat at 43-48% divergence). The 68010 spends 6 internal clocks between the EA calculation and the data cycle for (An), -(An) and d8(An,Xn), 8 for (An)+, and 4 for d16(An) and the absolute modes (Moira execMoves), then performs the access and the final prefetch, which carries the IPL poll. 68010 RTE re-read the format/vector word it had already probed, spending an extra bus cycle and returning 20 clocks: the hardware reads format word, SR and PC (four reads), discards the frame by adjusting SP, and refills - 24 clocks.
…ory write) 68010 MOVE from CCR calibrated against the vAmigaTS CPU/Timing2/MOVECCR measurements (Moira execMoveCcrRg/execMoveCcrEa): a register destination costs only the final prefetch (4 clocks, was 6); a memory destination spends per-mode internal clocks ((An) 2, (An)+ 4, -(An) 2), computes the EA, prefetches, and only THEN writes ((An) = 10 clocks total, was 8 with the write before the prefetch). ea_calc_cycles() is a shared prefetch-model EA-calculation cost table (extension-word fetches plus the internal clocks resolve_ea bills) used by this path and the MOVES billed total.
The billed MOVES total missed the 2 internal clocks the predecrement and brief-extension-word address calculations spend inside resolve_ea (Moira computeEA SYNC(2); the placement was already right, only the returned total under-counted). Route the EA cost through ea_calc_cycles so the -(An) byte/word total is 20 clocks and d8(An,Xn) is 24, matching the vAmigaTS CPU/Timing2/MOVES measurements.
…oundary STOP semantics on the 68000-040, pinned by the real-chip-validated SST m68000 single-step fixtures and the vAmigaTS CPU/Timing/STOP family: - The SR operand is loaded VERBATIM: a single-stepped STOP observes S and T exactly as written (STOP.json.bin passes locally against M68K_SST_FIXTURES). - An S-clear SR stops only momentarily: the stopped state's supervisor check - run at every instruction boundary on all three step paths - raises a privilege violation 4 internal clocks later, stacking the STOP instruction itself so the handler's RTE re-executes it (4 + 38 clocks to the handler on a 68000). Previously the core simply stopped with a user-mode SR and never recovered. - A trace pending when STOP executes (T set in the SR the instruction started with) has priority over both the stopped state and its supervisor check: the trace exception recovers from the stop. A T bit loaded BY the STOP does not fire while stopped. The 68060 keeps its distinct immediate-privilege model (SR unchanged, PC past the operand). stop_and_68010_timing_tests.rs pins these plus the 68010 cycle totals this branch and its base calibrate (MOVES both directions per EA mode, MOVE from CCR, format-0 RTE, the 44/46-clock interrupt dispatch).
The stopped-CPU idle fast-forward bounded its horizon to the Copper's WAIT position, but treated a running (non-waiting) Copper as event-free: a Copper MOVE to INTREQ right after a WAIT completed was overshot by whole fast-forward slices, waking the CPU up to 72 lines late (vAmigaTS stop1 never visibly woke). next_copper_wakeup_cck now returns a two-colour-clock horizon while the Copper is fetching instructions (a halted Copper still imposes none), and a pure idle single step advances one colour clock instead of a whole instruction's worth of time: a STOPped 68000 samples its IPL pins every bus-cycle period, so that is the hardware wake-up quantum.
9d8435a to
18ec267
Compare
This was referenced Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #144 (
fix/ipl-poll-placement) - merge that first. This branchis rebased onto it; the diff shown here against
mainincludes #144'scommits until it merges.
Calibrates the remaining self-contained CPU instruction-timing buckets from
the vAmigaTS suite (CPU/Timing + CPU/Timing2) against measured 68000/68010
hardware behaviour, and fixes the STOP state machine. #144 already landed
the shared foundations (68010 interrupt dispatch 46 clocks with the idles
billed in place, 68010 MOVES per-EA internal clocks, format-0 RTE read
count, IPL poll placement); this branch adds what it does not cover:
Hardware fixes (on top of #144)
destinations spend per-mode internal clocks and prefetch BEFORE the
write ((An) = 10 total). vAmigaTS CPU/Timing2/MOVECCR, Moira
execMoveCcrRg/execMoveCcrEa.
d8(An,Xn) address calculations spend inside resolve_ea (Moira computeEA
SYNC(2)); -(An) byte/word = 20 clocks, d8(An,Xn) = 24. Placement was
already right in m68k: align per-instruction IPL poll points and interrupt dispatch idle placement with the 68000 microcode #144; only the returned total under-counted.
observes S and T exactly as written - the real-chip-validated SST
m68000
STOP.json.binpins this and passes locally. An S-clear SRstops only momentarily: the stopped state's supervisor check (run at
every instruction boundary on all three step paths) raises a privilege
violation 4 internal clocks later, stacking the STOP instruction itself
so the handler's RTE re-executes it (4 + 38 clocks to the handler).
Previously the core stopped with a user-mode SR and never recovered.
with): the trace exception has priority over the stopped state and its
supervisor check, and recovers from the stop; a T bit loaded BY the
STOP does not fire while stopped. The 68060 keeps its distinct
immediate-privilege model.
(non-waiting) Copper as event-free, so a Copper MOVE to INTREQ right
after a WAIT was overshot by whole slices - the CPU woke up to 72 lines
late (stop1 never visibly woke). The fast-forward horizon is now two
colour clocks while the Copper is streaming, and a pure idle single
step advances one colour clock (a STOPped 68000 samples its IPL pins
every bus-cycle period).
vAmigaTS results (summed divergence, cases)
stop1/stop2/stop5 drop to 0.7-3.2% (from 26-85%). stop3/stop4 remain
22-30%: their privilege-violation loop never re-syncs to the beam, so the
one-off interrupt-entry offset from the known Copper-write/IPL-recognition
class persists for the whole frame.
MOVES/MOVECCR payload cells now measure cycle-exact against vAmiga (the
bracketing COLOR00 spans match pixel-for-pixel), but the buckets' scores
are dominated by the cpu2.i harness's colour-painting main loop: its
stripe phase depends on the exact instruction boundary at which each of
the six per-frame interrupts is recognized, and Copperline's recognition
point sits a few colour clocks later in the instruction stream than
vAmiga's (the documented Copper-write-timing + IPL-pipe class; the pushed
return PCs differ by 1-2 NOPs at equal beam positions). Until that class
is fixed, the stripe-noise floor for these buckets stays ~40%; the same
payloads measured by the silent-main-loop CPU/Timing harness score
2-4.5%.
Verification
crates/m68k/tests/stop_and_68010_timing_tests.rs: 10 regression tests(MOVES per-mode totals both directions, MOVE from CCR, RTE 24, dispatch
44/46, STOP verbatim-SR/privilege/trace/loaded-T semantics and cycles).
STOP.json.binpasses locally (M68K_SST_FIXTURES).Second Nature shift - isolated to the stopped-CPU wake-timing commit
(the m68k-only commits keep full identity): these demos trackload
through Kickstart, whose Exec idle loop STOPs between disk interrupts,
and each wake now lands at the device event instead of up to a
fast-forward quantum late. Verified benign: identical scenes with a
sub-second timeline shift (SOTA's credits screen appears at 30s on the
branch vs 31s on the base; eon/Second Nature differ by 1.6-2.2% of
pixels). Same legitimate-shift class as this base's own dispatch-order
change.
pre-rebase): zero regressions > 1pp outside the target buckets; most of
the 68010 landscape improves.
--all-targets --all-features -D warnings, cargo fmt --check: clean.
(The m68k SST/musashi fixture test bins fail to compile locally on
clean main too - known baseline; CI runs SST on the PR.)