bus.rsannotatedbus.rssource176 lines · 5.1 KB · raw
1//! SH-2 bus interface
2//!
3//! Implementations can assume that all addresses are masked to the lowest 29 bits (`address & 0x1FFFFFFF`)
4//! because the highest 3 bits are only used internally
5
6use crate::debug::Sh2Debugger;
7use crate::disassemble;
8use crate::disassemble::DisassembledInstruction;
9use crate::instructions::OpcodeTable;
10use bincode::{Decode, Encode};
11use std::fmt::{Display, Formatter};
12
13#[derive(Debug, Clone, Copy, Encode, Decode)]
14pub enum AccessContext {
15    Fetch,
16    Data { pc: u32, opcode: u16 },
17    InterruptVector,
18    Dma { channel: usize },
19}
20
21impl Display for AccessContext {
22    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
23        match self {
24            Self::Fetch => {
25                write!(f, "Opcode fetch")
26            }
27            &Self::Data { pc, opcode } => {
28                let mut instruction = DisassembledInstruction::new();
29                disassemble::disassemble_into(pc, opcode, &mut instruction);
30                write!(f, "PC={pc:08X}, opcode={opcode:04X}, instruction='{}'", instruction.text)
31            }
32            Self::InterruptVector => write!(f, "Interrupt vector fetch"),
33            Self::Dma { channel } => write!(f, "DMA channel {channel}"),
34        }
35    }
36}
37
38pub struct OpSize;
39
40// For cases where size can't be passed as a const generic (e.g. dyn-compatible trait methods)
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum OpSizeEnum {
43    Byte,
44    Word,
45    Longword,
46}
47
48impl OpSize {
49    pub const BYTE: u8 = 0;
50    pub const WORD: u8 = 1;
51    pub const LONGWORD: u8 = 2;
52
53    /// # Panics
54    ///
55    /// Panics if `SIZE` is not a valid `OpSize` value
56    #[must_use]
57    #[inline(always)]
58    pub fn display<const SIZE: u8>() -> &'static str {
59        match SIZE {
60            Self::BYTE => "byte",
61            Self::WORD => "word",
62            Self::LONGWORD => "longword",
63            _ => panic!("invalid size {SIZE}"),
64        }
65    }
66
67    /// # Panics
68    ///
69    /// Panics if `SIZE` is not a valid `OpSize` value
70    #[must_use]
71    #[inline(always)]
72    pub fn mask<const SIZE: u8>() -> u32 {
73        match SIZE {
74            Self::BYTE => 0xFF,
75            Self::WORD => 0xFFFF,
76            Self::LONGWORD => 0xFFFFFFFF,
77            _ => panic!("invalid size {SIZE}"),
78        }
79    }
80
81    /// # Panics
82    ///
83    /// Panics if `SIZE` is not a valid `OpSize` value
84    #[must_use]
85    #[inline(always)]
86    pub fn enum_value<const SIZE: u8>() -> OpSizeEnum {
87        match SIZE {
88            Self::BYTE => OpSizeEnum::Byte,
89            Self::WORD => OpSizeEnum::Word,
90            Self::LONGWORD => OpSizeEnum::Longword,
91            _ => panic!("invalid size {SIZE}"),
92        }
93    }
94}
95
96pub trait BusInterface: Sized {
97    /// Debug view type; if not implemented, set to [`crate::debug::DummySh2Debugger`]
98    type DebugView<'a>: Sh2Debugger
99    where
100        Self: 'a;
101
102    fn read<const SIZE: u8>(&mut self, address: u32, ctx: AccessContext) -> u32;
103
104    fn read_byte(&mut self, address: u32, ctx: AccessContext) -> u8 {
105        self.read::<{ OpSize::BYTE }>(address, ctx) as u8
106    }
107
108    fn read_word(&mut self, address: u32, ctx: AccessContext) -> u16 {
109        self.read::<{ OpSize::WORD }>(address, ctx) as u16
110    }
111
112    fn read_longword(&mut self, address: u32, ctx: AccessContext) -> u32 {
113        self.read::<{ OpSize::LONGWORD }>(address, ctx)
114    }
115
116    fn read_cache_line(&mut self, address: u32, ctx: AccessContext) -> [u16; 8];
117
118    fn write<const SIZE: u8>(&mut self, address: u32, value: u32, ctx: AccessContext);
119
120    fn write_byte(&mut self, address: u32, value: u8, ctx: AccessContext) {
121        self.write::<{ OpSize::BYTE }>(address, value.into(), ctx);
122    }
123
124    fn write_word(&mut self, address: u32, value: u16, ctx: AccessContext) {
125        self.write::<{ OpSize::WORD }>(address, value.into(), ctx);
126    }
127
128    fn write_longword(&mut self, address: u32, value: u32, ctx: AccessContext) {
129        self.write::<{ OpSize::LONGWORD }>(address, value, ctx);
130    }
131
132    /// The CPU will halt while this is `true` and then reset when it changes from `true` to `false`
133    fn reset(&self) -> bool;
134
135    /// Current external interrupt level from 0 to 15; 0 indicates no interrupt
136    fn interrupt_level(&self) -> u8;
137
138    /// DREQ line for DMA channel 0
139    fn dma_request_0(&self) -> bool;
140
141    /// DREQ line for DMA channel 1
142    fn dma_request_1(&self) -> bool;
143
144    fn acknowledge_dreq_1(&mut self);
145
146    /// Receive a byte from the serial interface, if any
147    fn serial_rx(&mut self) -> Option<u8>;
148
149    /// Transmit a byte to the serial interface
150    fn serial_tx(&mut self, value: u8);
151
152    fn increment_cycle_counter(&mut self, cycles: u64);
153
154    fn should_stop_execution(&self) -> bool;
155
156    fn debug_view(&mut self) -> Option<Self::DebugView<'_>> {
157        None
158    }
159
160    fn opcode_table<'a>() -> &'a OpcodeTable<Self>;
161}
162
163#[macro_export]
164macro_rules! impl_sh2_opcode_table {
165    ($bus:ident) => {
166        #[inline]
167        fn opcode_table<'a>() -> &'a $crate::OpcodeTable<$bus> {
168            static TABLE: ::std::sync::LazyLock<$crate::OpcodeTable<$bus>> =
169                ::std::sync::LazyLock::new(|| $crate::OpcodeTable::new());
170
171            &*TABLE
172        }
173    };
174}
175
176pub use impl_sh2_opcode_table;