nes_core/ppu/
bus.rs

1use alloc::vec;
2use alloc::vec::Vec;
3use super::Ppu;
4use crate::{
5    common::{ResetKind, NesRegion, Regional, Reset},
6    mapper::{Mapped, MappedRead, MappedWrite, Mapper, MemMap},
7    mem::{Access, Mem},
8    ppu::Mirroring,
9};
10use serde::{Deserialize, Serialize};
11
12pub trait PpuAddr {
13    /// Returns whether this value can be used to fetch a nametable attribute byte.
14    fn is_attr(&self) -> bool;
15    fn is_palette(&self) -> bool;
16}
17
18impl PpuAddr for u16 {
19    #[inline]
20    fn is_attr(&self) -> bool {
21        (*self & 0x03FF) >= 0x03C0
22    }
23    #[inline]
24    fn is_palette(&self) -> bool {
25        *self >= 0x3F00
26    }
27}
28
29#[derive(Clone, Serialize, Deserialize)]
30#[must_use]
31pub struct PpuBus {
32    mapper: Mapper,
33    mirror_shift: usize,
34    ciram: Vec<u8>, // $2007 PPUDATA
35    palette: [u8; Self::PALETTE_SIZE],
36    chr_rom: Vec<u8>,
37    chr_ram: Vec<u8>,
38    exram: Vec<u8>,
39    open_bus: u8,
40}
41
42impl Default for PpuBus {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl PpuBus {
49    const VRAM_SIZE: usize = 0x0800; // Two 1k Nametables
50    const PALETTE_SIZE: usize = 32; // 32 possible colors at a time
51
52    pub fn new() -> Self {
53        Self {
54            mapper: Mapper::none(),
55            mirror_shift: Mirroring::default() as usize,
56            ciram: vec![0x00; Self::VRAM_SIZE],
57            palette: [0x00; Self::PALETTE_SIZE],
58            chr_rom: vec![],
59            chr_ram: vec![],
60            exram: vec![],
61            open_bus: 0x00,
62        }
63    }
64
65    #[inline]
66    pub fn mirroring(&self) -> Mirroring {
67        self.mapper.mirroring()
68    }
69
70    #[inline]
71    pub fn update_mirroring(&mut self) {
72        self.mirror_shift = self.mirroring() as usize;
73    }
74
75    #[inline]
76    pub fn load_chr_rom(&mut self, chr_rom: Vec<u8>) {
77        self.chr_rom = chr_rom;
78    }
79
80    #[inline]
81    pub fn load_chr_ram(&mut self, chr_ram: Vec<u8>) {
82        self.chr_ram = chr_ram;
83    }
84
85    #[inline]
86    pub fn load_ex_ram(&mut self, ex_ram: Vec<u8>) {
87        self.exram = ex_ram;
88    }
89
90    #[inline]
91    pub fn load_mapper(&mut self, mapper: Mapper) {
92        self.mapper = mapper;
93    }
94
95    #[inline]
96    pub const fn mapper(&self) -> &Mapper {
97        &self.mapper
98    }
99
100    #[inline]
101    pub fn mapper_mut(&mut self) -> &mut Mapper {
102        &mut self.mapper
103    }
104
105    // Maps addresses to nametable pages based on mirroring mode
106    //
107    // Vram:            [ A ] [ B ]
108    //
109    // Horizontal:      [ A ] [ a ]
110    //                  [ B ] [ b ]
111    //
112    // Vertical:        [ A ] [ B ]
113    //                  [ a ] [ b ]
114    //
115    // Single Screen A: [ A ] [ a ]
116    //                  [ a ] [ a ]
117    //
118    // Single Screen B: [ b ] [ B ]
119    //                  [ b ] [ b ]
120    //
121    // Fourscreen should not use this method and instead should rely on mapper translation.
122    #[inline]
123    const fn ciram_mirror(&self, addr: usize) -> usize {
124        let nametable = (addr >> self.mirror_shift) & (Ppu::NT_SIZE as usize);
125        (nametable) | (!nametable & addr & 0x03FF)
126    }
127
128    #[inline]
129    const fn palette_mirror(&self, addr: usize) -> usize {
130        let addr = addr & 0x001F;
131        if addr >= 16 && addr.trailing_zeros() >= 2 {
132            addr - 16
133        } else {
134            addr
135        }
136    }
137}
138
139impl Mem for PpuBus {
140    fn read(&mut self, addr: u16, _access: Access) -> u8 {
141        let val = match addr {
142            0x0000..=0x1FFF => {
143                let addr = if let MappedRead::Chr(addr) = self.mapper.map_read(addr) {
144                    addr
145                } else {
146                    addr.into()
147                };
148                if self.chr_rom.is_empty() {
149                    self.chr_ram[addr]
150                } else {
151                    self.chr_rom[addr]
152                }
153            }
154            0x2000..=0x3EFF => match self.mapper.map_read(addr) {
155                MappedRead::CIRam(addr) => self.ciram[addr & 0x07FF],
156                MappedRead::ExRam(addr) => self.exram[addr & 0x03FF],
157                MappedRead::Data(data) => data,
158                _ => {
159                    if self.mirroring() == Mirroring::FourScreen {
160                        0x00
161                    } else {
162                        self.ciram[self.ciram_mirror(addr as usize)]
163                    }
164                }
165            },
166            0x3F00..=0x3FFF => self.palette[self.palette_mirror(addr as usize)],
167            _ => {
168                log::error!("unexpected PPU memory access at ${:04X}", addr);
169                0x00
170            }
171        };
172        self.open_bus = val;
173        val
174    }
175
176    fn peek(&self, addr: u16, _access: Access) -> u8 {
177        match addr {
178            0x2000..=0x3EFF => match self.mapper.map_peek(addr) {
179                MappedRead::CIRam(addr) => self.ciram[addr & 0x07FF],
180                MappedRead::ExRam(addr) => self.exram[addr & 0x03FF],
181                MappedRead::Data(data) => data,
182                _ => {
183                    if self.mirroring() == Mirroring::FourScreen {
184                        0x00
185                    } else {
186                        self.ciram[self.ciram_mirror(addr as usize)]
187                    }
188                }
189            },
190            0x0000..=0x1FFF => {
191                let addr = if let MappedRead::Chr(addr) = self.mapper.map_peek(addr) {
192                    addr
193                } else {
194                    addr.into()
195                };
196                if !self.chr_ram.is_empty() {
197                    self.chr_ram[addr]
198                } else {
199                    self.chr_rom[addr]
200                }
201            }
202            0x3F00..=0x3FFF => self.palette[self.palette_mirror(addr as usize)],
203            _ => {
204                log::error!("unexpected PPU memory access at ${:04X}", addr);
205                0x00
206            }
207        }
208    }
209
210    fn write(&mut self, addr: u16, val: u8, _access: Access) {
211        match addr {
212            0x2000..=0x3EFF => match self.mapper.map_write(addr, val) {
213                MappedWrite::CIRam(addr, val) => self.ciram[addr] = val,
214                MappedWrite::ExRam(addr, val) => self.exram[addr] = val,
215                _ => {
216                    if self.mirroring() != Mirroring::FourScreen {
217                        let addr = self.ciram_mirror(addr as usize);
218                        self.ciram[addr] = val;
219                    }
220                }
221            },
222            0x0000..=0x1FFF => {
223                if !self.chr_ram.is_empty() {
224                    if let MappedWrite::Chr(addr, val) = self.mapper.map_write(addr, val) {
225                        self.chr_ram[addr] = val;
226                    }
227                }
228            }
229            0x3F00..=0x3FFF => {
230                self.palette[self.palette_mirror(addr as usize)] = val;
231            }
232            _ => log::error!("unexpected PPU memory access at ${:04X}", addr),
233        }
234        self.mapper.ppu_bus_write(addr, val);
235        self.open_bus = val;
236    }
237}
238
239impl Regional for PpuBus {
240    #[inline]
241    fn region(&self) -> NesRegion {
242        self.mapper.region()
243    }
244
245    fn set_region(&mut self, region: NesRegion) {
246        self.mapper.set_region(region);
247    }
248}
249
250impl Reset for PpuBus {
251    fn reset(&mut self, kind: ResetKind) {
252        self.open_bus = 0x00;
253        self.mapper.reset(kind);
254    }
255}
256
257impl core::fmt::Debug for PpuBus {
258    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
259        f.debug_struct("PpuBus")
260            .field("mapper", &self.mapper)
261            .field("ciram_len", &self.ciram.len())
262            .field("palette_len", &self.palette.len())
263            .field("chr_rom_len", &self.chr_rom.len())
264            .field("chr_ram_len", &self.chr_ram.len())
265            .field("ex_ram_len", &self.exram.len())
266            .field("open_bus", &self.open_bus)
267            .finish()
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274
275    #[test]
276    fn ciram_mirror_horizontal() {
277        let mut ppu_bus = PpuBus::new();
278        ppu_bus.mirror_shift = Mirroring::Horizontal as usize;
279
280        assert_eq!(ppu_bus.ciram_mirror(0x2000), 0x0000);
281        assert_eq!(ppu_bus.ciram_mirror(0x2005), 0x0005);
282        assert_eq!(ppu_bus.ciram_mirror(0x23FF), 0x03FF);
283        assert_eq!(ppu_bus.ciram_mirror(0x2400), 0x0000);
284        assert_eq!(ppu_bus.ciram_mirror(0x2405), 0x0005);
285        assert_eq!(ppu_bus.ciram_mirror(0x27FF), 0x03FF);
286
287        assert_eq!(ppu_bus.ciram_mirror(0x2800), 0x0400);
288        assert_eq!(ppu_bus.ciram_mirror(0x2805), 0x0405);
289        assert_eq!(ppu_bus.ciram_mirror(0x2BFF), 0x07FF);
290        assert_eq!(ppu_bus.ciram_mirror(0x2C00), 0x0400);
291        assert_eq!(ppu_bus.ciram_mirror(0x2C05), 0x0405);
292        assert_eq!(ppu_bus.ciram_mirror(0x2FFF), 0x07FF);
293    }
294
295    #[test]
296    fn ciram_mirror_vertical() {
297        let mut ppu_bus = PpuBus::new();
298        ppu_bus.mirror_shift = Mirroring::Vertical as usize;
299
300        assert_eq!(ppu_bus.ciram_mirror(0x2000), 0x0000);
301        assert_eq!(ppu_bus.ciram_mirror(0x2005), 0x0005);
302        assert_eq!(ppu_bus.ciram_mirror(0x23FF), 0x03FF);
303        assert_eq!(ppu_bus.ciram_mirror(0x2800), 0x0000);
304        assert_eq!(ppu_bus.ciram_mirror(0x2805), 0x0005);
305        assert_eq!(ppu_bus.ciram_mirror(0x2BFF), 0x03FF);
306
307        assert_eq!(ppu_bus.ciram_mirror(0x2400), 0x0400);
308        assert_eq!(ppu_bus.ciram_mirror(0x2405), 0x0405);
309        assert_eq!(ppu_bus.ciram_mirror(0x27FF), 0x07FF);
310        assert_eq!(ppu_bus.ciram_mirror(0x2C00), 0x0400);
311        assert_eq!(ppu_bus.ciram_mirror(0x2C05), 0x0405);
312        assert_eq!(ppu_bus.ciram_mirror(0x2FFF), 0x07FF);
313    }
314
315    #[test]
316    fn ciram_mirror_single_screen_a() {
317        let mut ppu_bus = PpuBus::new();
318        ppu_bus.mirror_shift = Mirroring::SingleScreenA as usize;
319
320        assert_eq!(ppu_bus.ciram_mirror(0x2000), 0x0000);
321        assert_eq!(ppu_bus.ciram_mirror(0x2005), 0x0005);
322        assert_eq!(ppu_bus.ciram_mirror(0x23FF), 0x03FF);
323        assert_eq!(ppu_bus.ciram_mirror(0x2800), 0x0000);
324        assert_eq!(ppu_bus.ciram_mirror(0x2805), 0x0005);
325        assert_eq!(ppu_bus.ciram_mirror(0x2BFF), 0x03FF);
326        assert_eq!(ppu_bus.ciram_mirror(0x2400), 0x0000);
327        assert_eq!(ppu_bus.ciram_mirror(0x2405), 0x0005);
328        assert_eq!(ppu_bus.ciram_mirror(0x27FF), 0x03FF);
329        assert_eq!(ppu_bus.ciram_mirror(0x2C00), 0x0000);
330        assert_eq!(ppu_bus.ciram_mirror(0x2C05), 0x0005);
331        assert_eq!(ppu_bus.ciram_mirror(0x2FFF), 0x03FF);
332    }
333
334    #[test]
335    fn ciram_mirror_single_screen_b() {
336        let mut ppu_bus = PpuBus::new();
337        ppu_bus.mirror_shift = Mirroring::SingleScreenB as usize;
338
339        assert_eq!(ppu_bus.ciram_mirror(0x2000), 0x0400);
340        assert_eq!(ppu_bus.ciram_mirror(0x2005), 0x0405);
341        assert_eq!(ppu_bus.ciram_mirror(0x23FF), 0x07FF);
342        assert_eq!(ppu_bus.ciram_mirror(0x2800), 0x0400);
343        assert_eq!(ppu_bus.ciram_mirror(0x2805), 0x0405);
344        assert_eq!(ppu_bus.ciram_mirror(0x2BFF), 0x07FF);
345        assert_eq!(ppu_bus.ciram_mirror(0x2400), 0x0400);
346        assert_eq!(ppu_bus.ciram_mirror(0x2405), 0x0405);
347        assert_eq!(ppu_bus.ciram_mirror(0x27FF), 0x07FF);
348        assert_eq!(ppu_bus.ciram_mirror(0x2C00), 0x0400);
349        assert_eq!(ppu_bus.ciram_mirror(0x2C05), 0x0405);
350        assert_eq!(ppu_bus.ciram_mirror(0x2FFF), 0x07FF);
351    }
352}