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