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