1#![allow(
38 clippy::doc_markdown,
39 clippy::similar_names,
40 clippy::cast_possible_truncation
41)]
42
43use alloc::boxed::Box;
44use alloc::vec;
45
46use rustysnes_savestate::{SaveReader, SaveStateError, SaveWriter};
47
48use crate::board::{Board, Coprocessor, MappedAddr};
49use crate::coproc::epsonrtc::EpsonRtc;
50use crate::header::MapMode;
51
52mod decompressor;
53use decompressor::Decompressor;
54
55pub struct Spc7110Board {
57 prom: Box<[u8]>,
58 drom: Box<[u8]>,
59 ram: Box<[u8]>,
60
61 r4801: u8,
63 r4802: u8,
64 r4803: u8,
65 r4804: u8,
66 r4805: u8,
67 r4806: u8,
68 r4807: u8,
69 r4809: u8,
70 r480a: u8,
71 r480b: u8,
72 r480c: u8,
73 dcu_mode: u8,
74 dcu_address: u32,
75 dcu_offset: u32,
76 dcu_tile: [u8; 32],
77 decompressor: Decompressor,
78 dcu_pending: bool,
88 mul_pending: bool,
90 div_pending: bool,
92
93 r4810: u8,
95 r4811: u8,
96 r4812: u8,
97 r4813: u8,
98 r4814: u8,
99 r4815: u8,
100 r4816: u8,
101 r4817: u8,
102 r4818: u8,
103
104 r4820: u8,
106 r4821: u8,
107 r4822: u8,
108 r4823: u8,
109 r4824: u8,
110 r4825: u8,
111 r4826: u8,
112 r4827: u8,
113 r4828: u8,
114 r4829: u8,
115 r482a: u8,
116 r482b: u8,
117 r482c: u8,
118 r482d: u8,
119 r482e: u8,
120 r482f: u8,
121
122 r4830: u8,
124 r4831: u8,
125 r4832: u8,
126 r4833: u8,
127 r4834: u8,
128
129 rtc: EpsonRtc,
133}
134
135impl core::fmt::Debug for Spc7110Board {
136 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
137 f.debug_struct("Spc7110Board")
138 .field("prom_len", &self.prom.len())
139 .field("drom_len", &self.drom.len())
140 .finish_non_exhaustive()
141 }
142}
143
144impl Spc7110Board {
145 #[must_use]
147 pub fn new(prom: Box<[u8]>, drom: Box<[u8]>, sram_size: usize) -> Self {
148 Self {
149 prom,
150 drom,
151 ram: vec![0u8; sram_size].into_boxed_slice(),
152 r4801: 0,
153 r4802: 0,
154 r4803: 0,
155 r4804: 0,
156 r4805: 0,
157 r4806: 0,
158 r4807: 0,
159 r4809: 0,
160 r480a: 0,
161 r480b: 0,
162 r480c: 0,
163 dcu_mode: 0,
164 dcu_address: 0,
165 dcu_offset: 0,
166 dcu_tile: [0; 32],
167 decompressor: Decompressor::new(),
168 dcu_pending: false,
169 mul_pending: false,
170 div_pending: false,
171 r4810: 0,
172 r4811: 0,
173 r4812: 0,
174 r4813: 0,
175 r4814: 0,
176 r4815: 0,
177 r4816: 0,
178 r4817: 0,
179 r4818: 0,
180 r4820: 0,
181 r4821: 0,
182 r4822: 0,
183 r4823: 0,
184 r4824: 0,
185 r4825: 0,
186 r4826: 0,
187 r4827: 0,
188 r4828: 0,
189 r4829: 0,
190 r482a: 0,
191 r482b: 0,
192 r482c: 0,
193 r482d: 0,
194 r482e: 0,
195 r482f: 0,
196 r4830: 0,
197 r4831: 0,
198 r4832: 0x01,
199 r4833: 0x02,
200 r4834: 0,
201 rtc: EpsonRtc::new(),
202 }
203 }
204
205 fn datarom_read(&self, address: u32) -> u8 {
210 let size = 1u32 << (self.r4834 & 3); let mask = 0x0010_0000u32.wrapping_mul(size).wrapping_sub(1);
212 let offset = address & mask;
213 if (self.r4834 & 3) != 3 && (address & 0x40_0000) != 0 {
214 return 0;
215 }
216 self.drom
217 .get(bus_mirror(offset, self.drom.len() as u32) as usize)
218 .copied()
219 .unwrap_or(0)
220 }
221
222 fn data_offset(&self) -> u32 {
223 u32::from(self.r4811) | (u32::from(self.r4812) << 8) | (u32::from(self.r4813) << 16)
224 }
225
226 fn data_adjust(&self) -> u16 {
227 u16::from(self.r4814) | (u16::from(self.r4815) << 8)
228 }
229
230 fn data_stride(&self) -> u16 {
231 u16::from(self.r4816) | (u16::from(self.r4817) << 8)
232 }
233
234 const fn set_data_offset(&mut self, address: u32) {
235 self.r4811 = address as u8;
236 self.r4812 = (address >> 8) as u8;
237 self.r4813 = (address >> 16) as u8;
238 }
239
240 const fn set_data_adjust(&mut self, address: u16) {
241 self.r4814 = address as u8;
242 self.r4815 = (address >> 8) as u8;
243 }
244
245 fn data_port_read(&mut self) {
246 let offset = self.data_offset();
247 let mut adjust = if self.r4818 & 2 != 0 {
248 u32::from(self.data_adjust())
249 } else {
250 0
251 };
252 if self.r4818 & 8 != 0 {
253 adjust = sign_extend16(adjust as u16);
254 }
255 self.r4810 = self.datarom_read(offset.wrapping_add(adjust));
256 }
257
258 fn data_port_increment_4810(&mut self) {
259 let offset = self.data_offset();
260 let mut stride = if self.r4818 & 1 != 0 {
261 u32::from(self.data_stride())
262 } else {
263 1
264 };
265 let mut adjust = u32::from(self.data_adjust());
266 if self.r4818 & 4 != 0 {
267 stride = sign_extend16(stride as u16);
268 }
269 if self.r4818 & 8 != 0 {
270 adjust = sign_extend16(adjust as u16);
271 }
272 if self.r4818 & 16 == 0 {
273 self.set_data_offset(offset.wrapping_add(stride));
274 } else {
275 self.set_data_adjust(adjust.wrapping_add(stride) as u16);
276 }
277 self.data_port_read();
278 }
279
280 fn data_port_increment_seek(&mut self, select: u8) {
281 if self.r4818 >> 5 != select {
282 return;
283 }
284 let offset = self.data_offset();
285 let mut adjust = u32::from(self.data_adjust());
286 if self.r4818 & 8 != 0 {
287 adjust = sign_extend16(adjust as u16);
288 }
289 self.set_data_offset(offset.wrapping_add(adjust));
290 self.data_port_read();
291 }
292
293 fn dcu_load_address(&mut self) {
298 let table =
299 u32::from(self.r4801) | (u32::from(self.r4802) << 8) | (u32::from(self.r4803) << 16);
300 let index = u32::from(self.r4804) << 2;
301 let address = table.wrapping_add(index);
302 self.dcu_mode = self.datarom_read(address);
303 self.dcu_address = (u32::from(self.datarom_read(address.wrapping_add(1))) << 16)
304 | (u32::from(self.datarom_read(address.wrapping_add(2))) << 8)
305 | u32::from(self.datarom_read(address.wrapping_add(3)));
306 }
307
308 fn dcu_begin_transfer(&mut self) {
309 if self.dcu_mode == 3 {
310 return; }
312 let origin = self.dcu_address;
313 self.decompressor
314 .initialize(u32::from(self.dcu_mode), origin, |o| {
315 Self::datarom_read_static(&self.drom, self.r4834, o)
316 });
317 self.decompress_one();
318 let seek: u16 = if self.r480b & 2 != 0 {
319 u16::from(self.r4805) | (u16::from(self.r4806) << 8)
320 } else {
321 0
322 };
323 for _ in 0..seek {
324 self.decompress_one();
325 }
326 self.r480c |= 0x80;
327 self.dcu_offset = 0;
328 }
329
330 fn decompress_one(&mut self) {
331 let drom = &self.drom;
332 let r4834 = self.r4834;
333 self.decompressor
334 .decode(|o| Self::datarom_read_static(drom, r4834, o));
335 }
336
337 fn datarom_read_static(drom: &[u8], r4834: u8, address: u32) -> u8 {
340 let size = 1u32 << (r4834 & 3);
341 let mask = 0x0010_0000u32.wrapping_mul(size).wrapping_sub(1);
342 let offset = address & mask;
343 if (r4834 & 3) != 3 && (address & 0x40_0000) != 0 {
344 return 0;
345 }
346 drom.get(bus_mirror(offset, drom.len() as u32) as usize)
347 .copied()
348 .unwrap_or(0)
349 }
350
351 fn dcu_read(&mut self) -> u8 {
352 if self.r480c & 0x80 == 0 {
353 return 0;
354 }
355
356 if self.dcu_offset == 0 {
357 for row in 0..8usize {
358 match self.decompressor.bpp {
359 1 => self.dcu_tile[row] = self.decompressor.result as u8,
360 2 => {
361 self.dcu_tile[row * 2] = self.decompressor.result as u8;
362 self.dcu_tile[row * 2 + 1] = (self.decompressor.result >> 8) as u8;
363 }
364 4 => {
365 self.dcu_tile[row * 2] = self.decompressor.result as u8;
366 self.dcu_tile[row * 2 + 1] = (self.decompressor.result >> 8) as u8;
367 self.dcu_tile[row * 2 + 16] = (self.decompressor.result >> 16) as u8;
368 self.dcu_tile[row * 2 + 17] = (self.decompressor.result >> 24) as u8;
369 }
370 _ => {}
371 }
372 let seek = if self.r480b & 1 != 0 { self.r4807 } else { 1 };
373 for _ in 0..seek {
374 self.decompress_one();
375 }
376 }
377 }
378
379 let data = self.dcu_tile[self.dcu_offset as usize];
380 self.dcu_offset += 1;
381 self.dcu_offset &= 8 * self.decompressor.bpp - 1;
382 data
383 }
384
385 fn alu_multiply(&mut self) {
390 let result: u32 = if self.r482e & 1 != 0 {
391 let r0 = i32::from(i16::from_le_bytes([self.r4824, self.r4825]));
392 let r1 = i32::from(i16::from_le_bytes([self.r4820, self.r4821]));
393 r0.wrapping_mul(r1).cast_unsigned()
394 } else {
395 let r0 = u32::from(u16::from_le_bytes([self.r4824, self.r4825]));
396 let r1 = u32::from(u16::from_le_bytes([self.r4820, self.r4821]));
397 r0.wrapping_mul(r1)
398 };
399 self.r4828 = result as u8;
400 self.r4829 = (result >> 8) as u8;
401 self.r482a = (result >> 16) as u8;
402 self.r482b = (result >> 24) as u8;
403 self.r482f &= 0x7f;
404 }
405
406 fn alu_divide(&mut self) {
407 let (quotient, remainder): (u32, u16) = if self.r482e & 1 != 0 {
408 let dividend = i32::from_le_bytes([self.r4820, self.r4821, self.r4822, self.r4823]);
409 let divisor = i16::from_le_bytes([self.r4826, self.r4827]);
410 if divisor == 0 {
411 (0, dividend.cast_unsigned() as u16)
412 } else {
413 (
414 dividend.wrapping_div(i32::from(divisor)).cast_unsigned(),
415 dividend.wrapping_rem(i32::from(divisor)).cast_unsigned() as u16,
416 )
417 }
418 } else {
419 let dividend = u32::from_le_bytes([self.r4820, self.r4821, self.r4822, self.r4823]);
420 let divisor = u16::from_le_bytes([self.r4826, self.r4827]);
421 if divisor == 0 {
422 (0, dividend as u16)
423 } else {
424 (
425 dividend / u32::from(divisor),
426 (dividend % u32::from(divisor)) as u16,
427 )
428 }
429 };
430 self.r4828 = quotient as u8;
431 self.r4829 = (quotient >> 8) as u8;
432 self.r482a = (quotient >> 16) as u8;
433 self.r482b = (quotient >> 24) as u8;
434 self.r482c = remainder as u8;
435 self.r482d = (remainder >> 8) as u8;
436 self.r482f &= 0x7f;
437 }
438
439 const fn mcurom_linear(bank: u32, offset: u32) -> u32 {
447 ((bank & 0x3F) << 16) | offset
448 }
449
450 fn mcurom_read(&self, linear: u32) -> u8 {
451 if linear < 0x10_0000 {
452 let a = linear & 0x0f_ffff;
453 if !self.prom.is_empty() {
454 return self.prom[bus_mirror(a, self.prom.len() as u32) as usize];
455 }
456 return self.datarom_read(a | (0x10_0000 * u32::from(self.r4830 & 7)));
457 }
458 if linear < 0x20_0000 {
459 let a = linear & 0x0f_ffff;
460 if self.r4834 & 4 != 0 && !self.prom.is_empty() {
461 return self.prom[bus_mirror(0x10_0000 + a, self.prom.len() as u32) as usize];
462 }
463 return self.datarom_read(a | (0x10_0000 * u32::from(self.r4831 & 7)));
464 }
465 if linear < 0x30_0000 {
466 let a = linear & 0x0f_ffff;
467 return self.datarom_read(a | (0x10_0000 * u32::from(self.r4832 & 7)));
468 }
469 if linear < 0x40_0000 {
470 let a = linear & 0x0f_ffff;
471 return self.datarom_read(a | (0x10_0000 * u32::from(self.r4833 & 7)));
472 }
473 0
474 }
475
476 fn mcuram_addr(&self, bank: u32, offset_in_window: u32) -> usize {
477 let a = ((bank & 0x07) << 13) | (offset_in_window & 0x1FFF);
478 bus_mirror(a, self.ram.len() as u32) as usize
479 }
480
481 const fn reg_addr(addr24: u32) -> u16 {
488 let bank = (addr24 >> 16) & 0xff;
489 if bank == 0x50 {
490 return 0x4800;
491 }
492 if bank == 0x58 {
493 return 0x4808;
494 }
495 (0x4800 | (addr24 & 0x3f)) as u16
496 }
497
498 fn read_reg(&mut self, addr: u16) -> u8 {
499 match addr {
500 0x4800 => {
501 let counter = u16::from(self.r4809) | (u16::from(self.r480a) << 8);
502 let counter = counter.wrapping_sub(1);
503 self.r4809 = counter as u8;
504 self.r480a = (counter >> 8) as u8;
505 self.dcu_read()
506 }
507 0x4801 => self.r4801,
508 0x4802 => self.r4802,
509 0x4803 => self.r4803,
510 0x4804 => self.r4804,
511 0x4805 => self.r4805,
512 0x4806 => self.r4806,
513 0x4807 => self.r4807,
514 0x4809 => self.r4809,
516 0x480a => self.r480a,
517 0x480b => self.r480b,
518 0x480c => self.r480c,
519 0x4810 => {
520 let data = self.r4810;
521 self.data_port_increment_4810();
522 data
523 }
524 0x4811 => self.r4811,
525 0x4812 => self.r4812,
526 0x4813 => self.r4813,
527 0x4814 => self.r4814,
528 0x4815 => self.r4815,
529 0x4816 => self.r4816,
530 0x4817 => self.r4817,
531 0x4818 => self.r4818,
532 0x481a => {
533 self.data_port_increment_seek(3);
534 0
535 }
536 0x4820 => self.r4820,
537 0x4821 => self.r4821,
538 0x4822 => self.r4822,
539 0x4823 => self.r4823,
540 0x4824 => self.r4824,
541 0x4825 => self.r4825,
542 0x4826 => self.r4826,
543 0x4827 => self.r4827,
544 0x4828 => self.r4828,
545 0x4829 => self.r4829,
546 0x482a => self.r482a,
547 0x482b => self.r482b,
548 0x482c => self.r482c,
549 0x482d => self.r482d,
550 0x482e => self.r482e,
551 0x482f => self.r482f,
552 0x4830 => self.r4830,
553 0x4831 => self.r4831,
554 0x4832 => self.r4832,
555 0x4833 => self.r4833,
556 0x4834 => self.r4834,
557 _ => 0,
558 }
559 }
560
561 fn write_reg(&mut self, addr: u16, data: u8) {
562 match addr {
563 0x4801 => self.r4801 = data,
564 0x4802 => self.r4802 = data,
565 0x4803 => self.r4803 = data & 0x7f,
566 0x4804 => {
567 self.r4804 = data;
568 self.dcu_load_address();
569 }
570 0x4805 => self.r4805 = data,
571 0x4806 => {
572 self.r4806 = data;
573 self.r480c &= 0x7f;
574 self.dcu_pending = true;
577 }
578 0x4807 => self.r4807 = data,
579 0x4809 => self.r4809 = data,
580 0x480a => self.r480a = data,
581 0x480b => self.r480b = data & 0x03,
582 0x4811 => self.r4811 = data,
583 0x4812 => self.r4812 = data,
584 0x4813 => {
585 self.r4813 = data & 0x7f;
586 self.data_port_read();
587 }
588 0x4814 => {
589 self.r4814 = data;
590 self.data_port_increment_seek(1);
591 }
592 0x4815 => {
593 self.r4815 = data;
594 if self.r4818 & 2 != 0 {
595 self.data_port_read();
596 }
597 self.data_port_increment_seek(2);
598 }
599 0x4816 => self.r4816 = data,
600 0x4817 => self.r4817 = data,
601 0x4818 => {
602 self.r4818 = data & 0x7f;
603 self.data_port_read();
604 }
605 0x4820 => self.r4820 = data,
606 0x4821 => self.r4821 = data,
607 0x4822 => self.r4822 = data,
608 0x4823 => self.r4823 = data,
609 0x4824 => self.r4824 = data,
610 0x4825 => {
611 self.r4825 = data;
612 self.r482f |= 0x81;
613 self.mul_pending = true;
615 }
616 0x4826 => self.r4826 = data,
617 0x4827 => {
618 self.r4827 = data;
619 self.r482f |= 0x80;
620 self.div_pending = true;
622 }
623 0x482e => self.r482e = data & 0x01,
624 0x4830 => self.r4830 = data & 0x87,
625 0x4831 => self.r4831 = data & 0x07,
626 0x4832 => self.r4832 = data & 0x07,
627 0x4833 => self.r4833 = data & 0x07,
628 0x4834 => self.r4834 = data & 0x07,
629 _ => {}
630 }
631 }
632}
633
634impl Spc7110Board {
635 pub fn save_state(&self, w: &mut SaveWriter) {
640 w.section(*b"SP70", |s| {
641 s.write_u8(self.r4801);
642 s.write_u8(self.r4802);
643 s.write_u8(self.r4803);
644 s.write_u8(self.r4804);
645 s.write_u8(self.r4805);
646 s.write_u8(self.r4806);
647 s.write_u8(self.r4807);
648 s.write_u8(self.r4809);
649 s.write_u8(self.r480a);
650 s.write_u8(self.r480b);
651 s.write_u8(self.r480c);
652 s.write_u8(self.dcu_mode);
653 s.write_u32(self.dcu_address);
654 s.write_u32(self.dcu_offset);
655 s.write_bytes(&self.dcu_tile);
656 s.write_u8(self.r4810);
657 s.write_u8(self.r4811);
658 s.write_u8(self.r4812);
659 s.write_u8(self.r4813);
660 s.write_u8(self.r4814);
661 s.write_u8(self.r4815);
662 s.write_u8(self.r4816);
663 s.write_u8(self.r4817);
664 s.write_u8(self.r4818);
665 s.write_u8(self.r4820);
666 s.write_u8(self.r4821);
667 s.write_u8(self.r4822);
668 s.write_u8(self.r4823);
669 s.write_u8(self.r4824);
670 s.write_u8(self.r4825);
671 s.write_u8(self.r4826);
672 s.write_u8(self.r4827);
673 s.write_u8(self.r4828);
674 s.write_u8(self.r4829);
675 s.write_u8(self.r482a);
676 s.write_u8(self.r482b);
677 s.write_u8(self.r482c);
678 s.write_u8(self.r482d);
679 s.write_u8(self.r482e);
680 s.write_u8(self.r482f);
681 s.write_u8(self.r4830);
682 s.write_u8(self.r4831);
683 s.write_u8(self.r4832);
684 s.write_u8(self.r4833);
685 s.write_u8(self.r4834);
686 });
687 self.decompressor.save_state(w);
688 self.rtc.save_state(w);
689 }
690
691 pub fn load_state(&mut self, r: &mut SaveReader) -> Result<(), SaveStateError> {
706 let mut s = r.expect_section(*b"SP70")?;
707 self.r4801 = s.read_u8()?;
708 self.r4802 = s.read_u8()?;
709 self.r4803 = s.read_u8()? & 0x7F; self.r4804 = s.read_u8()?;
711 self.r4805 = s.read_u8()?;
712 self.r4806 = s.read_u8()?;
713 self.r4807 = s.read_u8()?;
714 self.r4809 = s.read_u8()?;
715 self.r480a = s.read_u8()?;
716 self.r480b = s.read_u8()? & 0x03; self.r480c = s.read_u8()?;
718 self.dcu_mode = s.read_u8()?;
719 self.dcu_address = s.read_u32()?;
720 self.dcu_offset = s.read_u32()? & 31;
721 self.dcu_tile.copy_from_slice(s.read_bytes(32)?);
722 self.r4810 = s.read_u8()?;
723 self.r4811 = s.read_u8()?;
724 self.r4812 = s.read_u8()?;
725 self.r4813 = s.read_u8()? & 0x7F; self.r4814 = s.read_u8()?;
727 self.r4815 = s.read_u8()?;
728 self.r4816 = s.read_u8()?;
729 self.r4817 = s.read_u8()?;
730 self.r4818 = s.read_u8()? & 0x7F; self.r4820 = s.read_u8()?;
732 self.r4821 = s.read_u8()?;
733 self.r4822 = s.read_u8()?;
734 self.r4823 = s.read_u8()?;
735 self.r4824 = s.read_u8()?;
736 self.r4825 = s.read_u8()?;
737 self.r4826 = s.read_u8()?;
738 self.r4827 = s.read_u8()?;
739 self.r4828 = s.read_u8()?;
740 self.r4829 = s.read_u8()?;
741 self.r482a = s.read_u8()?;
742 self.r482b = s.read_u8()?;
743 self.r482c = s.read_u8()?;
744 self.r482d = s.read_u8()?;
745 self.r482e = s.read_u8()? & 0x01; self.r482f = s.read_u8()?;
747 self.r4830 = s.read_u8()? & 0x87; self.r4831 = s.read_u8()? & 0x07; self.r4832 = s.read_u8()? & 0x07; self.r4833 = s.read_u8()? & 0x07; self.r4834 = s.read_u8()? & 0x07; if s.remaining() != 0 {
753 return Err(SaveStateError::Invalid(alloc::format!(
754 "SP70 section has {} trailing byte(s)",
755 s.remaining()
756 )));
757 }
758 self.decompressor.load_state(r)?;
759 self.rtc.load_state(r)
760 }
761}
762
763const fn sign_extend16(v: u16) -> u32 {
765 (v.cast_signed() as i32).cast_unsigned()
766}
767
768const fn bus_mirror(address: u32, size: u32) -> u32 {
780 if size == 0 {
781 return 0;
782 }
783 let mut address = address;
784 let mut size = size;
785 let mut base = 0u32;
786 let mut mask = 1u32 << 23;
787 while address >= size {
788 while address & mask == 0 {
789 mask >>= 1;
790 }
791 address -= mask;
792 if size > mask {
793 size -= mask;
794 base += mask;
795 }
796 mask >>= 1;
797 }
798 base + address
799}
800
801impl Board for Spc7110Board {
802 fn name(&self) -> &'static str {
803 "HiROM+SPC7110"
804 }
805
806 fn coprocessor(&self) -> Coprocessor {
807 Coprocessor::Spc7110
808 }
809
810 fn coprocessor_tick(&mut self) {
816 if self.dcu_pending {
817 self.dcu_pending = false;
818 self.dcu_begin_transfer();
819 }
820 if self.mul_pending {
821 self.mul_pending = false;
822 self.alu_multiply();
823 }
824 if self.div_pending {
825 self.div_pending = false;
826 self.alu_divide();
827 }
828 }
829
830 fn map(&self, addr24: u32) -> MappedAddr {
831 let a = addr24 & 0xff_ffff;
832 let bank = (a >> 16) & 0xff;
833 let off = a & 0xffff;
834 if bank == 0x50 || bank == 0x58 {
835 return MappedAddr::Coprocessor;
836 }
837 if matches!(bank, 0x00..=0x3f | 0x80..=0xbf) {
838 if (0x4800..=0x483f).contains(&off) || (0x4840..=0x4842).contains(&off) {
839 return MappedAddr::Coprocessor;
840 }
841 if (0x6000..=0x7fff).contains(&off) {
842 return MappedAddr::Sram(0);
843 }
844 if off >= 0x8000 {
845 return MappedAddr::Rom(a);
846 }
847 }
848 if matches!(bank, 0xc0..=0xff) {
857 return MappedAddr::Rom(a);
858 }
859 MappedAddr::Open
860 }
861
862 fn read24(&mut self, addr24: u32) -> u8 {
863 let a = addr24 & 0xff_ffff;
864 let bank = (a >> 16) & 0xff;
865 let off = a & 0xffff;
866
867 if bank == 0x50 || bank == 0x58 {
868 return self.read_reg(Self::reg_addr(a));
869 }
870 if matches!(bank, 0x00..=0x3f | 0x80..=0xbf) {
871 if (0x4800..=0x483f).contains(&off) {
872 return self.read_reg(Self::reg_addr(a));
873 }
874 if (0x4840..=0x4842).contains(&off) {
875 return self.rtc.read(off - 0x4840);
876 }
877 if (0x6000..=0x7fff).contains(&off) {
878 if self.r4830 & 0x80 == 0 {
879 return 0;
880 }
881 let idx = self.mcuram_addr(bank, off - 0x6000);
882 return self.ram[idx];
883 }
884 if off >= 0x8000 {
885 return self.mcurom_read(Self::mcurom_linear(bank, off));
886 }
887 }
888 if matches!(bank, 0xc0..=0xff) {
891 return self.mcurom_read(Self::mcurom_linear(bank, off));
892 }
893 0
894 }
895
896 fn write24(&mut self, addr24: u32, val: u8) {
897 let a = addr24 & 0xff_ffff;
898 let bank = (a >> 16) & 0xff;
899 let off = a & 0xffff;
900
901 if bank == 0x50 || bank == 0x58 {
902 self.write_reg(Self::reg_addr(a), val);
903 return;
904 }
905 if matches!(bank, 0x00..=0x3f | 0x80..=0xbf) {
906 if (0x4800..=0x483f).contains(&off) {
907 self.write_reg(Self::reg_addr(a), val);
908 return;
909 }
910 if (0x4840..=0x4842).contains(&off) {
911 self.rtc.write(off - 0x4840, val);
912 return;
913 }
914 if (0x6000..=0x7fff).contains(&off) {
915 if self.r4830 & 0x80 == 0 {
916 return;
917 }
918 let idx = self.mcuram_addr(bank, off - 0x6000);
919 self.ram[idx] = val;
920 }
921 }
923 }
924
925 fn rom(&self) -> &[u8] {
926 &self.drom
927 }
928
929 fn sram(&self) -> &[u8] {
930 &self.ram
931 }
932
933 fn sram_mut(&mut self) -> &mut [u8] {
934 &mut self.ram
935 }
936
937 fn save_state(&self, w: &mut SaveWriter) {
938 Self::save_state(self, w);
939 }
940
941 fn load_state(&mut self, r: &mut SaveReader) -> Result<(), SaveStateError> {
942 Self::load_state(self, r)
943 }
944}
945
946#[must_use]
963pub fn select(_map_mode: MapMode, rom: Box<[u8]>, sram_size: usize) -> Spc7110Board {
964 const PROM_SIZE: usize = 0x10_0000;
965 const DROM_SIZE: usize = 0x40_0000;
966 if rom.len() > PROM_SIZE {
967 let (prom, rest) = rom.split_at(PROM_SIZE);
968 let drom_len = rest.len().min(DROM_SIZE);
969 Spc7110Board::new(
970 prom.to_vec().into_boxed_slice(),
971 rest[..drom_len].to_vec().into_boxed_slice(),
972 sram_size,
973 )
974 } else {
975 Spc7110Board::new(Box::new([]), rom, sram_size)
976 }
977}
978
979#[cfg(test)]
980mod tests {
981 use super::*;
982
983 fn board() -> Spc7110Board {
984 Spc7110Board::new(
985 vec![0u8; 0x10_0000].into_boxed_slice(),
986 vec![0u8; 0x60_0000].into_boxed_slice(),
987 0x2000,
988 )
989 }
990
991 #[test]
992 fn control_register_roundtrip() {
993 let mut b = board();
994 b.write24(0x00_4801, 0xAB);
995 assert_eq!(b.read24(0x00_4801), 0xAB);
996 b.write24(0x00_4830, 0xFF); assert_eq!(b.read24(0x00_4830), 0x87);
998 }
999
1000 #[test]
1001 fn bank_50_and_58_mirror_the_data_port_and_dummy_register() {
1002 let mut b = board();
1003 assert_eq!(b.read24(0x58_1234), 0x00);
1005 assert_eq!(b.read24(0x50_ABCD), 0x00);
1008 }
1009
1010 #[test]
1011 fn alu_multiply_unsigned() {
1012 let mut b = board();
1013 b.write24(0x00_4820, 5); b.write24(0x00_4821, 0);
1015 b.write24(0x00_4824, 3); b.write24(0x00_4825, 0); assert_ne!(
1019 b.read24(0x00_482f) & 0x80,
1020 0,
1021 "the busy bit must be set immediately on the triggering write, before the tick"
1022 );
1023 b.coprocessor_tick();
1024 assert_eq!(
1025 b.read24(0x00_482f) & 0x80,
1026 0,
1027 "the busy bit clears once the deferred multiply actually runs"
1028 );
1029 assert_eq!(b.read24(0x00_4828), 15);
1030 assert_eq!(b.read24(0x00_4829), 0);
1031 }
1032
1033 #[test]
1034 fn alu_divide_unsigned() {
1035 let mut b = board();
1036 b.write24(0x00_4820, 10);
1037 b.write24(0x00_4821, 0);
1038 b.write24(0x00_4822, 0);
1039 b.write24(0x00_4823, 0);
1040 b.write24(0x00_4826, 3);
1041 b.write24(0x00_4827, 0); b.coprocessor_tick();
1043 assert_eq!(b.read24(0x00_4828), 3); assert_eq!(b.read24(0x00_482c), 1); }
1046
1047 #[test]
1052 fn dcu_begin_transfer_is_deferred_one_tick() {
1053 let mut b = board();
1054 b.write24(0x00_4801, 0);
1057 b.write24(0x00_4802, 0);
1058 b.write24(0x00_4803, 0);
1059 b.write24(0x00_4804, 0); b.write24(0x00_4806, 0); assert_eq!(
1062 b.read24(0x00_480c) & 0x80,
1063 0,
1064 "the ready bit must be clear immediately after the triggering write, before the tick"
1065 );
1066 b.coprocessor_tick();
1067 assert_ne!(
1068 b.read24(0x00_480c) & 0x80,
1069 0,
1070 "the ready bit sets once the deferred transfer actually runs"
1071 );
1072 }
1073
1074 #[test]
1075 fn mcurom_linear_folds_both_windows_to_the_same_space() {
1076 assert!(Spc7110Board::mcurom_linear(0x00, 0x8000) < 0x10_0000);
1078 assert!(Spc7110Board::mcurom_linear(0xc0, 0x0000) < 0x10_0000);
1079 }
1080
1081 #[test]
1082 fn register_and_rtc_state_round_trips_through_save_state() {
1083 let mut b = board();
1084 b.write24(0x00_4801, 0xAB);
1085 b.write24(0x00_4830, 0xFF); b.rtc.write(0, 1); b.rtc.write(1, 0x03); b.rtc.write(1, 0x00); b.rtc.write(1, 0x07); let mut w = SaveWriter::new();
1092 b.save_state(&mut w);
1093 let bytes = w.into_bytes();
1094
1095 let mut fresh = board();
1096 let mut r = SaveReader::new(&bytes);
1097 fresh.load_state(&mut r).unwrap();
1098
1099 assert_eq!(fresh.read24(0x00_4801), 0xAB);
1100 assert_eq!(fresh.read24(0x00_4830), 0x87);
1101 fresh.rtc.write(0, 0);
1107 fresh.rtc.write(0, 1);
1108 fresh.rtc.write(1, 0x0c); fresh.rtc.write(1, 0x00); assert_eq!(fresh.rtc.read(1), 7);
1111 assert_eq!(r.remaining(), 0);
1112 }
1113}