rustysnes_cart/coproc/
cx4.rs1#![allow(clippy::doc_markdown)]
25
26use alloc::boxed::Box;
27
28use rustysnes_savestate::{SaveReader, SaveStateError, SaveWriter};
29
30use crate::board::{Board, Coprocessor, MappedAddr};
31use crate::coproc::hg51b::{Hg51b, Hg51bBus};
32
33struct Cx4Mem<'a> {
38 inner: &'a mut dyn Board,
39}
40
41impl Hg51bBus for Cx4Mem<'_> {
42 fn is_rom(&self, address: u32) -> bool {
43 matches!(self.inner.map(address), MappedAddr::Rom(_))
44 }
45
46 fn is_ram(&self, address: u32) -> bool {
47 matches!(self.inner.map(address), MappedAddr::Sram(_))
48 }
49
50 fn read(&mut self, address: u32) -> u8 {
51 self.inner.read24(address)
52 }
53
54 fn write(&mut self, address: u32, data: u8) {
55 self.inner.write24(address, data);
56 }
57}
58
59enum Hit {
61 Dram(u32),
63 Io(u32),
65}
66
67const fn classify(addr24: u32) -> Option<Hit> {
68 let a = addr24 & 0xFF_FFFF;
73 if (a & 0x40_EC00) == 0x00_6C00 {
74 return Some(Hit::Io(0x7C00 | (a & 0x3FF)));
75 }
76 if (a & 0x40_E000) == 0x00_6000 && (a & 0x00_0C00) != 0x00_0C00 {
77 return Some(Hit::Dram(a & 0xFFF));
78 }
79 None
80}
81
82pub struct Cx4Board {
84 inner: Box<dyn Board>,
85 hg51b: Hg51b,
86}
87
88impl core::fmt::Debug for Cx4Board {
89 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
90 f.debug_struct("Cx4Board")
91 .field("inner", &self.inner.name())
92 .field("hg51b", &self.hg51b)
93 .finish()
94 }
95}
96
97impl Cx4Board {
98 #[must_use]
102 pub fn new(inner: Box<dyn Board>) -> Self {
103 Self {
104 inner,
105 hg51b: Hg51b::new(),
106 }
107 }
108}
109
110impl Board for Cx4Board {
111 fn name(&self) -> &'static str {
112 "LoROM+CX4"
113 }
114
115 fn coprocessor(&self) -> Coprocessor {
116 Coprocessor::Cx4
117 }
118
119 fn map(&self, addr24: u32) -> MappedAddr {
120 if classify(addr24).is_some() {
121 MappedAddr::Coprocessor
122 } else {
123 self.inner.map(addr24)
124 }
125 }
126
127 fn read24(&mut self, addr24: u32) -> u8 {
128 match classify(addr24) {
129 Some(Hit::Dram(a)) => self.hg51b.read_dram(a),
130 Some(Hit::Io(a)) => self.hg51b.read_io(a),
131 None => self.inner.read24(addr24),
132 }
133 }
134
135 fn write24(&mut self, addr24: u32, val: u8) {
136 match classify(addr24) {
137 Some(Hit::Dram(a)) => self.hg51b.write_dram(a, val),
138 Some(Hit::Io(a)) => {
139 let mut mem = Cx4Mem {
140 inner: &mut *self.inner,
141 };
142 self.hg51b.write_io(a, val, &mut mem);
143 }
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.hg51b.load_data_rom(bytes)
162 }
163
164 fn firmware_hint(&self) -> Option<&'static str> {
165 Some("cx4.rom")
166 }
167
168 fn irq_pending(&self) -> bool {
169 self.hg51b.irq_pending()
170 }
171
172 fn coprocessor_host_accesses(&self) -> u64 {
173 self.hg51b.instructions_run()
174 }
175
176 fn save_state(&self, w: &mut SaveWriter) {
177 self.hg51b.save_state(w);
178 self.inner.save_state(w);
179 }
180
181 fn load_state(&mut self, r: &mut SaveReader) -> Result<(), SaveStateError> {
182 self.hg51b.load_state(r)?;
183 self.inner.load_state(r)
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::board::LoRom;
191 use alloc::vec;
192
193 fn board() -> Cx4Board {
194 let inner = Box::new(LoRom::new(
195 vec![0u8; 0x8_0000].into_boxed_slice(),
196 vec![].into_boxed_slice(),
197 ));
198 Cx4Board::new(inner)
199 }
200
201 #[test]
202 fn window_classify() {
203 assert!(matches!(classify(0x00_6000), Some(Hit::Dram(0))));
204 assert!(matches!(classify(0x00_6BFF), Some(Hit::Dram(0xBFF))));
205 assert!(classify(0x00_6C00).is_some()); assert!(matches!(classify(0x00_6C00), Some(Hit::Io(0x7C00))));
207 assert!(matches!(classify(0x00_7FEF), Some(Hit::Io(0x7FEF))));
208 assert!(classify(0x00_8000).is_none()); assert!(matches!(classify(0x80_6000), Some(Hit::Dram(0))));
210 }
211
212 #[test]
213 fn inert_without_data_rom() {
214 let mut b = board();
215 assert!(!b.hg51b.data_rom_loaded());
216 b.write24(0x00_7C4F, 0x00);
218 assert!(b.hg51b.data_rom_loaded().eq(&false));
219 }
220
221 #[test]
222 fn dram_roundtrip() {
223 let mut b = board();
224 b.write24(0x00_6000, 0x42);
225 assert_eq!(b.read24(0x00_6000), 0x42);
226 }
227
228 #[test]
229 fn engine_state_round_trips_through_save_state() {
230 let mut b = board();
231 b.write24(0x00_6000, 0x42);
232 b.write24(0x00_7F4D, 0x12); let mut w = SaveWriter::new();
235 b.save_state(&mut w);
236 let bytes = w.into_bytes();
237
238 let mut fresh = board();
239 let mut r = SaveReader::new(&bytes);
240 fresh.load_state(&mut r).unwrap();
241
242 assert_eq!(fresh.read24(0x00_6000), 0x42);
243 assert_eq!(fresh.read24(0x00_7F4D), 0x12);
244 assert_eq!(r.remaining(), 0);
245 }
246}