1#![allow(
68 clippy::cast_possible_truncation,
69 clippy::cast_lossless,
70 clippy::struct_excessive_bools,
71 clippy::missing_const_for_fn,
72 clippy::doc_markdown,
73 clippy::option_if_let_else
74)]
75
76use crate::cartridge::Mirroring;
77use crate::mapper::{Mapper, MapperCaps, MapperError};
78use alloc::{boxed::Box, vec::Vec};
79use alloc::{format, vec};
80
81const PRG_BANK_16K: usize = 0x4000;
82const CHR_BANK_1K: usize = 0x0400;
83const NAMETABLE_SIZE: usize = 0x0400;
84const NAMETABLE_SIZE_U16: u16 = 0x0400;
85
86const SAVE_STATE_VERSION: u8 = 2;
90
91const WRAM_153: usize = 0x2000;
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum FcgVariant {
97 Both,
100 Fcg,
103 Lz93d50_24c02,
106 Lz93d50_24c01,
108 Lz93d50Wram,
111}
112
113impl FcgVariant {
114 const fn responds_low(self) -> bool {
115 matches!(self, Self::Both | Self::Fcg)
116 }
117 const fn responds_high(self) -> bool {
118 matches!(
119 self,
120 Self::Both | Self::Lz93d50_24c02 | Self::Lz93d50_24c01 | Self::Lz93d50Wram
121 )
122 }
123 const fn latched_counter(self) -> bool {
126 !matches!(self, Self::Fcg)
127 }
128 const fn eeprom_bytes(self) -> usize {
129 match self {
130 Self::Lz93d50_24c01 => 128,
131 Self::Both | Self::Lz93d50_24c02 => 256,
132 Self::Fcg | Self::Lz93d50Wram => 0,
133 }
134 }
135 const fn is_x24c01(self) -> bool {
138 matches!(self, Self::Lz93d50_24c01)
139 }
140}
141
142#[derive(Debug, Clone)]
164struct Eeprom {
165 mem: Box<[u8]>,
166 is_x24c01: bool,
167
168 last_scl: u8,
169 last_sda: u8,
170
171 mode: I2cMode,
172 next_mode: I2cMode,
173 chip_addr: u8,
175 addr: u8,
177 data: u8,
179 counter: u8,
181 output: u8,
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186enum I2cMode {
187 Idle,
188 ChipAddress,
189 Address,
190 Read,
191 Write,
192 SendAck,
193 WaitAck,
194}
195
196impl Eeprom {
197 fn new(bytes: usize, is_x24c01: bool) -> Self {
198 Self {
199 mem: vec![0xFFu8; bytes.max(1)].into_boxed_slice(),
200 is_x24c01,
201 last_scl: 0,
202 last_sda: 0,
203 mode: I2cMode::Idle,
204 next_mode: I2cMode::Idle,
205 chip_addr: 0,
206 addr: 0,
207 data: 0,
208 counter: 0,
209 output: 1,
210 }
211 }
212
213 fn addr_mask(&self) -> u8 {
214 if self.is_x24c01 { 0x7F } else { 0xFF }
215 }
216
217 fn write_bit(&mut self, dest: &mut u8, value: u8) {
220 if self.counter < 8 {
221 let shift = if self.is_x24c01 {
222 self.counter
223 } else {
224 7 - self.counter
225 };
226 let mask = !(1u8 << shift);
227 *dest = (*dest & mask) | (value << shift);
228 self.counter += 1;
229 }
230 }
231
232 fn read_bit(&mut self) {
235 if self.counter < 8 {
236 let shift = if self.is_x24c01 {
237 self.counter
238 } else {
239 7 - self.counter
240 };
241 self.output = u8::from((self.data & (1u8 << shift)) != 0);
242 self.counter += 1;
243 }
244 }
245
246 fn write_lines(&mut self, scl_b: bool, sda_b: bool) {
250 let scl = u8::from(scl_b);
251 let sda = u8::from(sda_b);
252
253 if self.last_scl != 0 && scl != 0 && sda < self.last_sda {
254 self.mode = if self.is_x24c01 {
256 I2cMode::Address
257 } else {
258 I2cMode::ChipAddress
259 };
260 self.addr = 0;
261 self.counter = 0;
262 self.output = 1;
263 } else if self.last_scl != 0 && scl != 0 && sda > self.last_sda {
264 self.mode = I2cMode::Idle;
266 self.output = 1;
267 } else if scl > self.last_scl {
268 self.clock_rise(sda);
269 } else if scl < self.last_scl {
270 self.clock_fall(sda);
271 }
272
273 self.last_scl = scl;
274 self.last_sda = sda;
275 }
276
277 fn read_sda(&self) -> bool {
278 self.output != 0
279 }
280
281 fn clock_rise(&mut self, sda: u8) {
282 match self.mode {
283 I2cMode::ChipAddress => {
284 let mut chip = self.chip_addr;
285 self.write_bit(&mut chip, sda);
286 self.chip_addr = chip;
287 }
288 I2cMode::Address => {
289 if self.is_x24c01 {
290 if self.counter < 7 {
292 let mut addr = self.addr;
293 self.write_bit(&mut addr, sda);
294 self.addr = addr;
295 } else if self.counter == 7 {
296 self.counter = 8;
297 if sda != 0 {
298 self.next_mode = I2cMode::Read;
299 self.data = self.mem[(self.addr & 0x7F) as usize];
300 } else {
301 self.next_mode = I2cMode::Write;
302 }
303 }
304 } else {
305 let mut addr = self.addr;
306 self.write_bit(&mut addr, sda);
307 self.addr = addr;
308 }
309 }
310 I2cMode::Read => self.read_bit(),
311 I2cMode::Write => {
312 let mut data = self.data;
313 self.write_bit(&mut data, sda);
314 self.data = data;
315 }
316 I2cMode::SendAck => self.output = 0,
317 I2cMode::WaitAck => {
318 if sda == 0 {
319 if self.is_x24c01 {
320 self.next_mode = I2cMode::Idle;
322 } else {
323 self.next_mode = I2cMode::Read;
325 self.data = self.mem[self.addr as usize];
326 }
327 }
328 }
329 I2cMode::Idle => {}
330 }
331 }
332
333 fn clock_fall(&mut self, _sda: u8) {
334 match self.mode {
335 I2cMode::ChipAddress => {
336 if self.counter == 8 {
337 if (self.chip_addr & 0xA0) == 0xA0 {
338 self.mode = I2cMode::SendAck;
339 self.counter = 0;
340 self.output = 1;
341 if self.chip_addr & 0x01 != 0 {
342 self.next_mode = I2cMode::Read;
343 self.data = self.mem[self.addr as usize];
344 } else {
345 self.next_mode = I2cMode::Address;
346 }
347 } else {
348 self.mode = I2cMode::Idle;
349 self.counter = 0;
350 self.output = 1;
351 }
352 }
353 }
354 I2cMode::Address => {
355 if self.is_x24c01 {
356 if self.counter == 8 {
357 self.mode = I2cMode::SendAck;
359 self.output = 1;
360 }
361 } else if self.counter == 8 {
362 self.counter = 0;
363 self.mode = I2cMode::SendAck;
364 self.next_mode = I2cMode::Write;
365 self.output = 1;
366 }
367 }
368 I2cMode::SendAck => {
369 self.mode = self.next_mode;
370 self.counter = 0;
371 self.output = 1;
372 }
373 I2cMode::Read => {
374 if self.counter == 8 {
375 self.mode = I2cMode::WaitAck;
376 self.addr = self.addr.wrapping_add(1) & self.addr_mask();
383 }
384 }
385 I2cMode::Write => {
386 if self.counter == 8 {
387 self.mode = I2cMode::SendAck;
388 self.next_mode = if self.is_x24c01 {
389 I2cMode::Idle
390 } else {
391 I2cMode::Write
392 };
393 self.mem[(self.addr & self.addr_mask()) as usize] = self.data;
394 self.addr = self.addr.wrapping_add(1) & self.addr_mask();
401 self.counter = 0;
402 }
403 }
404 I2cMode::WaitAck => {
405 if !self.is_x24c01 {
406 self.mode = self.next_mode;
407 self.counter = 0;
408 self.output = 1;
409 }
410 }
411 I2cMode::Idle => {}
412 }
413 }
414}
415
416pub struct BandaiFcg {
418 prg_rom: Box<[u8]>,
419 chr: Box<[u8]>,
420 vram: Box<[u8]>,
421 chr_is_ram: bool,
422
423 variant: FcgVariant,
424
425 prg_bank: u8,
426 chr_banks: [u8; 8],
427 mirroring: Mirroring,
428
429 irq_latch: u16,
431 irq_counter: u16,
432 irq_enabled: bool,
433 irq_pending: bool,
434
435 eeprom: Option<Eeprom>,
436 eeprom_ctrl: u8,
438
439 outer: u8,
441 wram_enabled: bool,
443 wram: Box<[u8]>,
445}
446
447impl BandaiFcg {
448 pub fn new(
457 prg_rom: Box<[u8]>,
458 chr_rom: Box<[u8]>,
459 mirroring: Mirroring,
460 variant: FcgVariant,
461 ) -> Result<Self, MapperError> {
462 if prg_rom.is_empty() || !prg_rom.len().is_multiple_of(PRG_BANK_16K) {
463 return Err(MapperError::Invalid(format!(
464 "Bandai-FCG PRG-ROM size {} is not a non-zero multiple of 16 KiB",
465 prg_rom.len()
466 )));
467 }
468 let chr_is_ram = chr_rom.is_empty();
469 let chr: Box<[u8]> = if chr_is_ram {
470 vec![0u8; 8 * CHR_BANK_1K].into_boxed_slice()
471 } else if chr_rom.len().is_multiple_of(CHR_BANK_1K) {
472 chr_rom
473 } else {
474 return Err(MapperError::Invalid(format!(
475 "Bandai-FCG CHR-ROM size {} is not a multiple of 1 KiB",
476 chr_rom.len()
477 )));
478 };
479 let eeprom = if variant.eeprom_bytes() > 0 {
480 Some(Eeprom::new(variant.eeprom_bytes(), variant.is_x24c01()))
481 } else {
482 None
483 };
484 Ok(Self {
485 prg_rom,
486 chr,
487 vram: vec![0u8; 2 * NAMETABLE_SIZE].into_boxed_slice(),
488 chr_is_ram,
489 variant,
490 prg_bank: 0,
491 chr_banks: [0; 8],
492 mirroring,
493 irq_latch: 0,
494 irq_counter: 0,
495 irq_enabled: false,
496 irq_pending: false,
497 eeprom,
498 eeprom_ctrl: 0,
499 outer: 0,
500 wram_enabled: false,
501 wram: if variant == FcgVariant::Lz93d50Wram {
502 vec![0u8; WRAM_153].into_boxed_slice()
503 } else {
504 Box::new([])
505 },
506 })
507 }
508
509 const fn outer_base(&self) -> usize {
511 (self.outer as usize & 0x01) << 4
512 }
513
514 const fn nametable_offset(&self, addr: u16) -> usize {
515 let table = (((addr - 0x2000) / NAMETABLE_SIZE_U16) & 0x03) as u8;
516 let local = (addr as usize) & (NAMETABLE_SIZE - 1);
517 let physical = self.mirroring.physical_bank(table);
518 physical * NAMETABLE_SIZE + local
519 }
520
521 fn chr_offset(&self, addr: u16) -> usize {
522 if self.variant == FcgVariant::Lz93d50Wram {
528 return addr as usize & 0x1FFF;
529 }
530 let slot = (addr as usize / CHR_BANK_1K) & 0x07;
531 let total = (self.chr.len() / CHR_BANK_1K).max(1);
532 let bank = (self.chr_banks[slot] as usize) % total;
533 bank * CHR_BANK_1K + (addr as usize & (CHR_BANK_1K - 1))
534 }
535
536 fn write_reg(&mut self, off: u8, value: u8) {
538 match off & 0x0F {
539 0x0..=0x3 if self.variant == FcgVariant::Lz93d50Wram => self.outer = value & 0x01,
542 0x4..=0x7 if self.variant == FcgVariant::Lz93d50Wram => {}
543 0x0..=0x7 => self.chr_banks[(off & 0x07) as usize] = value,
544 0x8 => self.prg_bank = value & 0x0F,
545 0x9 => {
546 self.mirroring = match value & 0x03 {
547 0 => Mirroring::Vertical,
548 1 => Mirroring::Horizontal,
549 2 => Mirroring::SingleScreenA,
550 _ => Mirroring::SingleScreenB,
551 };
552 }
553 0xA => {
554 self.irq_pending = false;
556 self.irq_enabled = (value & 0x01) != 0;
557 if self.variant.latched_counter() {
558 self.irq_counter = self.irq_latch;
560 }
561 }
562 0xB => {
563 if self.variant.latched_counter() {
564 self.irq_latch = (self.irq_latch & 0xFF00) | value as u16;
565 } else {
566 self.irq_counter = (self.irq_counter & 0xFF00) | value as u16;
567 }
568 }
569 0xC => {
570 if self.variant.latched_counter() {
571 self.irq_latch = (self.irq_latch & 0x00FF) | ((value as u16) << 8);
572 } else {
573 self.irq_counter = (self.irq_counter & 0x00FF) | ((value as u16) << 8);
574 }
575 }
576 0xD if self.variant == FcgVariant::Lz93d50Wram => {
577 self.eeprom_ctrl = value;
578 self.wram_enabled = value & 0x20 != 0;
579 }
580 0xD => {
581 self.eeprom_ctrl = value;
582 if let Some(ee) = self.eeprom.as_mut() {
583 let scl = (value & 0x20) != 0;
585 let sda = (value & 0x40) != 0;
586 ee.write_lines(scl, sda);
587 }
588 }
589 _ => {}
590 }
591 }
592}
593
594impl Mapper for BandaiFcg {
595 fn sram(&self) -> &[u8] {
600 if !self.wram.is_empty() {
601 return &self.wram;
602 }
603 self.eeprom.as_ref().map_or(&[], |e| &e.mem)
604 }
605 fn sram_mut(&mut self) -> &mut [u8] {
606 if !self.wram.is_empty() {
607 return &mut self.wram;
608 }
609 match self.eeprom.as_mut() {
610 Some(e) => &mut e.mem,
611 None => &mut [],
612 }
613 }
614
615 fn caps(&self) -> MapperCaps {
617 MapperCaps::CYCLE_IRQ
618 }
619
620 fn cpu_read(&mut self, addr: u16) -> u8 {
621 match addr {
622 0x6000..=0x7FFF if !self.wram.is_empty() => {
623 if self.wram_enabled {
624 self.wram[usize::from(addr - 0x6000)]
625 } else {
626 0
627 }
628 }
629 0x6000..=0x7FFF => {
630 if let Some(ee) = self.eeprom.as_ref() {
634 let bit = u8::from(ee.read_sda());
635 return bit << 4;
636 }
637 0
638 }
639 0x8000..=0xBFFF => {
640 let total = (self.prg_rom.len() / PRG_BANK_16K).max(1);
641 let bank = (self.prg_bank as usize | self.outer_base()) % total;
642 self.prg_rom[bank * PRG_BANK_16K + (addr - 0x8000) as usize]
643 }
644 0xC000..=0xFFFF => {
645 let total = (self.prg_rom.len() / PRG_BANK_16K).max(1);
646 let last = if self.wram.is_empty() {
648 total - 1
649 } else {
650 (0x0F | self.outer_base()) % total
651 };
652 self.prg_rom[last * PRG_BANK_16K + (addr - 0xC000) as usize]
653 }
654 _ => 0,
655 }
656 }
657
658 fn cpu_write(&mut self, addr: u16, value: u8) {
659 if !self.wram.is_empty() && self.wram_enabled && (0x6000..=0x7FFF).contains(&addr) {
660 self.wram[usize::from(addr - 0x6000)] = value;
661 }
662 if self.variant.responds_low() && (0x6000..=0x7FFF).contains(&addr) {
663 self.write_reg((addr & 0x0F) as u8, value);
664 }
665 if self.variant.responds_high() && (0x8000..=0xFFFF).contains(&addr) {
666 self.write_reg((addr & 0x0F) as u8, value);
667 }
668 }
669
670 fn cpu_read_unmapped(&self, addr: u16) -> bool {
671 if self.eeprom.is_some() && (0x6000..=0x7FFF).contains(&addr) {
674 return false;
675 }
676 if !self.wram.is_empty() && (0x6000..=0x7FFF).contains(&addr) {
677 return !self.wram_enabled;
678 }
679 (0x4020..=0x5FFF).contains(&addr)
680 }
681
682 fn ppu_read(&mut self, addr: u16) -> u8 {
683 let addr = addr & 0x3FFF;
684 match addr {
685 0x0000..=0x1FFF => {
686 let off = self.chr_offset(addr);
687 self.chr[off % self.chr.len()]
688 }
689 0x2000..=0x3EFF => self.vram[self.nametable_offset(addr)],
690 _ => 0,
691 }
692 }
693
694 fn ppu_write(&mut self, addr: u16, value: u8) {
695 let addr = addr & 0x3FFF;
696 match addr {
697 0x0000..=0x1FFF => {
698 if self.chr_is_ram {
699 let off = self.chr_offset(addr);
700 let len = self.chr.len();
701 self.chr[off % len] = value;
702 }
703 }
704 0x2000..=0x3EFF => {
705 let off = self.nametable_offset(addr);
706 self.vram[off] = value;
707 }
708 _ => {}
709 }
710 }
711
712 fn notify_cpu_cycle(&mut self) {
713 if !self.irq_enabled {
714 return;
715 }
716 if self.irq_counter == 0 {
720 self.irq_pending = true;
721 }
722 self.irq_counter = self.irq_counter.wrapping_sub(1);
723 }
724
725 fn irq_pending(&self) -> bool {
726 self.irq_pending
727 }
728
729 fn current_mirroring(&self) -> Mirroring {
730 self.mirroring
731 }
732
733 fn debug_info(&self) -> crate::mapper::MapperDebugInfo {
734 let id = match self.variant {
735 FcgVariant::Lz93d50_24c01 => 159,
736 FcgVariant::Lz93d50Wram => 153,
737 _ => 16,
738 };
739 let mut info = crate::mapper::MapperDebugInfo {
740 mapper_id: id,
741 name: format!("Bandai FCG ({id})"),
742 mirroring: crate::mapper::mirroring_name(self.mirroring),
743 ..Default::default()
744 };
745 info.prg_banks
746 .push(("PRG".into(), format!("{:#04x}", self.prg_bank)));
747 for (i, b) in self.chr_banks.iter().enumerate() {
748 info.chr_banks
749 .push((format!("CHR{i}"), format!("{b:#04x}")));
750 }
751 info.irq_state
752 .push(("latch".into(), format!("{:#06x}", self.irq_latch)));
753 info.irq_state
754 .push(("counter".into(), format!("{:#06x}", self.irq_counter)));
755 info.irq_state
756 .push(("enabled".into(), format!("{}", self.irq_enabled)));
757 info.irq_state
758 .push(("pending".into(), format!("{}", self.irq_pending)));
759 info.extra.push((
760 "eeprom".into(),
761 match self.eeprom.as_ref() {
762 Some(ee) => format!("{} bytes", ee.mem.len()),
763 None => "none".into(),
764 },
765 ));
766 info
767 }
768
769 fn save_state(&self) -> Vec<u8> {
770 let ee_len = self.eeprom.as_ref().map_or(0, |e| e.mem.len());
771 let mut out = Vec::with_capacity(
772 18 + self.vram.len() + ee_len + if self.chr_is_ram { self.chr.len() } else { 0 },
773 );
774 out.push(SAVE_STATE_VERSION);
775 out.push(self.prg_bank);
776 out.extend_from_slice(&self.chr_banks);
777 out.push(self.mirroring as u8);
778 out.extend_from_slice(&self.irq_latch.to_le_bytes());
779 out.extend_from_slice(&self.irq_counter.to_le_bytes());
780 out.push(u8::from(self.irq_enabled));
781 out.push(u8::from(self.irq_pending));
782 out.push(self.eeprom_ctrl);
783 if let Some(ee) = self.eeprom.as_ref() {
785 out.extend_from_slice(&ee.mem);
786 }
787 out.extend_from_slice(&self.vram);
788 if self.chr_is_ram {
789 out.extend_from_slice(&self.chr);
790 }
791 out.push(self.outer);
792 out.push(u8::from(self.wram_enabled));
793 out.extend_from_slice(&self.wram);
794 out
795 }
796
797 fn load_state(&mut self, data: &[u8]) -> Result<(), MapperError> {
798 let ee_len = self.eeprom.as_ref().map_or(0, |e| e.mem.len());
799 let need_chr = if self.chr_is_ram { self.chr.len() } else { 0 };
800 let core_len = 18 + self.vram.len() + ee_len + need_chr;
803 let expected = match data.first() {
804 Some(&SAVE_STATE_VERSION) => core_len + 2 + self.wram.len(),
805 Some(&v) => return Err(MapperError::UnsupportedVersion(v)),
806 None => core_len,
807 };
808 if data.len() != expected {
809 return Err(MapperError::WrongLength {
810 expected,
811 got: data.len(),
812 });
813 }
814 self.prg_bank = data[1];
815 self.chr_banks.copy_from_slice(&data[2..10]);
816 self.mirroring = match data[10] {
817 0 => Mirroring::Horizontal,
818 1 => Mirroring::Vertical,
819 2 => Mirroring::SingleScreenA,
820 3 => Mirroring::SingleScreenB,
821 4 => Mirroring::FourScreen,
822 other => return Err(MapperError::Invalid(format!("mirroring {other}"))),
823 };
824 self.irq_latch = u16::from_le_bytes([data[11], data[12]]);
825 self.irq_counter = u16::from_le_bytes([data[13], data[14]]);
826 self.irq_enabled = data[15] != 0;
827 let mut cursor = 16;
833 let pending = data[cursor];
834 cursor += 1;
835 let ctrl = data[cursor];
836 cursor += 1;
837 self.irq_pending = pending != 0;
838 self.eeprom_ctrl = ctrl;
839 if let Some(ee) = self.eeprom.as_mut() {
840 ee.mem.copy_from_slice(&data[cursor..cursor + ee.mem.len()]);
841 cursor += ee.mem.len();
842 }
843 self.vram
844 .copy_from_slice(&data[cursor..cursor + self.vram.len()]);
845 cursor += self.vram.len();
846 if self.chr_is_ram {
847 self.chr
848 .copy_from_slice(&data[cursor..cursor + self.chr.len()]);
849 cursor += self.chr.len();
850 }
851 self.outer = data[cursor] & 0x01;
852 self.wram_enabled = data[cursor + 1] != 0;
853 cursor += 2;
854 self.wram
855 .copy_from_slice(&data[cursor..cursor + self.wram.len()]);
856 Ok(())
857 }
858}
859
860#[cfg(test)]
861#[allow(clippy::cast_possible_truncation)]
862mod tests {
863 use super::*;
864
865 fn synth_prg(banks_16k: usize) -> Box<[u8]> {
866 let mut v = vec![0u8; banks_16k * PRG_BANK_16K];
867 for b in 0..banks_16k {
868 v[b * PRG_BANK_16K] = b as u8;
869 }
870 v.into_boxed_slice()
871 }
872
873 fn synth_chr(banks_1k: usize) -> Box<[u8]> {
874 let mut v = vec![0u8; banks_1k * CHR_BANK_1K];
875 for b in 0..banks_1k {
876 v[b * CHR_BANK_1K] = b as u8;
877 }
878 v.into_boxed_slice()
879 }
880
881 #[test]
882 fn lz93d50_prg_bank_and_fixed_last() {
883 let mut m = BandaiFcg::new(
884 synth_prg(8),
885 synth_chr(16),
886 Mirroring::Vertical,
887 FcgVariant::Lz93d50_24c02,
888 )
889 .unwrap();
890 assert_eq!(m.cpu_read(0x8000), 0);
891 assert_eq!(m.cpu_read(0xC000), 7);
892 m.cpu_write(0x8008, 3);
893 assert_eq!(m.cpu_read(0x8000), 3);
894 assert_eq!(m.cpu_read(0xC000), 7);
895 }
896
897 #[test]
898 fn chr_bank_select_per_1k_slot() {
899 let mut m = BandaiFcg::new(
900 synth_prg(8),
901 synth_chr(16),
902 Mirroring::Vertical,
903 FcgVariant::Lz93d50_24c02,
904 )
905 .unwrap();
906 m.cpu_write(0x8000, 4); m.cpu_write(0x8004, 9); assert_eq!(m.ppu_read(0x0000), 4);
909 assert_eq!(m.ppu_read(0x1000), 9);
910 }
911
912 #[test]
913 fn mirroring_control() {
914 let mut m = BandaiFcg::new(
915 synth_prg(8),
916 synth_chr(16),
917 Mirroring::Vertical,
918 FcgVariant::Lz93d50_24c02,
919 )
920 .unwrap();
921 m.cpu_write(0x8009, 1);
922 assert_eq!(m.current_mirroring(), Mirroring::Horizontal);
923 m.cpu_write(0x8009, 2);
924 assert_eq!(m.current_mirroring(), Mirroring::SingleScreenA);
925 }
926
927 #[test]
928 fn lz93d50_latched_irq_counts_down_to_zero() {
929 let mut m = BandaiFcg::new(
930 synth_prg(8),
931 synth_chr(16),
932 Mirroring::Vertical,
933 FcgVariant::Lz93d50_24c02,
934 )
935 .unwrap();
936 m.cpu_write(0x800B, 0x03);
938 m.cpu_write(0x800C, 0x00);
939 m.cpu_write(0x800A, 0x01);
941 assert_eq!(m.irq_counter, 3);
942 m.notify_cpu_cycle(); m.notify_cpu_cycle(); m.notify_cpu_cycle(); assert!(!m.irq_pending());
946 m.notify_cpu_cycle(); assert!(m.irq_pending());
948 }
949
950 #[test]
951 fn fcg_writes_counter_directly() {
952 let mut m = BandaiFcg::new(
953 synth_prg(8),
954 synth_chr(16),
955 Mirroring::Vertical,
956 FcgVariant::Fcg,
957 )
958 .unwrap();
959 m.cpu_write(0x600B, 0x02);
961 m.cpu_write(0x600C, 0x00);
962 assert_eq!(m.irq_counter, 2);
963 m.cpu_write(0x600A, 0x01); assert_eq!(m.irq_counter, 2);
965 }
966
967 #[test]
968 fn irq_acknowledge_on_control_write() {
969 let mut m = BandaiFcg::new(
970 synth_prg(8),
971 synth_chr(16),
972 Mirroring::Vertical,
973 FcgVariant::Lz93d50_24c02,
974 )
975 .unwrap();
976 m.irq_pending = true;
977 m.cpu_write(0x800A, 0x00); assert!(!m.irq_pending());
979 assert!(!m.irq_enabled);
980 }
981
982 #[test]
983 fn eeprom_present_for_159_absent_for_fcg() {
984 let m159 = BandaiFcg::new(
985 synth_prg(8),
986 synth_chr(16),
987 Mirroring::Vertical,
988 FcgVariant::Lz93d50_24c01,
989 )
990 .unwrap();
991 assert!(m159.eeprom.is_some());
992 assert_eq!(m159.eeprom.as_ref().unwrap().mem.len(), 128);
993 let mfcg = BandaiFcg::new(
994 synth_prg(8),
995 synth_chr(16),
996 Mirroring::Vertical,
997 FcgVariant::Fcg,
998 )
999 .unwrap();
1000 assert!(mfcg.eeprom.is_none());
1001 }
1002
1003 #[test]
1004 fn eeprom_idle_reads_high() {
1005 let mut m = BandaiFcg::new(
1006 synth_prg(8),
1007 synth_chr(16),
1008 Mirroring::Vertical,
1009 FcgVariant::Lz93d50_24c01,
1010 )
1011 .unwrap();
1012 assert_eq!(m.cpu_read(0x6000) & 0x10, 0x10);
1015 }
1016
1017 struct I2cDriver<'a> {
1021 m: &'a mut BandaiFcg,
1022 }
1023
1024 impl I2cDriver<'_> {
1025 const SCL: u8 = 0x20;
1026 const SDA: u8 = 0x40;
1027
1028 fn lines(&mut self, scl: bool, sda: bool) {
1029 let v = (u8::from(scl) * Self::SCL) | (u8::from(sda) * Self::SDA);
1030 self.m.cpu_write(0x800D, v);
1031 }
1032
1033 fn start(&mut self) {
1034 self.lines(true, true);
1036 self.lines(true, false);
1037 }
1038
1039 fn stop(&mut self) {
1040 self.lines(true, false);
1042 self.lines(true, true);
1043 }
1044
1045 fn send_bit(&mut self, bit: bool) {
1047 self.lines(false, bit); self.lines(true, bit); self.lines(false, bit); }
1051
1052 fn recv_bit(&mut self) -> bool {
1054 self.lines(false, true); self.lines(true, true); let out = self.m.eeprom.as_ref().unwrap().read_sda();
1057 self.lines(false, true); out
1059 }
1060
1061 fn send_addr_rw(&mut self, addr: u8, read: bool) {
1063 for i in 0..7 {
1064 self.send_bit((addr >> i) & 1 != 0);
1065 }
1066 self.send_bit(read);
1067 }
1068
1069 fn read_ack(&mut self) -> bool {
1071 !self.recv_bit()
1072 }
1073
1074 fn send_data_lsb_first(&mut self, byte: u8) {
1075 for i in 0..8 {
1076 self.send_bit((byte >> i) & 1 != 0);
1077 }
1078 }
1079
1080 fn recv_data_lsb_first(&mut self) -> u8 {
1081 let mut byte = 0u8;
1082 for i in 0..8 {
1083 if self.recv_bit() {
1084 byte |= 1 << i;
1085 }
1086 }
1087 byte
1088 }
1089 }
1090
1091 #[test]
1092 fn x24c01_write_then_read_round_trips() {
1093 let mut m = BandaiFcg::new(
1094 synth_prg(8),
1095 synth_chr(16),
1096 Mirroring::Vertical,
1097 FcgVariant::Lz93d50_24c01,
1098 )
1099 .unwrap();
1100 {
1101 let mut d = I2cDriver { m: &mut m };
1102 d.start();
1104 d.send_addr_rw(0x12, false); assert!(d.read_ack(), "device must ack the address byte");
1106 d.send_data_lsb_first(0x5A);
1107 assert!(d.read_ack(), "device must ack the data byte");
1108 d.stop();
1109
1110 d.start();
1112 d.send_addr_rw(0x12, true); assert!(d.read_ack(), "device must ack the read address");
1114 let got = d.recv_data_lsb_first();
1115 assert_eq!(got, 0x5A, "read-back must match the written byte");
1116 d.stop();
1117 }
1118 assert_eq!(m.eeprom.as_ref().unwrap().mem[0x12], 0x5A);
1119 }
1120
1121 #[test]
1122 fn save_state_round_trip_with_eeprom() {
1123 let mut m = BandaiFcg::new(
1124 synth_prg(8),
1125 synth_chr(16),
1126 Mirroring::Horizontal,
1127 FcgVariant::Lz93d50_24c02,
1128 )
1129 .unwrap();
1130 m.cpu_write(0x8008, 2);
1131 m.cpu_write(0x8000, 5);
1132 m.cpu_write(0x800B, 0x10);
1133 m.cpu_write(0x800A, 0x01);
1134 if let Some(ee) = m.eeprom.as_mut() {
1135 ee.mem[0] = 0x42;
1136 }
1137 let blob = m.save_state();
1138 let mut m2 = BandaiFcg::new(
1139 synth_prg(8),
1140 synth_chr(16),
1141 Mirroring::Horizontal,
1142 FcgVariant::Lz93d50_24c02,
1143 )
1144 .unwrap();
1145 m2.load_state(&blob).unwrap();
1146 assert_eq!(m.cpu_read(0x8000), m2.cpu_read(0x8000));
1147 assert_eq!(m.ppu_read(0x0000), m2.ppu_read(0x0000));
1148 assert_eq!(m.irq_counter, m2.irq_counter);
1149 assert_eq!(m2.eeprom.as_ref().unwrap().mem[0], 0x42);
1150 }
1151
1152 #[test]
1161 fn eeprom_address_wraps_at_the_top_of_each_chip() {
1162 for (bytes, is_x24c01, top) in [(256usize, false, 0xFFu8), (128, true, 0x7F)] {
1163 let mut e = Eeprom::new(bytes, is_x24c01);
1164 e.addr = top;
1165 e.data = 0xA5;
1166 e.counter = 8;
1167 e.mode = I2cMode::Write;
1168 e.clock_fall(0);
1170 assert_eq!(e.addr, 0, "x24c01={is_x24c01}: ${top:02X} + 1 wraps to $00");
1171 }
1172 }
1173
1174 #[test]
1175 fn eeprom_sequential_read_wraps_at_the_top_of_each_chip() {
1176 for (bytes, is_x24c01, top) in [(256usize, false, 0xFFu8), (128, true, 0x7F)] {
1177 let mut e = Eeprom::new(bytes, is_x24c01);
1178 e.addr = top;
1179 e.counter = 8;
1180 e.mode = I2cMode::Read;
1181 e.clock_fall(0);
1182 assert_eq!(e.addr, 0, "x24c01={is_x24c01}: read rollover reaches $00");
1183 }
1184 }
1185
1186 fn m153(banks_16k: usize) -> BandaiFcg {
1189 BandaiFcg::new(
1190 synth_prg(banks_16k),
1191 Box::new([]),
1192 Mirroring::Vertical,
1193 FcgVariant::Lz93d50Wram,
1194 )
1195 .unwrap()
1196 }
1197
1198 #[test]
1199 fn m153_outer_bank_from_8000_to_8003_and_fixed_last_of_the_block() {
1200 let mut m = m153(32); m.cpu_write(0x8008, 3);
1202 assert_eq!(m.cpu_read(0x8000), 3);
1203 assert_eq!(m.cpu_read(0xC000), 15, "last bank of the first 256 KiB");
1204 for a in 0x8000..=0x8003u16 {
1205 m.cpu_write(a, 1);
1206 }
1207 assert_eq!(m.cpu_read(0x8000), 16 + 3);
1208 assert_eq!(m.cpu_read(0xC000), 31);
1209 m.cpu_write(0x8004, 0);
1211 assert_eq!(m.cpu_read(0x8000), 16 + 3);
1212 }
1213
1214 #[test]
1215 fn m153_wram_follows_the_800d_chip_enable() {
1216 let mut m = m153(32);
1217 assert_eq!(m.sram().len(), WRAM_153, "the battery save is the WRAM");
1218 assert!(m.cpu_read_unmapped(0x6000), "disabled at power-on");
1219 m.cpu_write(0x6000, 0x11);
1220 m.cpu_write(0x800D, 0x20);
1221 assert!(!m.cpu_read_unmapped(0x6000));
1222 assert_eq!(m.cpu_read(0x6000), 0, "the disabled write was dropped");
1223 m.cpu_write(0x7FFF, 0x22);
1224 assert_eq!(m.cpu_read(0x7FFF), 0x22);
1225 m.cpu_write(0x800D, 0x00);
1226 assert!(m.cpu_read_unmapped(0x7FFF));
1227 }
1228
1229 #[test]
1230 fn m153_chr_is_unbanked_ram_and_irq_is_the_lz93d50s() {
1231 let mut m = m153(32);
1232 m.cpu_write(0x8000, 1); m.ppu_write(0x0005, 0x9A);
1234 assert_eq!(m.ppu_read(0x0005), 0x9A);
1235 m.cpu_write(0x800B, 2);
1236 m.cpu_write(0x800C, 0);
1237 m.cpu_write(0x800A, 1);
1238 for _ in 0..3 {
1239 m.notify_cpu_cycle();
1240 }
1241 assert!(m.irq_pending(), "latched counter 2 reaches zero");
1242 }
1243
1244 #[test]
1251 fn m153_chr_ram_is_8k_with_no_aliasing_between_1k_windows() {
1252 let mut m = m153(32);
1253 for a in 0x8000..=0x8007u16 {
1256 m.cpu_write(a, 0x05);
1257 }
1258 for slot in 0..8u16 {
1259 m.ppu_write(slot * 0x400 + 0x123, 0xA0 | slot as u8);
1260 }
1261 for slot in 0..8u16 {
1262 assert_eq!(
1263 m.ppu_read(slot * 0x400 + 0x123),
1264 0xA0 | slot as u8,
1265 "1 KiB window {slot} aliased another window"
1266 );
1267 }
1268 m.ppu_write(0x1FFF, 0x5A);
1269 assert_eq!(m.ppu_read(0x1FFF), 0x5A);
1270 assert_eq!(m.ppu_read(0x03FF), 0, "$1FFF must not alias $03FF");
1271 }
1272
1273 #[test]
1274 fn m153_state_round_trips_and_v1_is_refused() {
1275 let mut a = m153(32);
1276 a.cpu_write(0x8001, 1);
1277 a.cpu_write(0x800D, 0x20);
1278 a.cpu_write(0x6123, 0x44);
1279 let blob = a.save_state();
1280 let mut b = m153(32);
1281 b.load_state(&blob).unwrap();
1282 assert_eq!(b.cpu_read(0x6123), 0x44);
1283 assert_eq!(b.cpu_read(0xC000), 31);
1284 assert_eq!(b.save_state(), blob);
1285 let mut v1 = blob.clone();
1286 v1[0] = 1;
1287 v1.truncate(v1.len() - 2 - WRAM_153);
1288 assert!(
1289 b.load_state(&v1).is_err(),
1290 "a v1 blob has no WRAM to restore"
1291 );
1292 let mut fcg = BandaiFcg::new(
1295 synth_prg(8),
1296 synth_chr(128),
1297 Mirroring::Vertical,
1298 FcgVariant::Fcg,
1299 )
1300 .unwrap();
1301 let mut v1 = fcg.save_state();
1302 v1[0] = 1;
1303 v1.truncate(v1.len() - 2);
1304 assert!(matches!(
1305 fcg.load_state(&v1),
1306 Err(MapperError::UnsupportedVersion(1))
1307 ));
1308 }
1309}