m68k: align per-instruction IPL poll points and interrupt dispatch idle placement with the 68000 microcode#144
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.
LinuxJedi
added a commit
that referenced
this pull request
Jul 7, 2026
…nt models it The +4 recognition-latency offset for copper-raised interrupts was calibrated against the pre-#144 IPL pipe, which itself embedded the old +4-late copper landings. With the per-instruction IPL poll placement on main, copper INTREQ/INTENA writes recognize correctly from their bus-true landings: with the offset the vAmigaTS Copper/Irq family regressed 15.0 -> 42.3 and coprace 96.4 -> 167.1; without it they sit at the new-main baseline.
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.
Hardware behavior
The 68000/68010 sample their IPL pins at ONE microcode-determined point per
instruction, and the interrupt decision at the next instruction boundary
consumes that sample (Moira's POLL placement; pasti 68kPrefetch). Copperline
samples at the start of every CPU bus access and consumed the LAST access's
sample - correct for the majority class whose poll rides the final prefetch,
but one bus access off wherever the microcode polls elsewhere. Under DMA
contention those instructions recognized interrupts one boundary early or
late. Three aligned pieces:
service_interrupt): the 68000spends 6 idle clocks before the PC-low frame write, runs the 4-clock
IACK next, and 4 more internal clocks before the SR and PC-high writes;
the 68010 spends 12 leading idle clocks and writes PC-low, SR, PC-high,
vector word (46 clocks total). All idle time was previously paid after
the handler-entry prefetch, so every handler's first instruction started
about 14 clocks early.
AddressBus::ipl_hold_sample/ipl_release_samplemarkers let the core pin the boundary sample at theaccess that carries the microcode poll. Placements (Moira/pasti):
ADDQ/SUBQ, CLR/NEG/NEGX/NOT, BCHG/BSET/BCLR, NBCD, Scc, AND/OR/EOR/
ADD/SUB Dn,, memory shifts, MOVE to -(An). ADDI/SUBI and MOVE
from SR keep polling at the write (their microcode differs).
operand reads; ADDX/SUBX.L at the low-word write of the interleaved
writeback; MOVEP (read form) at the last byte read.
size; MOVE.L with a register/immediate source to (An), d16(An) or
d8(An,Xn) polls before the write's low word (write split into its two
word cycles).
stack read; PEA at the pre-push prefetch, except absolute modes which
push FIRST and prefetch last (order fix).
clocks (an interrupt rising during the division is taken one
instruction later); division by zero spends 8 internal clocks before
the exception frame. Taken TRAPV performs its dummy program-space
read before the frame; trace spends 4 internal clocks before its
first stack write.
mode-dependent 6/8/4 internal clocks between the EA calculation and the
data cycle, 18-22 clocks total); RTE re-read the already-probed format
word (hardware: format, SR, PC = four reads, SP adjust, refill; 24
clocks).
Evidence
vAmigaTS CPU/IPL family - 446 cases; each case raises IRQ1-6 on a 2-cck
grid across every boundary of a payload instruction, so one bus access of
poll placement error shows as one shifted handler bar:
190 cases improve by more than 1pp, 88 regress. The regressions are the
documented copper-write-landing class (Copperline's copper MOVE after WAIT
lands ~4 cck late; interfere*): the old 14-clock-early handler entry
happened to cancel it on lucky grid alignments, and the corrected dispatch
exposes it - the regressed set includes word forms this PR does not touch
at all (SUB1w, MOVE5w/7b, BSR1). Handler-entry structure now matches vAmiga
to within 2 cck with correct preemption spans (row-level traces on ADD1w;
previously 6 cck early with wrong irq6 spans). An IPL-pipe recalibration
(COPPERLINE_IRQ_LATENCY_CCK=3/4 sweep) was measured and rejected: it
re-rolls the alignment instead of fixing the class.
Guard families (same machine, before/after):
Both guards improve net; the cases above +1pp (12 distinct Paula tests, max
+5.1: vblank6, irqbpl3b/5b, inttim doubles; 2 CIA tests: cnt3c +3.2,
todint3 +1.9) are the same documented IRQ-cadence/copper-landing class and
are outweighed by in-family improvements (cnt3b -11.8, todpulse1a -4.3,
basicint/inttim broad gains).
Demo gate (14 configs vs main at fixed emulated times): 11 byte-identical;
eon, sota and secondnature differ as expected from the hardware-documented
dispatch bus-order change and were verified visually (same scene within one
animation frame; sota's fade reconverges by 31s). eon scene-player
diagnostics on the branch: 2183 mainline iterations in 60-104s (the PR #140
acceptance value, vAmiga runs 2182) and 2 boundary-early softint
recognitions (253 before #140).
cargo test --lib green (the TAS arbitration test gains the hardware
read-to-write internal cck), clippy --all-targets -D warnings clean,
cargo fmt --check clean.
Explicitly out of scope (measured)
harness renders a whole-screen phase hash of the entire 68010 sync loop;
it needs a 68010 per-instruction cycle audit, not poll placement.
forms): below the 1-cck granularity of the INTREQ recognition-latency
model.
Moira pushes first. Kept the yacht order.
documented interrupt/CPU-write beam-timing class, not poll placement.