Skip to main content

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}