Skip to main content

rustynes_cpu/
bus.rs

1//! CPU `Bus` trait.
2//!
3//! Per `docs/cpu-6502.md` §Interfaces. Phase 1 keeps the surface minimal:
4//! address-fanout reads/writes plus interrupt polling. The DMA halt mechanism
5//! lands when the APU does (Phase 3), and the cycle-level tick callback is
6//! enough to drive `cpu_timing_test` and golden-log compares without the full
7//! lockstep scheduler.
8
9use crate::scheduler::M2Phase;
10
11/// Address-space bus seen by the CPU.
12///
13/// The CPU borrows `&mut Bus` for the duration of an instruction; the bus
14/// fans the access out to RAM, PPU registers, APU registers, controllers,
15/// and the cartridge's mapper.
16pub trait Bus {
17    /// Read a byte at `addr`.
18    fn cpu_read(&mut self, addr: u16) -> u8;
19
20    /// Write `value` to `addr`.
21    fn cpu_write(&mut self, addr: u16, value: u8);
22
23    /// Edge-triggered NMI poll. Returns `true` exactly once per high-to-low
24    /// transition of the NMI line; subsequent calls return `false` until the
25    /// next transition.
26    fn poll_nmi(&mut self) -> bool {
27        false
28    }
29
30    /// Level-sensitive IRQ. Sampled by the CPU on every instruction's
31    /// second-to-last cycle; only honored when the CPU's I flag is clear.
32    fn poll_irq(&mut self) -> bool {
33        false
34    }
35
36    /// Phase-aware level-sensitive IRQ sample.
37    ///
38    /// Returns the IRQ line as seen at the requested half of the 6502
39    /// cycle.  Phase-aware bus implementations override this to expose
40    /// the M2-low vs M2-high asymmetry the C1 IRQ-timing rework relies
41    /// on (see `docs/adr/0002-irq-timing-coordination.md`); the default
42    /// impl simply delegates to [`Bus::poll_irq`], so legacy / test bus
43    /// stubs that don't model the phase distinction stay correct without
44    /// needing to import [`M2Phase`].
45    ///
46    /// Phase B3 of the C1 rework: `Cpu::idle_tick` calls
47    /// `bus.poll_irq_at_phase(M2Phase::High)` — semantically identical
48    /// to the previous `bus.poll_irq()` call because the production
49    /// [`crate::Bus`] impl on `LockstepBus` takes its M2-high snapshot
50    /// at the same end-of-cycle point the historical `poll_irq` query
51    /// fired from.
52    fn poll_irq_at_phase(&mut self, phase: M2Phase) -> bool {
53        let _ = phase;
54        self.poll_irq()
55    }
56
57    /// Called once per CPU cycle consumed. Used by the scheduler to advance
58    /// the PPU/APU in lockstep (Phase 2+) and by the test harness to count
59    /// cycles for golden-log compare.
60    fn on_cpu_cycle(&mut self) {}
61
62    /// φ1 (pre-access) half of one CPU cycle, for the C1 access-reorder
63    /// axis attempt 17.  Called BEFORE the bus access in
64    /// `Cpu::read1` / `Cpu::write1` when the
65    /// `cpu-c1-attempt-17-access-reorder` feature is enabled.
66    ///
67    /// On the production [`crate::Bus`] (`LockstepBus`), this ticks
68    /// PPU sub-dot 0 (1 PPU dot) and captures the M2-low IRQ
69    /// snapshot.  Default impl is a no-op so legacy / test buses
70    /// don't accidentally advance state when paired with the φ2
71    /// default (which calls [`Bus::on_cpu_cycle`] to do all the
72    /// work).
73    fn cpu_cycle_phi1(&mut self) {}
74
75    /// φ2 (post-access) half of one CPU cycle.  Called AFTER the
76    /// bus access in `Cpu::read1` / `Cpu::write1`
77    /// when the `cpu-c1-attempt-17-access-reorder` feature is
78    /// enabled.
79    ///
80    /// On the production `LockstepBus`, this ticks PPU sub-dots 1+2
81    /// (2 PPU dots), increments the bus-side cycle counter, fires
82    /// `notify_cpu_cycle` + `tick_with_external`, and captures the
83    /// M2-high IRQ snapshot.
84    ///
85    /// The default impl delegates to [`Bus::on_cpu_cycle`] so
86    /// legacy / test buses keep their current behaviour: φ1 is a
87    /// no-op, φ2 does all the work, same total per-cycle work as a
88    /// single `on_cpu_cycle` call.
89    fn cpu_cycle_phi2(&mut self) {
90        self.on_cpu_cycle();
91    }
92
93    /// Notify the bus that the CPU is about to perform an interrupt
94    /// vector fetch from `vector` (`$FFFE` for IRQ/BRK, `$FFFA` for NMI,
95    /// or `$FFFA` if an IRQ/BRK service sequence was hijacked by an NMI
96    /// edge during cycles 1..=5 of the service sequence).  `is_nmi` is
97    /// `true` for an NMI service entry and `false` for an IRQ or BRK
98    /// service entry (so the bus can distinguish hijack from a clean
99    /// NMI even when the vector is the same).
100    ///
101    /// Default impl is a no-op; production buses with the
102    /// `irq-timing-trace` feature override this to emit a
103    /// [`ServiceEvent`] into the IRQ trace fixture.  Phase 1.2 of
104    /// Track C1 attempt 14 added this method to close the schema gap
105    /// with Mesen2's `emu.eventType.irq` / `emu.eventType.nmi` oracle.
106    ///
107    /// [`ServiceEvent`]: # "see rustynes_core::irq_trace::ServiceEvent"
108    fn notify_irq_service(&mut self, vector: u16, is_nmi: bool) {
109        let _ = vector;
110        let _ = is_nmi;
111    }
112
113    /// Cumulative bus-side cycle counter.
114    ///
115    /// On the production `LockstepBus`, this is `self.cycle` —
116    /// the total number of CPU cycles the bus has ticked, INCLUDING
117    /// DMC DMA halt + dummy + alignment + transfer cycles (which
118    /// the CPU's own `Cpu::cycles` field does NOT count because
119    /// they advance through `bus.tick_one_cpu_cycle()` rather than
120    /// the CPU's `idle_tick`).
121    ///
122    /// Used by the SH* unstable-store family (`SHA / SHX / SHY /
123    /// SHS / TAS`) to detect when DMC DMA interrupted the
124    /// instruction's dummy-read cycle: per Mesen2 `NesCpu.h`
125    /// `SyaSxaAxa` (lines 716-745), if the dummy read consumed
126    /// more than 1 bus cycle, a DMA fired, and the value written
127    /// is `valueReg` un-ANDed with the H+1 byte (the DMA pulled
128    /// the bus low / corrupted the latch).  Mesen2 detects this
129    /// via `_state.CycleCount - cyc > 1` after the dummy read;
130    /// we mirror via `bus.cycle_count() - before > 1`.
131    ///
132    /// Default impl returns `0` for legacy / test bus stubs.
133    fn cycle_count(&self) -> u64 {
134        0
135    }
136
137    /// Most recent value driven onto the **internal** CPU data bus.
138    ///
139    /// The 2A03 silicon has two distinct data buses: the **internal**
140    /// data bus carries CPU instruction fetches, operand reads, ALU
141    /// results, and writes; the **external** data bus is shared with
142    /// the DMC DMA fetch path and is observable via the open-bus
143    /// latch.  The two buses are equal on every cycle where the CPU
144    /// drives the bus, but diverge during DMC DMA halt: the DMC
145    /// fetch drives the external bus (the "open bus") while the CPU
146    /// is halted and the internal bus retains its prior value.
147    ///
148    /// Default impl returns `0` for legacy / test bus stubs that do
149    /// not model the distinction.  The production `LockstepBus`
150    /// overrides this to expose the latched internal value (mirrored
151    /// from every CPU read but NOT updated by DMC DMA fetches).
152    ///
153    /// Used by the SH* unstable-store family (`SHA / SHX / SHY / SHS
154    /// / TAS`, opcodes `$93 / $9C / $9E / $9F / $9B`) when computing
155    /// the address-high-byte AND-and-write quantity under DMC DMA
156    /// interleaving, and by the `$4015` read path for the bit-5
157    /// open-bus exposure that `CPU Behavior :: Open Bus` Test 9
158    /// brackets.  Phase 1 of the v1.0.0-final
159    /// `linked-puzzling-sutherland` brief (see
160    /// `to-dos/phase-6-v1.0.0-final/sprint-6-sh-unstable-stores.md`).
161    ///
162    fn internal_data_bus(&self) -> u8 {
163        0
164    }
165
166    // ================================================================
167    // v2.0 master-clock R1 substrate — clean Bus contract (Phase 1).
168    //
169    // These methods exist ALONGSIDE the legacy lockstep methods above and
170    // are only consulted by the `mc-r1-substrate` CPU loop (Phases 2+). They
171    // carry default impls delegating to the legacy surface so every existing
172    // `Bus` impl (test stubs included) keeps compiling unchanged; the
173    // production `LockstepBus` overrides them with the real master-clock
174    // catch-up. Gated so the default build's trait surface is unchanged.
175    // See `docs/audit/v2.0-master-clock-r1-port-plan-2026-06-03.md`.
176    // ================================================================
177
178    /// Pure address-space read (no per-cycle work). Under R1 the cycle work
179    /// is done by [`Bus::run_ppu_to`] + [`Bus::cpu_clock`], which the CPU
180    /// calls around the access. Default delegates to [`Bus::cpu_read`].
181    fn read(&mut self, addr: u16) -> u8 {
182        self.cpu_read(addr)
183    }
184
185    /// Pure address-space write. Default delegates to [`Bus::cpu_write`].
186    fn write(&mut self, addr: u16, value: u8) {
187        self.cpu_write(addr, value);
188    }
189
190    /// Master clocks per CPU cycle for the cartridge region: NTSC 12, PAL 16,
191    /// Dendy 15 (the master-clock unit is shared with [`Bus::run_ppu_to`]'s
192    /// `ppu_divider`, so per CPU cycle the PPU advances `cpu_divider /
193    /// ppu_divider` dots — 3:1 NTSC, 3.2:1 PAL, 3:1 Dendy). The R1 CPU loop
194    /// advances `master_clock` and derives its read/write split off this. The
195    /// default (12) keeps test stubs + the non-regioned path on NTSC; the
196    /// `LockstepBus` overrides from the cartridge region.
197    fn cpu_divider(&self) -> u64 {
198        12
199    }
200
201    /// Catch the PPU up to `target` master clocks (Mesen `NesPpu::Run` /
202    /// `TetaNES` `clock_to`). Ticks whole PPU dots while
203    /// `ppu_clock + ppu_divider <= target`. Called by the R1 CPU loop in
204    /// BOTH halves of each access (the double catch-up). Default no-op.
205    ///
206    /// `is_post_access` distinguishes WHICH half of the CPU cycle this
207    /// catch-up belongs to: `false` for the pre-access half (called from
208    /// `Cpu::start_cycle`, before the bus access — mirrors Mesen's
209    /// `StartCpuCycle`), `true` for the post-access half (called from
210    /// `Cpu::end_cycle`, after the bus access — mirrors `EndCpuCycle`).
211    /// R1c-3 (`mmc3-m2-phase-irq`, default-off experiment): `LockstepBus`
212    /// forwards this as the real M2-phase label on the `PpuBusAdapter` it
213    /// constructs, replacing the previously call-local (and therefore
214    /// almost-always-zero) `sub_dot` counter with a value that actually
215    /// distinguishes the pre-access (M2-low, φ1) and post-access
216    /// (M2-high, φ2) halves for any A12 transition ticked during this
217    /// catch-up. See `docs/adr/0002-irq-timing-coordination.md` and
218    /// `docs/audit/r1r2-per-dot-scheduler-attempt-2026-07-02.md`.
219    fn run_ppu_to(&mut self, target: u64, is_post_access: bool) {
220        let _ = (target, is_post_access);
221    }
222
223    /// One CPU cycle of bus-side work (Mesen `ProcessCpuClock`): APU +
224    /// frame counter + per-cycle mapper hook + bus-side DMA drain + cycle
225    /// counter. The PPU advance is in [`Bus::run_ppu_to`], not here. Default
226    /// delegates to [`Bus::on_cpu_cycle`] (legacy combined per-cycle work).
227    fn cpu_clock(&mut self) {
228        self.on_cpu_cycle();
229    }
230
231    /// F-2: tick ONLY the DMC byte-timer + DMA arm, at END of cycle (called
232    /// from `Cpu::end_cycle` after the access + PPU catch-up). This places the
233    /// DMC fire-phase at main's end-of-cycle position (so `DMASync`'s `$4000`
234    /// open-bus conflict lands), while the rest of the APU (incl. the IRQ line)
235    /// stays on the cycle-start `cpu_clock` tick (so the C1 φ2 IRQ sample is
236    /// unchanged). Default no-op. Pairs with `Apu::set_dmc_driven_externally`.
237    fn cpu_clock_apu_dmc(&mut self) {}
238
239    /// Master clocks consumed by bus-side DMA cycles since the last call,
240    /// then reset to 0. The R1 CPU loop folds this into `master_clock` in
241    /// `end_cycle` so the CPU<->PPU phase stays coherent across a bus-side
242    /// DMA span. Default 0 (no bus-side DMA accounting on test stubs).
243    fn take_dma_mc_consumed(&mut self) -> u64 {
244        0
245    }
246
247    /// Live IRQ line level (mapper IRQ OR APU frame-counter/DMC IRQ). The
248    /// CPU does the I-flag mask + one-cycle `prev_run_irq` delay itself.
249    /// Default `false`; the production bus overrides this.
250    fn irq_level(&self) -> bool {
251        false
252    }
253
254    /// Live /NMI line level (PPU-driven). The CPU does its own edge detect +
255    /// one-cycle `prev_need_nmi` delay. Default `false` (test stubs).
256    fn nmi_level(&self) -> bool {
257        false
258    }
259
260    /// Phase B (interleaved DMC DMA): is a DMC DMA pending and needing cycles?
261    /// The CPU loops on this in `read1`, running one `dmc_dma_step` per R1 cycle
262    /// BEFORE its own read (DMA halts only on read cycles). Default `false`.
263    fn dmc_dma_pending(&self) -> bool {
264        false
265    }
266
267    /// `mc-r1-dmc-load-get-entry`: defer a LOAD whose first-service would be a PUT
268    /// cycle by 1 CPU cycle so it enters on a GET (span-3 hardware load). Gates BOTH
269    /// the read1 loop AND the `idle_tick` loop (`DMASync`'s load fires during NOPs=idle).
270    fn dmc_dma_defer_load_entry(&self) -> bool {
271        false
272    }
273
274    /// Phase B: perform ONE cycle's worth of interleaved DMC DMA bus access
275    /// (halt re-read / sample get), advancing the halt/get state. `halted_addr`
276    /// is the CPU read the DMA is preempting. Default no-op.
277    fn dmc_dma_step(&mut self, halted_addr: u16) {
278        let _ = halted_addr;
279    }
280
281    /// `mc-r1-dmc-idle-halt`: perform one interleaved DMC-DMA cycle during a CPU
282    /// INTERNAL cycle (no instruction read). The bus supplies the held address
283    /// (its last-read bus address) since `idle_tick` has none. Default no-op.
284    fn dmc_dma_step_idle(&mut self) {}
285
286    /// Stage-D (`mc-r1-full-cpu`): is an OAM DMA pending or in flight? The CPU
287    /// loops on this in `read1` (after the DMC loop, DMC-get-before-OAM-get), so
288    /// each OAM cycle runs CPU-driven (wrapped `start_cycle`/`end_cycle`) and
289    /// samples IRQ/NMI via the φ2 pipeline — the surface the bus-burst bypassed.
290    /// Default `false`.
291    fn oam_dma_pending(&self) -> bool {
292        false
293    }
294
295    /// Stage-D: perform ONE cycle of the OAM DMA (set-up on first call from a
296    /// pending `$4014`, then halt/align/read/write per cycle). Does NOT advance
297    /// time — the surrounding `start_cycle`/`end_cycle` do. Default no-op.
298    fn oam_dma_step(&mut self, halted_addr: u16) {
299        let _ = halted_addr;
300    }
301
302    /// Program M (M-2, `mc-r1-dmc-oam-overlap`): is an OAM DMA actually IN FLIGHT
303    /// (started, cycles still owed) — distinct from `oam_dma_pending`, which is
304    /// true for a not-yet-started `$4014` write too. The overlap loop uses this
305    /// to decide whether a DMC halt cycle can SHARE an OAM cycle. Default `false`.
306    fn oam_dma_in_flight(&self) -> bool {
307        false
308    }
309
310    /// W3-Stage-0 (`mc-r1-counter-collapse` boundary realign): may a pending DMC
311    /// DMA join an OAM DMA as an OVERLAP event? Default delegates to
312    /// [`Bus::oam_dma_in_flight`]. Under the counter-collapse flag the bus also
313    /// answers `true` for a `$4014` write that is PENDING but not yet started:
314    /// the end-of-cycle byte-timer shift can surface the DMC arm in the gap
315    /// between the `$4014` write and OAM's first cycle, and routing that arm to
316    /// the standalone `dmc_dma_step` (full unshared span) instead of the overlap
317    /// event is exactly the DMC+OAM idx\[7\] regime-transition error (lockstep
318    /// latches OAM in `drain_dma` BEFORE its DMC-pending check, so the same arm
319    /// overlaps OAM's halt/alignment cycles there).
320    fn oam_dma_overlap_ready(&self) -> bool {
321        self.oam_dma_in_flight()
322    }
323
324    /// Program M (M-2): did the most recent [`Bus::dmc_dma_step`] perform the DMC
325    /// GET (the sample fetch) rather than a halt/dummy/align cycle? The overlap
326    /// loop advances OAM on non-GET (halt) cycles only — the GET steals an OAM
327    /// slot. Default `false`.
328    fn dmc_dma_last_was_get(&self) -> bool {
329        false
330    }
331
332    /// Program M (M-2): advance ONE OAM DMA cycle that is SHARED with a DMC halt
333    /// cycle (the 6502 is RDY-halted by the DMC, but the OAM engine keeps
334    /// consuming its read/write slot on the external bus). Does NOT advance time
335    /// — the surrounding `start_cycle`/`end_cycle` do. Default no-op.
336    fn oam_dma_overlap_cycle(&mut self) {}
337
338    /// Program M (M-2, exact): begin ONE DMC-DMA-during-OAM event, mirroring the
339    /// lockstep `service_dmc_dma_during_oam` prologue. Latches the DMA span + the
340    /// open-bus replay and returns the UNCONDITIONAL halt/dummy/align noop count
341    /// (2 for a short/load DMA, 3 for a reload) — NOT parity-gated. The CPU then
342    /// runs exactly that many [`Bus::dmc_overlap_noop_cycle`]s, one
343    /// [`Bus::dmc_overlap_get_cycle`], and (if OAM still owes) one
344    /// [`Bus::dmc_overlap_realign_cycle`]. `halted_addr` is the CPU read the DMA
345    /// pair is preempting — used as the OAM halt address when the event starts a
346    /// PENDING (not-yet-latched) `$4014` OAM DMA (the counter-collapse boundary
347    /// case; an already-in-flight OAM keeps its own latched halt address).
348    /// Default `0` (no DMC event).
349    fn dmc_overlap_begin(&mut self, halted_addr: u16) -> u32 {
350        let _ = halted_addr;
351        0
352    }
353
354    /// Program M (M-2, exact): one DMC halt/dummy/align cycle that OVERLAPS OAM.
355    /// Replays the held CPU read's side-effect, then (if OAM still owes) advances
356    /// one OAM slot. Mirrors lockstep's noop-loop body (`replay_dma_noop_read` +
357    /// `clock_oam_dma_cycle`) minus the time tick. Default no-op.
358    fn dmc_overlap_noop_cycle(&mut self) {}
359
360    /// Program M (M-2, exact): the DMC GET cycle — owns the memory read; OAM is
361    /// STALLED (does NOT advance). Fetches + delivers the sample and clears the
362    /// DMC-DMA pending state. Mirrors lockstep's get block + the R1
363    /// `dmc_dma_step` GET. Default no-op.
364    fn dmc_overlap_get_cycle(&mut self) {}
365
366    /// Program M (M-2, exact): the post-GET realign stall — ONE extra OAM-stalled
367    /// cycle (OAM does NOT advance) so the next OAM read resumes on a later get,
368    /// mirroring lockstep's `if dma_cycles_owed > 0 { tick }`. The cycle the prior
369    /// per-cycle scaffold was MISSING. Default no-op.
370    fn dmc_overlap_realign_cycle(&mut self) {}
371
372    /// W3-Stage-1 (`mc-r1-dma-unified`): is ANY DMA work pending for the
373    /// unified DMC/OAM engine — a serviceable DMC DMA (pending and not a
374    /// load deferred to its get-cycle entry, the `mc-r1-dmc-load-get-entry`
375    /// rule), a `$4014` OAM DMA awaiting its first cycle, or an OAM transfer
376    /// still in flight? The ONE `Cpu::read1`/`idle_tick` DMA loop spins on
377    /// this, running one [`Bus::unified_dma_cycle`] per CPU cycle (each a
378    /// full R1 cycle: `start_cycle` -> dispatch -> `end_cycle`, so every DMA
379    /// cycle keeps the φ2 IRQ sample — the C1-safe shape). Default `false`.
380    fn unified_dma_pending(&self) -> bool {
381        false
382    }
383
384    /// W3-Stage-1 (`mc-r1-dma-unified`): ONE cycle of the unified DMC/OAM DMA
385    /// engine — a direct port of the `TriCNES` `_6502` per-cycle DMA dispatch
386    /// table (the SINGLE driver standalone DMC, standalone OAM, and the
387    /// overlap all ride), at FLOOR parity for this stage. `halted_addr` is
388    /// the CPU read the DMA is preempting (the parked 6502 address bus).
389    /// Does NOT advance time — the surrounding `start_cycle`/`end_cycle` do.
390    /// Default no-op.
391    fn unified_dma_cycle(&mut self, halted_addr: u16) {
392        let _ = halted_addr;
393    }
394
395    /// W3-Stage-1 (`mc-r1-dma-unified`): one unified-engine DMA cycle during
396    /// a CPU INTERNAL cycle (no instruction read; the bus supplies its held
397    /// last-read address). The unified replacement for
398    /// [`Bus::dmc_dma_step_idle`]. Default no-op.
399    fn unified_dma_cycle_idle(&mut self) {}
400
401    /// accuracycoin-100 Phase 2 (`mc-r1-dmc-abort-cancel`): is a 1-byte
402    /// non-looping implicit DMC-DMA abort matured and awaiting service? The CPU
403    /// consults this at the top of `read1`/`write1`. Default `false`.
404    fn dmc_abort_pending(&self) -> bool {
405        false
406    }
407
408    /// accuracycoin-100 Phase 2: is the upcoming cycle a GET (read) cycle for
409    /// the DMC DMA (`!put_cycle`)? On a get cycle the matured abort runs as a
410    /// 1-cycle DMA (Y=1); on a put cycle (or any CPU write) it does NOT occur
411    /// (Y=0, "the abort will not land on a write cycle"). Default `false`.
412    fn dmc_abort_is_get_cycle(&self) -> bool {
413        false
414    }
415
416    /// accuracycoin-100 Phase 2: service the matured abort as a 1-cycle DMA
417    /// (Y=1) — one halt re-read of `halted_addr`, then clear the abort + the
418    /// pending reload. Called by `read1` only on a get cycle. Default no-op.
419    fn dmc_abort_halt_step(&mut self, halted_addr: u16) {
420        let _ = halted_addr;
421    }
422
423    /// accuracycoin-100 Phase 2: cancel the matured abort with NO halt cycle
424    /// (Y=0) — the abort lands on a write/put cycle so the DMA does not occur.
425    /// Clears the abort + the pending reload. Default no-op.
426    fn dmc_abort_cancel(&mut self) {}
427
428    /// Diagnostic-only hook fired once per R1 CPU cycle from `Cpu::end_cycle`
429    /// (after `handle_interrupts`), so the `irq-timing-trace` tooling can
430    /// record a `CycleRecord` for the R1 access path (which bypasses the
431    /// `LockstepBus` `tick_one_cpu_cycle` push). Default no-op; the production
432    /// bus overrides it only under the `irq-timing-trace` feature, so non-trace
433    /// R1 builds compile this to an empty call.
434    fn trace_end_cycle(&mut self) {}
435
436    /// Diagnostic-only hook fired once per CPU INSTRUCTION from `Cpu::step`
437    /// (at the opcode fetch), with the instruction's `pc` and the cumulative
438    /// CPU cycle count. Lets the `cpu-instr-cycle-trace` tooling diff R1 vs
439    /// default per-instruction to pin the cumulative cycle-count divergence
440    /// (the R1c-1 odd-cycle source). Default no-op.
441    #[cfg(feature = "cpu-instr-cycle-trace")]
442    fn trace_instr(&mut self, _pc: u16, _cpu_cycle: u64) {}
443}