rustysnes_cart/coproc/
dsp1.rs1#![allow(clippy::doc_markdown)]
22
23use alloc::boxed::Box;
24
25use rustysnes_savestate::{SaveReader, SaveStateError, SaveWriter};
26
27use crate::board::{Board, Coprocessor, MappedAddr};
28use crate::coproc::upd77c25::{Revision, Upd77c25};
29use crate::header::MapMode;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33enum DspWindow {
34 HiRom,
36 LoRomSmall,
38 LoRomLarge,
40}
41
42impl DspWindow {
43 const fn select(map_mode: MapMode, rom_len: usize) -> Self {
45 match map_mode {
46 MapMode::HiRom | MapMode::ExHiRom => Self::HiRom,
47 MapMode::LoRom | MapMode::ExLoRom if rom_len > 0x10_0000 => Self::LoRomLarge,
48 MapMode::LoRom | MapMode::ExLoRom => Self::LoRomSmall,
49 }
50 }
51
52 fn classify(self, addr24: u32) -> Option<bool> {
55 let bank = (addr24 >> 16) & 0x7F;
56 let addr = addr24 & 0xFFFF;
57 match self {
58 Self::HiRom => ((0x00..=0x1F).contains(&bank) && (0x6000..=0x7FFF).contains(&addr))
59 .then_some(addr & 0x1000 != 0),
60 Self::LoRomSmall => {
61 ((0x30..=0x3F).contains(&bank) && addr >= 0x8000).then_some(addr & 0x4000 != 0)
62 }
63 Self::LoRomLarge => {
64 ((0x60..=0x6F).contains(&bank) && addr < 0x8000).then_some(addr & 0x4000 != 0)
65 }
66 }
67 }
68}
69
70pub struct Dsp1Board {
72 inner: Box<dyn Board>,
73 dsp: Upd77c25,
74 window: DspWindow,
75 name: &'static str,
76}
77
78impl core::fmt::Debug for Dsp1Board {
79 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
80 f.debug_struct("Dsp1Board")
81 .field("name", &self.name)
82 .field("inner", &self.inner.name())
83 .field("window", &self.window)
84 .field("firmware_loaded", &self.dsp.firmware_loaded())
85 .finish()
86 }
87}
88
89impl Dsp1Board {
90 #[must_use]
94 pub fn new(inner: Box<dyn Board>, map_mode: MapMode, rom_len: usize) -> Self {
95 let window = DspWindow::select(map_mode, rom_len);
96 let name = match window {
97 DspWindow::HiRom => "HiROM+DSP-1",
98 DspWindow::LoRomSmall | DspWindow::LoRomLarge => "LoROM+DSP-1",
99 };
100 Self {
101 inner,
102 dsp: Upd77c25::new(Revision::Upd7725),
103 window,
104 name,
105 }
106 }
107
108 #[must_use]
110 pub const fn firmware_loaded(&self) -> bool {
111 self.dsp.firmware_loaded()
112 }
113}
114
115impl Board for Dsp1Board {
116 fn name(&self) -> &'static str {
117 self.name
118 }
119
120 fn coprocessor(&self) -> Coprocessor {
121 Coprocessor::Dsp
122 }
123
124 fn map(&self, addr24: u32) -> MappedAddr {
125 if self.window.classify(addr24).is_some() {
126 MappedAddr::Coprocessor
127 } else {
128 self.inner.map(addr24)
129 }
130 }
131
132 fn read24(&mut self, addr24: u32) -> u8 {
133 match self.window.classify(addr24) {
134 Some(true) => self.dsp.read_sr(),
135 Some(false) => self.dsp.read_dr(),
136 None => self.inner.read24(addr24),
137 }
138 }
139
140 fn write24(&mut self, addr24: u32, val: u8) {
141 match self.window.classify(addr24) {
142 Some(true) => self.dsp.write_sr(val),
143 Some(false) => self.dsp.write_dr(val),
144 None => self.inner.write24(addr24, val),
145 }
146 }
147
148 fn rom(&self) -> &[u8] {
149 self.inner.rom()
150 }
151
152 fn sram(&self) -> &[u8] {
153 self.inner.sram()
154 }
155
156 fn sram_mut(&mut self) -> &mut [u8] {
157 self.inner.sram_mut()
158 }
159
160 fn load_firmware(&mut self, bytes: &[u8]) -> bool {
161 self.dsp.load_firmware(bytes)
162 }
163
164 fn coprocessor_host_accesses(&self) -> u64 {
165 self.dsp.host_accesses()
166 }
167
168 fn save_state(&self, w: &mut SaveWriter) {
171 self.dsp.save_state(w);
172 self.inner.save_state(w);
173 }
174
175 fn load_state(&mut self, r: &mut SaveReader) -> Result<(), SaveStateError> {
176 self.dsp.load_state(r)?;
177 self.inner.load_state(r)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::board::LoRom;
185 use alloc::vec;
186
187 fn dsp_lorom_small() -> Dsp1Board {
188 let inner = Box::new(LoRom::new(
189 vec![0u8; 0x8_0000].into_boxed_slice(),
190 vec![].into_boxed_slice(),
191 ));
192 Dsp1Board::new(inner, MapMode::LoRom, 0x8_0000)
193 }
194
195 #[test]
196 fn window_split_lorom_small() {
197 let w = DspWindow::LoRomSmall;
198 assert_eq!(w.classify(0x30_8000), Some(false)); assert_eq!(w.classify(0x30_C000), Some(true)); assert_eq!(w.classify(0xB0_8000), Some(false)); assert_eq!(w.classify(0x00_8000), None); }
203
204 #[test]
205 fn window_split_hirom_and_large() {
206 assert_eq!(DspWindow::HiRom.classify(0x00_6000), Some(false));
207 assert_eq!(DspWindow::HiRom.classify(0x00_7000), Some(true));
208 assert_eq!(DspWindow::LoRomLarge.classify(0x60_0000), Some(false));
209 assert_eq!(DspWindow::LoRomLarge.classify(0x60_4000), Some(true));
210 }
211
212 #[test]
213 fn inert_without_firmware() {
214 let mut b = dsp_lorom_small();
215 assert!(!b.firmware_loaded());
216 assert_eq!(b.read24(0x30_C000), 0);
218 b.write24(0x30_8000, 0x42);
219 assert_eq!(b.read24(0x30_8000), 0);
220 }
221
222 #[test]
223 fn rejects_short_firmware() {
224 let mut b = dsp_lorom_small();
225 assert!(!b.load_firmware(&[0u8; 16]));
226 assert!(!b.firmware_loaded());
227 }
228
229 #[test]
230 fn engine_state_round_trips_through_save_state() {
231 let mut b = dsp_lorom_small();
232 assert!(b.load_firmware(&[0u8; 8192]));
236 b.dsp.write_dp(0x10, 0xAB);
237 b.dsp.write_dp(0x11, 0xCD);
238 let before = b.dsp.data_ram_word(0x10 >> 1);
239 assert_ne!(
240 before, 0,
241 "the write above should have produced non-zero data-RAM content"
242 );
243
244 let mut w = SaveWriter::new();
245 b.save_state(&mut w);
246 let bytes = w.into_bytes();
247
248 let mut fresh = dsp_lorom_small();
249 assert!(fresh.load_firmware(&[0u8; 8192]));
250 let mut r = SaveReader::new(&bytes);
251 fresh.load_state(&mut r).unwrap();
252
253 assert_eq!(fresh.dsp.data_ram_word(0x10 >> 1), before);
254 assert_eq!(r.remaining(), 0);
255 }
256}