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;