Skip to content

m68k: align per-instruction IPL poll points and interrupt dispatch idle placement with the 68000 microcode#144

Merged
LinuxJedi merged 3 commits into
mainfrom
fix/ipl-poll-placement
Jul 7, 2026
Merged

m68k: align per-instruction IPL poll points and interrupt dispatch idle placement with the 68000 microcode#144
LinuxJedi merged 3 commits into
mainfrom
fix/ipl-poll-placement

Conversation

@LinuxJedi

Copy link
Copy Markdown
Owner

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:

  1. Interrupt dispatch idle placement (service_interrupt): the 68000
    spends 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.
  2. Per-instruction poll points: new AddressBus::ipl_hold_sample /
    ipl_release_sample markers let the core pin the boundary sample at the
    access that carries the microcode poll. Placements (Moira/pasti):
    • RMW instructions poll at the pre-writeback prefetch: ANDI/ORI/EORI,
      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).
    • CMPM, ABCD/SBCD and byte/word ADDX/SUBX -(Ay),-(Ax) poll between the
      operand reads; ADDX/SUBX.L at the low-word write of the interleaved
      writeback; MOVEP (read form) at the last byte read.
    • MOVE to (An)+ polls at the destination write for every source and
      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).
    • LINK polls right before the An push; UNLK before the low word of its
      stack read; PEA at the pre-push prefetch, except absolute modes which
      push FIRST and prefetch last (order fix).
    • TAS runs read, 2 internal clocks, write, then the final prefetch.
    • DIVU/DIVS issue the final prefetch BEFORE the division's internal
      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.
  3. 68010 MOVES and RTE: MOVES billed 4 clocks flat (hardware spends
    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:

baseline (main @ #140) this PR delta
family sum 4681.8 4166.6 -515.2 (-11.0%)
68010 half 2820.6 2312.7 -507.9
68000 half 1861.1 1853.8 -7.3

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):

family before after delta
Paula/Interrupts (168) 1220.8 1134.3 -86.4
CIA (93) 693.6 673.9 -19.7

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)

  • MOVES_68010 residual (680 of the family sum, was 725.6): the ipl1.i
    harness renders a whole-screen phase hash of the entire 68010 sync loop;
    it needs a 68010 per-instruction cycle audit, not poll placement.
  • 68010 poll points that fall 2 clocks before the final prefetch (ALU long
    forms): below the 1-cck granularity of the INTREQ recognition-latency
    model.
  • JSR bus order: Copperline follows yacht (target prefetch, push, prefetch);
    Moira pushes first. Kept the yacht order.
  • The remaining uniform ~2 cck handler-entry offset against vAmiga is the
    documented interrupt/CPU-write beam-timing class, not poll placement.

LinuxJedi added 3 commits July 6, 2026 23:48
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 LinuxJedi merged commit 2cd7e73 into main Jul 7, 2026
7 checks passed
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.
@LinuxJedi LinuxJedi deleted the fix/ipl-poll-placement branch July 7, 2026 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant