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;