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;