Skip to main content

mc6809_core/
cpu.rs

1//   Copyright 2026 Martin Åkesson
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15use crate::memory::Memory;
16use crate::peripheral::BusSignals;
17use crate::registers::Registers;
18
19mod opcodes;
20
21pub use opcodes::instruction_cycles;
22
23// ---------------------------------------------------------------------------
24// Interrupt vector addresses
25// ---------------------------------------------------------------------------
26
27pub const VEC_RESET: u16 = 0xFFFE;
28pub const VEC_NMI: u16 = 0xFFFC;
29pub const VEC_SWI: u16 = 0xFFFA;
30pub const VEC_IRQ: u16 = 0xFFF8;
31pub const VEC_FIRQ: u16 = 0xFFF6;
32pub const VEC_SWI2: u16 = 0xFFF4;
33pub const VEC_SWI3: u16 = 0xFFF2;
34
35// ---------------------------------------------------------------------------
36// CPU state
37// ---------------------------------------------------------------------------
38
39/// Motorola 6809 CPU emulator.
40#[derive(Clone)]
41pub struct Cpu {
42    /// Programmer-visible registers.
43    pub(super) reg: Registers,
44    /// Total elapsed cycles since reset.
45    cycles: u64,
46    /// CPU execution has been explicitly halted by an instruction.
47    halted: bool,
48    /// Sticky status bit set when an illegal opcode is executed.
49    illegal: bool,
50
51    // ---- interrupt state ----
52    /// NMI is armed (becomes true after first write to S).
53    nmi_armed: bool,
54    /// Pending interrupt lines: `BusSignals::NMI | BusSignals::FIRQ | BusSignals::IRQ`.
55    ///
56    /// `NMI` is an edge latch (set externally, cleared when serviced).
57    /// `FIRQ` / `IRQ` mirror the physical pin levels; only the peripheral
58    /// (via for example ['apply_signals`](Self::apply_signals) or
59    /// [`set_irq`](Self::set_irq) / [`set_firq`](Self::set_firq)) clears them.
60    int_lines: BusSignals,
61    /// CWAI: entire state already pushed, waiting for a serviceable interrupt.
62    cwai: bool,
63    /// SYNC: waiting for any interrupt edge.
64    sync: bool,
65}
66
67impl Cpu {
68    /// Create a new CPU with all state zeroed.
69    pub fn new() -> Self {
70        Self {
71            reg: Registers::new(),
72            cycles: 0,
73            halted: false,
74            illegal: false,
75            nmi_armed: false,
76            int_lines: BusSignals::default(),
77            cwai: false,
78            sync: false,
79        }
80    }
81
82    /// Hardware reset: read PC from reset vector, set I+F, clear state.
83    pub fn reset(&mut self, mem: &mut impl Memory) {
84        self.reg = Registers::new();
85        self.reg.cc.set_irq_inhibit(true);
86        self.reg.cc.set_firq_inhibit(true);
87        self.reg.pc = mem.read_word(VEC_RESET);
88        self.cycles = 0;
89        self.halted = false;
90        self.illegal = false;
91        self.nmi_armed = false;
92        self.int_lines = BusSignals::default();
93        self.cwai = false;
94        self.sync = false;
95    }
96
97    /// Read-only access to the programmer-visible registers.
98    pub fn registers(&self) -> &Registers {
99        &self.reg
100    }
101
102    /// Mutable access to the programmer-visible registers via an RAII guard.
103    ///
104    /// The guard implements [`std::ops::Deref`] and [`std::ops::DerefMut`] for
105    /// [`Registers`], giving transparent read/write access to all fields. On drop
106    /// it checks whether the hardware stack pointer (S) changed and, if so, arms
107    /// the NMI — matching the real 6809 behaviour where the first write to S
108    /// enables edge-triggered NMI.
109    ///
110    /// Note: the guard detects S changes by comparing the value on entry with the
111    /// value on drop. Writing S to the value it already holds will not arm NMI, but
112    /// because `nmi_armed` is sticky (never cleared) this is inconsequential in
113    /// practice.
114    ///
115    /// # Example
116    /// ```ignore
117    /// cpu.registers_mut().s = 0x8000; // arms NMI
118    /// {
119    ///     let mut r = cpu.registers_mut();
120    ///     r.s -= 2;
121    ///     mem[r.s as usize] = lo;
122    /// } // NMI armed here via Drop
123    /// ```
124    pub fn registers_mut(&mut self) -> RegistersMut<'_> {
125        let prev_s = self.reg.s;
126        RegistersMut { cpu: self, prev_s }
127    }
128
129    /// Total elapsed cycles since the last [`Self::reset`].
130    pub fn cycles(&self) -> u64 {
131        self.cycles
132    }
133
134    /// `true` if the CPU has been halted by a halt instruction.
135    ///
136    /// Illegal opcodes do not set this flag; they only set [`Self::illegal`]
137    /// so the host can decide whether to keep running or stop.
138    pub fn halted(&self) -> bool {
139        self.halted
140    }
141
142    /// Assert or de-assert the halted state.
143    pub fn set_halted(&mut self, active: bool) {
144        self.halted = active;
145    }
146
147    /// Sticky flag set when an illegal opcode is executed.
148    ///
149    /// The 6809 keeps running after undefined opcodes, so this flag does not
150    /// halt the CPU by itself. Hosts that want trap-like behaviour can check
151    /// this flag after each [`Self::step`] and stop on their own policy.
152    pub fn illegal(&self) -> bool {
153        self.illegal
154    }
155
156    /// Clear the illegal opcode flag.
157    pub fn clear_illegal(&mut self) {
158        self.illegal = false;
159    }
160
161    /// Assert or de-assert the IRQ line (level-triggered).
162    ///
163    /// The CPU samples this each step. Only the peripheral should de-assert it
164    /// (by calling `set_irq(false)`); the CPU never clears it internally.
165    pub fn set_irq(&mut self, active: bool) {
166        if active {
167            self.int_lines.insert(BusSignals::IRQ);
168        } else {
169            self.int_lines.remove(BusSignals::IRQ);
170        }
171    }
172
173    /// Assert or de-assert the FIRQ line (level-triggered).
174    ///
175    /// The CPU samples this each step. Only the peripheral should de-assert it
176    /// (by calling `set_firq(false)`); the CPU never clears it internally.
177    pub fn set_firq(&mut self, active: bool) {
178        if active {
179            self.int_lines.insert(BusSignals::FIRQ);
180        } else {
181            self.int_lines.remove(BusSignals::FIRQ);
182        }
183    }
184
185    /// Trigger an NMI (edge-triggered). Only effective if NMI is armed.
186    pub fn trigger_nmi(&mut self) {
187        if self.nmi_armed {
188            self.int_lines.insert(BusSignals::NMI);
189        }
190    }
191
192    /// Apply a snapshot of bus signals to the CPU, handling NMI edge detection.
193    ///
194    /// Call this from the host loop whenever [`BusSignals`] change. Passing the
195    /// previous snapshot allows the CPU to detect the NMI rising edge internally,
196    /// so the caller does not need to track edge transitions for NMI.
197    ///
198    /// IRQ and FIRQ are level-triggered: their state is mirrored directly into
199    /// the CPU. The CPU will hold the line until the peripheral de-asserts it
200    /// (i.e. returns a snapshot without `IRQ`/`FIRQ` set on a subsequent tick).
201    ///
202    /// RESET is not handled here; the host loop is responsible for calling
203    /// [`Cpu::reset`] when `signals` contains [`BusSignals::RESET`].
204    ///
205    /// # Host loop pattern
206    /// ```ignore
207    /// let mut prev_signals = BusSignals::default();
208    /// loop {
209    ///     let cycles = cpu.step(&mut mem);
210    ///     let signals = peripheral.tick(cycles);
211    ///
212    ///     if signals.contains(BusSignals::RESET) {
213    ///         cpu.reset(&mut mem);
214    ///         prev_signals = BusSignals::default();
215    ///         continue;
216    ///     }
217    ///
218    ///     if signals != prev_signals {
219    ///         cpu.apply_signals(signals, prev_signals);
220    ///         prev_signals = signals;
221    ///     }
222    ///
223    ///     if cpu.halted() { break; }
224    /// }
225    /// ```
226    pub fn apply_signals(&mut self, signals: BusSignals, prev: BusSignals) {
227        // NMI: edge-triggered — arm on rising edge only
228        if signals.contains(BusSignals::NMI) && !prev.contains(BusSignals::NMI) {
229            self.trigger_nmi();
230        }
231        // IRQ/FIRQ: level-triggered — mirror current pin state
232        if signals.contains(BusSignals::FIRQ) {
233            self.int_lines.insert(BusSignals::FIRQ);
234        } else {
235            self.int_lines.remove(BusSignals::FIRQ);
236        }
237        if signals.contains(BusSignals::IRQ) {
238            self.int_lines.insert(BusSignals::IRQ);
239        } else {
240            self.int_lines.remove(BusSignals::IRQ);
241        }
242    }
243
244    /// Execute a single instruction (or handle a pending interrupt).
245    /// Returns the number of cycles consumed or ZERO if the CPU is halted.
246    ///
247    /// Warning! Busy-loops in host code that doesn't check [`Self::halted`]
248    /// can lead to high CPU usage.
249    ///
250    /// If the decoded instruction is illegal, the CPU records that in
251    /// [`Self::illegal`] and continues execution unless the caller chooses to
252    /// stop.
253    pub fn step(&mut self, mem: &mut impl Memory) -> u64 {
254        if self.halted {
255            return 0;
256        }
257
258        let start_cycles = self.cycles;
259
260        // Handle SYNC state: wait for any interrupt edge
261        if self.sync {
262            if !self.int_lines.is_empty() {
263                self.sync = false;
264            } else {
265                self.cycles += 1;
266                return 1;
267            }
268        }
269
270        // Handle CWAI state: entire state already pushed, waiting for a
271        // serviceable interrupt (NMI is always serviceable; FIRQ/IRQ respect masks).
272        if self.cwai {
273            let serviceable = self.int_lines.contains(BusSignals::NMI)
274                || (self.int_lines.contains(BusSignals::FIRQ) && !self.reg.cc.firq_inhibit())
275                || (self.int_lines.contains(BusSignals::IRQ) && !self.reg.cc.irq_inhibit());
276            if !serviceable {
277                self.cycles += 1;
278                return 1;
279            }
280        }
281
282        // Check pending interrupts (priority: NMI > FIRQ > IRQ)
283        if self.check_interrupts(mem) {
284            return self.cycles - start_cycles;
285        }
286
287        // Fetch and execute one instruction
288        let opcode = self.fetch_byte(mem);
289        self.execute(mem, opcode);
290
291        self.cycles - start_cycles
292    }
293
294    /// Run until at least `cycle_budget` cycles have been consumed.
295    ///
296    /// This method stops only when the cycle budget is exhausted or
297    /// [`Self::halted`] becomes true. Illegal opcodes do not stop `run`; check
298    /// [`Self::illegal`] in the host loop if that policy is desired.
299    pub fn run(&mut self, mem: &mut impl Memory, cycle_budget: u64) -> u64 {
300        let start_cycles = self.cycles;
301        let target = self.cycles + cycle_budget;
302        while self.cycles < target && !self.halted {
303            self.step(mem);
304        }
305        self.cycles - start_cycles
306    }
307
308    // ---- interrupt logic ----
309
310    fn check_interrupts(&mut self, mem: &mut impl Memory) -> bool {
311        if self.int_lines.is_empty() {
312            return false;
313        }
314
315        // NMI (edge-triggered, highest priority): clear the latch on service.
316        if self.int_lines.contains(BusSignals::NMI) {
317            self.int_lines.remove(BusSignals::NMI);
318            if !self.cwai {
319                self.reg.cc.set_entire(true);
320                self.push_entire_state(mem);
321            }
322            self.cwai = false;
323            self.reg.cc.set_irq_inhibit(true);
324            self.reg.cc.set_firq_inhibit(true);
325            self.reg.pc = mem.read_word(VEC_NMI);
326            self.cycles += 19;
327            return true;
328        }
329
330        // FIRQ (level-triggered): do NOT clear — only the peripheral de-asserts.
331        if self.int_lines.contains(BusSignals::FIRQ) && !self.reg.cc.firq_inhibit() {
332            if !self.cwai {
333                self.reg.cc.set_entire(false);
334                self.push_word_s(mem, self.reg.pc);
335                self.push_byte_s(mem, self.reg.cc.to_byte());
336            }
337            self.cwai = false;
338            self.reg.cc.set_irq_inhibit(true);
339            self.reg.cc.set_firq_inhibit(true);
340            self.reg.pc = mem.read_word(VEC_FIRQ);
341            self.cycles += 10;
342            return true;
343        }
344
345        // IRQ (level-triggered): do NOT clear — only the peripheral de-asserts.
346        if self.int_lines.contains(BusSignals::IRQ) && !self.reg.cc.irq_inhibit() {
347            if !self.cwai {
348                self.reg.cc.set_entire(true);
349                self.push_entire_state(mem);
350            }
351            self.cwai = false;
352            self.reg.cc.set_irq_inhibit(true);
353            self.reg.pc = mem.read_word(VEC_IRQ);
354            self.cycles += 19;
355            return true;
356        }
357
358        false
359    }
360
361    // ---- stack helpers ----
362
363    /// Push a byte onto the hardware stack (S).
364    pub(super) fn push_byte_s(&mut self, mem: &mut impl Memory, val: u8) {
365        self.reg.s = self.reg.s.wrapping_sub(1);
366        mem.write(self.reg.s, val);
367    }
368
369    /// Push a 16-bit word onto the hardware stack (S), low byte first
370    /// using write_word() which is ok because stack grows downwards.
371    pub(super) fn push_word_s(&mut self, mem: &mut impl Memory, val: u16) {
372        self.reg.s = self.reg.s.wrapping_sub(2);
373        mem.write_word(self.reg.s, val);
374    }
375
376    /// Pull a byte from the hardware stack (S).
377    pub(super) fn pull_byte_s(&mut self, mem: &mut impl Memory) -> u8 {
378        let val = mem.read(self.reg.s);
379        self.reg.s = self.reg.s.wrapping_add(1);
380        val
381    }
382
383    /// Pull a 16-bit word from the hardware stack (S).
384    pub(super) fn pull_word_s(&mut self, mem: &mut impl Memory) -> u16 {
385        let val = mem.read_word(self.reg.s);
386        self.reg.s = self.reg.s.wrapping_add(2);
387        val
388    }
389
390    /// Push a byte onto the user stack (U).
391    pub(super) fn push_byte_u(&mut self, mem: &mut impl Memory, val: u8) {
392        self.reg.u = self.reg.u.wrapping_sub(1);
393        mem.write(self.reg.u, val);
394    }
395
396    /// Push a 16-bit word onto the user stack (U).
397    pub(super) fn push_word_u(&mut self, mem: &mut impl Memory, val: u16) {
398        self.reg.u = self.reg.u.wrapping_sub(2);
399        mem.write_word(self.reg.u, val);
400    }
401
402    /// Pull a byte from the user stack (U).
403    pub(super) fn pull_byte_u(&mut self, mem: &mut impl Memory) -> u8 {
404        let val = mem.read(self.reg.u);
405        self.reg.u = self.reg.u.wrapping_add(1);
406        val
407    }
408
409    /// Pull a 16-bit word from the user stack (U).
410    pub(super) fn pull_word_u(&mut self, mem: &mut impl Memory) -> u16 {
411        let val = mem.read_word(self.reg.u);
412        self.reg.u = self.reg.u.wrapping_add(2);
413        val
414    }
415
416    /// Push the entire register state onto S (used by NMI, IRQ, SWI).
417    /// Order: CC, A, B, DP, X, Y, U, PC (PC pushed first = highest address).
418    pub(super) fn push_entire_state(&mut self, mem: &mut impl Memory) {
419        self.push_word_s(mem, self.reg.pc);
420        self.push_word_s(mem, self.reg.u);
421        self.push_word_s(mem, self.reg.y);
422        self.push_word_s(mem, self.reg.x);
423        self.push_byte_s(mem, self.reg.dp);
424        self.push_byte_s(mem, self.reg.b());
425        self.push_byte_s(mem, self.reg.a());
426        self.push_byte_s(mem, self.reg.cc.to_byte());
427    }
428
429    // ---- instruction fetch helpers ----
430
431    /// Fetch a byte from [PC] and advance PC.
432    pub(super) fn fetch_byte(&mut self, mem: &mut impl Memory) -> u8 {
433        let val = mem.read(self.reg.pc);
434        self.reg.pc = self.reg.pc.wrapping_add(1);
435        val
436    }
437
438    /// Fetch a big-endian 16-bit word from [PC] and advance PC by 2.
439    pub(super) fn fetch_word(&mut self, mem: &mut impl Memory) -> u16 {
440        let val = mem.read_word(self.reg.pc);
441        self.reg.pc = self.reg.pc.wrapping_add(2);
442        val
443    }
444
445    // ---- addressing mode helpers ----
446
447    /// Direct addressing: DP:fetch_byte → effective address.
448    pub(super) fn addr_direct(&mut self, mem: &mut impl Memory) -> u16 {
449        let lo = self.fetch_byte(mem) as u16;
450        ((self.reg.dp as u16) << 8) | lo
451    }
452
453    /// Extended addressing: fetch 16-bit absolute address.
454    pub(super) fn addr_extended(&mut self, mem: &mut impl Memory) -> u16 {
455        self.fetch_word(mem)
456    }
457
458    /// Indexed addressing: decode post-byte and return (effective_address, extra_cycles).
459    pub(super) fn addr_indexed(&mut self, mem: &mut impl Memory) -> (u16, u8) {
460        crate::addressing::indexed(self, mem)
461    }
462
463    /// Relative 8-bit: signed offset from current PC.
464    pub(super) fn addr_relative8(&mut self, mem: &mut impl Memory) -> u16 {
465        let offset = self.fetch_byte(mem) as i8 as i16 as u16;
466        self.reg.pc.wrapping_add(offset)
467    }
468
469    /// Relative 16-bit: signed offset from current PC.
470    pub(super) fn addr_relative16(&mut self, mem: &mut impl Memory) -> u16 {
471        let offset = self.fetch_word(mem);
472        self.reg.pc.wrapping_add(offset)
473    }
474
475    /// Arm the NMI (called when S is first written to).
476    pub(super) fn arm_nmi(&mut self) {
477        self.nmi_armed = true;
478    }
479}
480
481impl Default for Cpu {
482    fn default() -> Self {
483        Self::new()
484    }
485}
486
487// ---------------------------------------------------------------------------
488// RegistersMut — RAII guard for mutable register access
489// ---------------------------------------------------------------------------
490
491/// RAII guard returned by [`Cpu::registers_mut`].
492///
493/// Dereferences to [`Registers`], giving full read/write access to all
494/// programmer-visible registers. On drop the guard arms the NMI if the
495/// hardware stack pointer (S) changed during the guard's lifetime.
496pub struct RegistersMut<'a> {
497    cpu: &'a mut Cpu,
498    prev_s: u16,
499}
500
501impl std::ops::Deref for RegistersMut<'_> {
502    type Target = Registers;
503    fn deref(&self) -> &Registers {
504        &self.cpu.reg
505    }
506}
507
508impl std::ops::DerefMut for RegistersMut<'_> {
509    fn deref_mut(&mut self) -> &mut Registers {
510        &mut self.cpu.reg
511    }
512}
513
514impl Drop for RegistersMut<'_> {
515    fn drop(&mut self) {
516        if self.cpu.reg.s != self.prev_s {
517            self.cpu.nmi_armed = true;
518        }
519    }
520}
521
522impl fmt::Debug for Cpu {
523    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
524        write!(f, "{} cyc={}", self.reg, self.cycles)
525    }
526}
527
528use std::fmt;