mc6809_core/peripheral.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 std::fmt;
16use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
17
18/// Interrupt and control signals returned by [`Clocked::tick`].
19///
20/// Each flag corresponds to a physical input pin on the 6809 CPU.
21/// Signals can be combined with `|` and tested with [`contains`](Self::contains).
22/// The default is all signals de-asserted.
23///
24/// # Example
25/// ```
26/// use mc6809_core::BusSignals;
27///
28/// let signals = BusSignals::IRQ | BusSignals::NMI;
29/// assert!(signals.contains(BusSignals::IRQ));
30/// assert!(signals.contains(BusSignals::NMI));
31/// ```
32#[derive(Clone, Copy, Default, PartialEq, Eq)]
33#[must_use]
34pub struct BusSignals(u8);
35
36impl BusSignals {
37 /// NMI pin state (level).
38 pub const NMI: Self = Self(0x01);
39 /// FIRQ line state (active = asserted, level-triggered).
40 pub const FIRQ: Self = Self(0x02);
41 /// IRQ line state (active = asserted, level-triggered).
42 pub const IRQ: Self = Self(0x04);
43 /// RESET pin asserted — the host loop should call [`Cpu::reset`](crate::Cpu::reset).
44 pub const RESET: Self = Self(0x08);
45
46 /// Returns `true` if all bits in `other` are set in `self`.
47 #[inline]
48 pub fn contains(self, other: Self) -> bool {
49 self.0 & other.0 == other.0
50 }
51
52 /// Returns `true` if no signals are asserted.
53 #[inline]
54 pub fn is_empty(self) -> bool {
55 self.0 == 0
56 }
57
58 /// Assert one or more signals (set their bits).
59 #[inline]
60 pub fn insert(&mut self, signals: Self) {
61 *self |= signals;
62 }
63
64 /// De-assert one or more signals (clear their bits).
65 #[inline]
66 pub fn remove(&mut self, signals: Self) {
67 *self &= !signals;
68 }
69}
70
71impl BitOr for BusSignals {
72 type Output = Self;
73 fn bitor(self, rhs: Self) -> Self {
74 Self(self.0 | rhs.0)
75 }
76}
77
78impl BitOrAssign for BusSignals {
79 fn bitor_assign(&mut self, rhs: Self) {
80 self.0 |= rhs.0;
81 }
82}
83
84impl BitAnd for BusSignals {
85 type Output = Self;
86 fn bitand(self, rhs: Self) -> Self {
87 Self(self.0 & rhs.0)
88 }
89}
90
91impl BitAndAssign for BusSignals {
92 fn bitand_assign(&mut self, rhs: Self) {
93 self.0 &= rhs.0;
94 }
95}
96
97impl BitXor for BusSignals {
98 type Output = Self;
99 fn bitxor(self, rhs: Self) -> Self {
100 Self(self.0 ^ rhs.0)
101 }
102}
103
104impl BitXorAssign for BusSignals {
105 fn bitxor_assign(&mut self, rhs: Self) {
106 self.0 ^= rhs.0;
107 }
108}
109
110impl Not for BusSignals {
111 type Output = Self;
112 fn not(self) -> Self {
113 Self(!self.0)
114 }
115}
116
117impl fmt::Debug for BusSignals {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 const FLAGS: &[(&str, BusSignals)] = &[
120 ("NMI", BusSignals::NMI),
121 ("FIRQ", BusSignals::FIRQ),
122 ("IRQ", BusSignals::IRQ),
123 ("RESET", BusSignals::RESET),
124 ];
125 write!(f, "BusSignals(")?;
126 let mut first = true;
127 for (name, flag) in FLAGS {
128 if self.contains(*flag) {
129 if !first {
130 write!(f, " | ")?;
131 }
132 write!(f, "{name}")?;
133 first = false;
134 }
135 }
136 if first {
137 write!(f, "empty")?;
138 }
139 write!(f, ")")
140 }
141}
142
143/// Implement this trait for any peripheral that needs to track CPU cycles and
144/// signal interrupts. The host loop calls [`tick`](Clocked::tick) after each CPU
145/// step (or batch of steps), then feeds the returned [`BusSignals`] into the
146/// CPU via [`Cpu::apply_signals`](crate::Cpu::apply_signals).
147///
148/// Each call to `tick` should return the **current pin state** — the full set of
149/// signals that are asserted right now, not just what changed. The host loop
150/// uses [`BusSignals`] equality to detect changes and only calls `apply_signals`
151/// when something actually transitions, keeping the hot path to a single
152/// comparison.
153///
154/// The trait is intentionally thin so that implementations can be layered.
155/// A debug or tracing system can wrap an inner `Clocked` implementation, forwarding
156/// `tick()` calls while intercepting or logging signals — without requiring
157/// changes to the wrapped implementation or the host loop.
158///
159/// When multiple peripherals share a bus, OR their signals together:
160/// ```ignore
161/// let mut signals = BusSignals::default();
162/// for p in &mut peripherals {
163/// signals |= p.tick(cycles);
164/// }
165/// ```
166///
167/// ## Recommended host loop
168///
169/// ```ignore
170/// use mc6809_core::{BusSignals, Cpu, Memory};
171///
172/// let mut prev_signals = BusSignals::default();
173///
174/// loop {
175/// let cycles = cpu.step(&mut mem);
176/// let signals = peripheral.tick(cycles);
177///
178/// // RESET is handled before apply_signals so a held-RESET pin keeps the
179/// // CPU quiescent and is not confused with a regular interrupt transition.
180/// if signals.contains(BusSignals::RESET) {
181/// cpu.reset(&mut mem);
182/// prev_signals = BusSignals::default();
183/// continue;
184/// }
185///
186/// // Only call into the CPU when something actually changed on the bus.
187/// if signals != prev_signals {
188/// cpu.apply_signals(signals, prev_signals);
189/// prev_signals = signals;
190/// }
191///
192/// if cpu.halted() { break; }
193/// }
194/// ```
195///
196/// The `signals != prev_signals` guard means `apply_signals` is called at most
197/// once per transition (rising/falling edge), not every cycle. Signals that
198/// remain asserted (e.g. a peripheral holding IRQ) stay latched inside the CPU
199/// via `int_lines` without any per-cycle overhead.
200///
201/// The default implementation is a no-op returning all signals inactive,
202/// suitable for simple test systems with no peripherals.
203pub trait Clocked {
204 fn tick(&mut self, _cycles: u64) -> BusSignals {
205 BusSignals::default()
206 }
207}