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::bus::Memory;
16use crate::registers::Registers;
17
18mod opcodes;
19
20pub use opcodes::instruction_cycles;
21
22// ---------------------------------------------------------------------------
23// Interrupt vector addresses
24// ---------------------------------------------------------------------------
25
26pub const VEC_RESET: u16 = 0xFFFE;
27pub const VEC_NMI: u16 = 0xFFFC;
28pub const VEC_SWI: u16 = 0xFFFA;
29pub const VEC_IRQ: u16 = 0xFFF8;
30pub const VEC_FIRQ: u16 = 0xFFF6;
31pub const VEC_SWI2: u16 = 0xFFF4;
32pub const VEC_SWI3: u16 = 0xFFF2;
33
34// ---------------------------------------------------------------------------
35// CPU state
36// ---------------------------------------------------------------------------
37
38/// Motorola 6809 CPU emulator.
39pub struct Cpu {
40    /// Programmer-visible registers.
41    pub reg: Registers,
42    /// Total elapsed cycles since reset.
43    pub cycles: u64,
44    /// CPU is halted (hit illegal opcode or RESET instruction).
45    pub halted: bool,
46    /// CPU encountered an illegal opcode (invalid in current state).
47    pub illegal: bool,
48
49    // ---- interrupt state ----
50    /// NMI is armed (becomes true after first write to S).
51    nmi_armed: bool,
52    /// NMI pending (edge-triggered).
53    nmi_pending: bool,
54    /// FIRQ line asserted (level-triggered).
55    firq_line: bool,
56    /// IRQ line asserted (level-triggered).
57    irq_line: bool,
58    /// CWAI: entire state already pushed, waiting for interrupt.
59    cwai: bool,
60    /// SYNC: waiting for any interrupt edge.
61    sync: bool,
62}
63
64impl Cpu {
65    /// Create a new CPU with all state zeroed.
66    pub fn new() -> Self {
67        Self {
68            reg: Registers::new(),
69            cycles: 0,
70            halted: false,
71            illegal: false,
72            nmi_armed: false,
73            nmi_pending: false,
74            firq_line: false,
75            irq_line: false,
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.nmi_pending = false;
92        self.firq_line = false;
93        self.irq_line = false;
94        self.cwai = false;
95        self.sync = false;
96    }
97
98    /// Assert or de-assert the IRQ line (level-triggered).
99    pub fn set_irq(&mut self, active: bool) {
100        self.irq_line = active;
101    }
102
103    /// Assert or de-assert the FIRQ line (level-triggered).
104    pub fn set_firq(&mut self, active: bool) {
105        self.firq_line = active;
106    }
107
108    /// Trigger an NMI (edge-triggered). Only effective if NMI is armed.
109    pub fn trigger_nmi(&mut self) {
110        if self.nmi_armed {
111            self.nmi_pending = true;
112        }
113    }
114
115    /// Execute a single instruction (or handle a pending interrupt).
116    /// Returns the number of cycles consumed.
117    pub fn step(&mut self, mem: &mut impl Memory) -> u64 {
118        if self.halted {
119            return 1;
120        }
121
122        let start_cycles = self.cycles;
123
124        // Handle SYNC state: wait for any interrupt edge
125        if self.sync {
126            if self.nmi_pending || self.firq_line || self.irq_line {
127                self.sync = false;
128            } else {
129                self.cycles += 1;
130                return 1;
131            }
132        }
133
134        // Check pending interrupts (priority: NMI > FIRQ > IRQ)
135        if self.check_interrupts(mem) {
136            return self.cycles - start_cycles;
137        }
138
139        // Fetch and execute one instruction
140        let opcode = self.fetch_byte(mem);
141        self.execute(mem, opcode);
142
143        self.cycles - start_cycles
144    }
145
146    /// Run until at least `cycle_budget` cycles have been consumed.
147    pub fn run(&mut self, mem: &mut impl Memory, cycle_budget: u64) -> u64 {
148        let start_cycles = self.cycles;
149        let target = self.cycles + cycle_budget;
150        while self.cycles < target && !self.halted {
151            self.step(mem);
152        }
153        self.cycles - start_cycles
154    }
155
156    // ---- interrupt logic ----
157
158    fn check_interrupts(&mut self, mem: &mut impl Memory) -> bool {
159        // NMI (edge-triggered, highest priority)
160        if self.nmi_pending {
161            self.nmi_pending = false;
162            if !self.cwai {
163                self.reg.cc.set_entire(true);
164                self.push_entire_state(mem);
165            }
166            self.cwai = false;
167            self.reg.cc.set_irq_inhibit(true);
168            self.reg.cc.set_firq_inhibit(true);
169            self.reg.pc = mem.read_word(VEC_NMI);
170            self.cycles += 19;
171            return true;
172        }
173
174        // FIRQ (level-triggered)
175        if self.firq_line && !self.reg.cc.firq_inhibit() {
176            if !self.cwai {
177                self.reg.cc.set_entire(false);
178                self.push_word_s(mem, self.reg.pc);
179                self.push_byte_s(mem, self.reg.cc.to_byte());
180            }
181            self.cwai = false;
182            self.reg.cc.set_irq_inhibit(true);
183            self.reg.cc.set_firq_inhibit(true);
184            self.reg.pc = mem.read_word(VEC_FIRQ);
185            self.cycles += 10;
186            return true;
187        }
188
189        // IRQ (level-triggered)
190        if self.irq_line && !self.reg.cc.irq_inhibit() {
191            if !self.cwai {
192                self.reg.cc.set_entire(true);
193                self.push_entire_state(mem);
194            }
195            self.cwai = false;
196            self.reg.cc.set_irq_inhibit(true);
197            self.reg.pc = mem.read_word(VEC_IRQ);
198            self.cycles += 19;
199            return true;
200        }
201
202        false
203    }
204
205    // ---- stack helpers ----
206
207    /// Push a byte onto the hardware stack (S).
208    pub(super) fn push_byte_s(&mut self, mem: &mut impl Memory, val: u8) {
209        self.reg.s = self.reg.s.wrapping_sub(1);
210        mem.write(self.reg.s, val);
211    }
212
213    /// Push a 16-bit word onto the hardware stack (S), low byte first.
214    pub(super) fn push_word_s(&mut self, mem: &mut impl Memory, val: u16) {
215        self.push_byte_s(mem, val as u8); // low byte pushed first (ends at higher address)
216        self.push_byte_s(mem, (val >> 8) as u8);
217    }
218
219    /// Pull a byte from the hardware stack (S).
220    pub(super) fn pull_byte_s(&mut self, mem: &mut impl Memory) -> u8 {
221        let val = mem.read(self.reg.s);
222        self.reg.s = self.reg.s.wrapping_add(1);
223        val
224    }
225
226    /// Pull a 16-bit word from the hardware stack (S).
227    pub(super) fn pull_word_s(&mut self, mem: &mut impl Memory) -> u16 {
228        let hi = self.pull_byte_s(mem) as u16;
229        let lo = self.pull_byte_s(mem) as u16;
230        (hi << 8) | lo
231    }
232
233    /// Push a byte onto the user stack (U).
234    pub(super) fn push_byte_u(&mut self, mem: &mut impl Memory, val: u8) {
235        self.reg.u = self.reg.u.wrapping_sub(1);
236        mem.write(self.reg.u, val);
237    }
238
239    /// Push a 16-bit word onto the user stack (U).
240    pub(super) fn push_word_u(&mut self, mem: &mut impl Memory, val: u16) {
241        self.push_byte_u(mem, val as u8);
242        self.push_byte_u(mem, (val >> 8) as u8);
243    }
244
245    /// Pull a byte from the user stack (U).
246    pub(super) fn pull_byte_u(&mut self, mem: &mut impl Memory) -> u8 {
247        let val = mem.read(self.reg.u);
248        self.reg.u = self.reg.u.wrapping_add(1);
249        val
250    }
251
252    /// Pull a 16-bit word from the user stack (U).
253    pub(super) fn pull_word_u(&mut self, mem: &mut impl Memory) -> u16 {
254        let hi = self.pull_byte_u(mem) as u16;
255        let lo = self.pull_byte_u(mem) as u16;
256        (hi << 8) | lo
257    }
258
259    /// Push the entire register state onto S (used by NMI, IRQ, SWI).
260    /// Order: CC, A, B, DP, X, Y, U, PC (PC pushed first = highest address).
261    pub(super) fn push_entire_state(&mut self, mem: &mut impl Memory) {
262        self.push_word_s(mem, self.reg.pc);
263        self.push_word_s(mem, self.reg.u);
264        self.push_word_s(mem, self.reg.y);
265        self.push_word_s(mem, self.reg.x);
266        self.push_byte_s(mem, self.reg.dp);
267        self.push_byte_s(mem, self.reg.b());
268        self.push_byte_s(mem, self.reg.a());
269        self.push_byte_s(mem, self.reg.cc.to_byte());
270    }
271
272    // ---- instruction fetch helpers ----
273
274    /// Fetch a byte from [PC] and advance PC.
275    pub(super) fn fetch_byte(&mut self, mem: &mut impl Memory) -> u8 {
276        let val = mem.read(self.reg.pc);
277        self.reg.pc = self.reg.pc.wrapping_add(1);
278        val
279    }
280
281    /// Fetch a big-endian 16-bit word from [PC] and advance PC by 2.
282    pub(super) fn fetch_word(&mut self, mem: &mut impl Memory) -> u16 {
283        let hi = self.fetch_byte(mem) as u16;
284        let lo = self.fetch_byte(mem) as u16;
285        (hi << 8) | lo
286    }
287
288    // ---- addressing mode helpers ----
289
290    /// Direct addressing: DP:fetch_byte → effective address.
291    pub(super) fn addr_direct(&mut self, mem: &mut impl Memory) -> u16 {
292        let lo = self.fetch_byte(mem) as u16;
293        ((self.reg.dp as u16) << 8) | lo
294    }
295
296    /// Extended addressing: fetch 16-bit absolute address.
297    pub(super) fn addr_extended(&mut self, mem: &mut impl Memory) -> u16 {
298        self.fetch_word(mem)
299    }
300
301    /// Indexed addressing: decode post-byte and return (effective_address, extra_cycles).
302    pub(super) fn addr_indexed(&mut self, mem: &mut impl Memory) -> (u16, u8) {
303        crate::addressing::indexed(self, mem)
304    }
305
306    /// Relative 8-bit: signed offset from current PC.
307    pub(super) fn addr_relative8(&mut self, mem: &mut impl Memory) -> u16 {
308        let offset = self.fetch_byte(mem) as i8 as i16 as u16;
309        self.reg.pc.wrapping_add(offset)
310    }
311
312    /// Relative 16-bit: signed offset from current PC.
313    pub(super) fn addr_relative16(&mut self, mem: &mut impl Memory) -> u16 {
314        let offset = self.fetch_word(mem);
315        self.reg.pc.wrapping_add(offset)
316    }
317
318    /// Arm the NMI (called when S is first written to).
319    pub(super) fn arm_nmi(&mut self) {
320        self.nmi_armed = true;
321    }
322}
323
324impl Default for Cpu {
325    fn default() -> Self {
326        Self::new()
327    }
328}
329
330impl fmt::Debug for Cpu {
331    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332        write!(f, "{} cyc={}", self.reg, self.cycles)
333    }
334}
335
336use std::fmt;