Skip to main content

nescore/mapper/
unrom.rs

1//
2// mapper/unrom.rs
3//
4// @author Natesh Narain <nnaraindev@gmail.com>
5// @date Dec 27 2019
6//
7
8// +-----------------+
9// ¦ Mapper 2: UNROM ¦
10// +-----------------+
11// 
12// +---------------+         +------------------------------------------+
13// ¦ $8000 - $FFFF +---------¦ PPPPPPPP                                 ¦
14// +---------------+         ¦ +------+                                 ¦
15//                           ¦    ¦                                     ¦
16//                           ¦    ¦                                     ¦
17//                           ¦    +------- Select 16K ROM bank at $8000 ¦
18//                           +------------------------------------------+
19// 
20// Notes: - When the cart is first started, the first 16K ROM bank in the cart
21//           is loaded into $8000, and the LAST 16K ROM bank is loaded into
22//           $C000. This last 16K bank is permanently "hard-wired" to $C000,
23//           and it cannot be swapped.
24//        - This mapper has no provisions for VROM; therefore, all carts
25//           using it have 8K of VRAM at PPU $0000.
26//        - Most carts with this mapper are 128K. A few, mostly Japanese
27//           carts, such as Final Fantasy 2 and Dragon Quest 3, are 256K.
28//        - Overall, this is one of the easiest mappers to implement in
29//           a NES emulator.
30// http://tuxnes.sourceforge.net/mappers-0.80.txt
31
32use super::MapperControl;
33use super::mem::Memory;
34use crate::cart::{Cartridge, PRG_ROM_BANK_SIZE};
35
36const CHR_RAM_SIZE: usize = kb!(8);
37
38/// UNROM Mapper
39pub struct Unrom {
40    prg_rom: Memory,
41    chr_ram: [u8; CHR_RAM_SIZE],
42    rom_bank_selection: usize, // Select ROM bank
43}
44
45
46impl From<Cartridge> for Unrom {
47    fn from(cart: Cartridge) -> Self {
48        // Extract info and ROM data, VROM is unused
49        let (_, prg_rom, _, _) = cart.into_parts();
50
51        Unrom{
52            prg_rom: Memory::new(prg_rom, PRG_ROM_BANK_SIZE),
53            chr_ram: [0; CHR_RAM_SIZE],
54            rom_bank_selection: 0,
55        }
56    }
57}
58
59impl MapperControl for Unrom {
60    fn read(&self, addr: u16) -> u8 {
61        match addr {
62            0x8000..=0xBFFF => {
63                self.prg_rom.read(self.rom_bank_selection, (addr - 0x8000) as usize)
64            },
65            0xC000..=0xFFFF => {
66                self.prg_rom.read_last((addr - 0xC000) as usize)
67            }
68            _ => { 0 }
69        }
70    }
71
72    fn write(&mut self, addr: u16, data: u8) {
73        if let 0x8000..=0xFFFF = addr {
74            self.rom_bank_selection = (data & 0x0F) as usize;
75        }
76    }
77
78    fn read_chr(&self, addr: u16) -> u8 {
79        self.chr_ram[addr as usize]
80    }
81
82    fn write_chr(&mut self, addr: u16, value: u8) {
83        self.chr_ram[addr as usize] = value;
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn bank_select() {
93        let mut data = vec![0; 20];
94        data[10] = 0xDE;
95
96        let mut unrom = init_unrom(data, 10);
97        // Select ROM bank 1
98        unrom.write(0x8000, 1);
99
100        assert_eq!(unrom.read(0x8000), 0xDE);
101    }
102
103    #[test]
104    fn irq() {
105        let mut prg = vec![0; PRG_ROM_BANK_SIZE * 2];
106        prg[(PRG_ROM_BANK_SIZE * 2)-2] = 0x01;
107        prg[(PRG_ROM_BANK_SIZE * 2)-1] = 0x60;
108
109        let unrom = init_unrom(prg, PRG_ROM_BANK_SIZE);
110        let irq_lo = unrom.read(0xFFFE) as u16;
111        let irq_hi = unrom.read(0xFFFF) as u16;
112
113        let irq = (irq_hi << 8) | irq_lo;
114
115        assert_eq!(irq, 0x6001);
116    }
117
118    fn init_unrom(data: Vec<u8>, bank_size: usize) -> Unrom {
119        Unrom {
120            prg_rom: Memory::new(data, bank_size),
121            chr_ram: [0; CHR_RAM_SIZE],
122            rom_bank_selection: 0,
123        }
124    }
125}