tetanes_core/ppu/
bus.rs

1//! PPU Memory/Data Bus.
2
3use crate::{
4    common::{NesRegion, Regional, Reset, ResetKind},
5    mapper::{BusKind, MapRead, MapWrite, MappedRead, MappedWrite, Mapper, Mirrored, OnBusWrite},
6    mem::{ConstSlice, Memory, RamState, Read, Write},
7    ppu::{Mirroring, Ppu},
8};
9use serde::{Deserialize, Serialize};
10use tracing::error;
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    fn is_attr(&self) -> bool {
20        (*self & (Ppu::NT_SIZE - 1)) >= Ppu::ATTR_OFFSET
21    }
22
23    fn is_palette(&self) -> bool {
24        *self >= Ppu::PALETTE_START
25    }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[must_use]
30pub struct Bus {
31    pub mapper: Mapper,
32    pub chr: Memory<Vec<u8>>,
33    pub ciram: Memory<ConstSlice<u8, 0x0800>>, // $2007 PPUDATA
34    pub palette: Memory<ConstSlice<u8, 32>>,
35    #[serde(skip)]
36    pub exram: Memory<Vec<u8>>,
37    pub open_bus: u8,
38}
39
40impl Default for Bus {
41    fn default() -> Self {
42        Self::new(RamState::default())
43    }
44}
45
46impl Bus {
47    pub const VRAM_SIZE: usize = 0x0800; // Two 1k Nametables
48    pub const PALETTE_SIZE: usize = 32; // 32 possible colors at a time
49
50    pub fn new(ram_state: RamState) -> Self {
51        Self {
52            mapper: Mapper::none(),
53            chr: Memory::rom(),
54            ciram: Memory::ram_const(ram_state),
55            palette: Memory::ram_const(ram_state),
56            exram: Memory::ram(ram_state),
57            open_bus: 0x00,
58        }
59    }
60
61    pub fn mirroring(&self) -> Mirroring {
62        self.mapper.mirroring()
63    }
64
65    pub fn load_chr(&mut self, chr: Memory<Vec<u8>>) {
66        self.chr = chr;
67    }
68
69    pub fn load_ex_ram(&mut self, ex_ram: Memory<Vec<u8>>) {
70        self.exram = ex_ram;
71    }
72
73    // Maps addresses to nametable pages based on mirroring mode
74    //
75    // Vram:            [ A ] [ B ]
76    //
77    // Horizontal:      [ A ] [ a ]
78    //                  [ B ] [ b ]
79    //
80    // Vertical:        [ A ] [ B ]
81    //                  [ a ] [ b ]
82    //
83    // Single Screen A: [ A ] [ a ]
84    //                  [ a ] [ a ]
85    //
86    // Single Screen B: [ b ] [ B ]
87    //                  [ b ] [ b ]
88    //
89    // Fourscreen should not use this method and instead should rely on mapper translation.
90
91    pub const fn ciram_mirror(addr: u16, mirroring: Mirroring) -> usize {
92        let nametable = (addr >> mirroring as u16) & Ppu::NT_SIZE;
93        (nametable | (!nametable & addr & 0x03FF)) as usize
94    }
95
96    const fn palette_mirror(&self, addr: u16) -> usize {
97        let addr = addr & 0x001F;
98        let addr = if addr >= 16 && addr.trailing_zeros() >= 2 {
99            addr - 16
100        } else {
101            addr
102        };
103        addr as usize
104    }
105
106    pub fn read_ciram(&mut self, addr: u16) -> u8 {
107        match self.mapper.map_read(addr) {
108            MappedRead::Bus => self.ciram[Self::ciram_mirror(addr, self.mirroring())],
109            MappedRead::CIRam(addr) => self.ciram[addr],
110            MappedRead::ExRam(addr) => self.exram[addr],
111            MappedRead::Data(data) => data,
112            MappedRead::Chr(addr) => self.chr[addr],
113            MappedRead::PrgRom(mapped) => {
114                tracing::warn!("unexpected mapped PRG-ROM read at ${addr:04X} ${mapped:04X}");
115                0
116            }
117            MappedRead::PrgRam(mapped) => {
118                tracing::warn!("unexpected mapped PRG-RAM read at ${addr:04X} ${mapped:04X}");
119                0
120            }
121        }
122    }
123
124    pub fn peek_ciram(&self, addr: u16) -> u8 {
125        match self.mapper.map_peek(addr) {
126            MappedRead::Bus => self.ciram[Self::ciram_mirror(addr, self.mirroring())],
127            MappedRead::CIRam(addr) => self.ciram[addr],
128            MappedRead::ExRam(addr) => self.exram[addr],
129            MappedRead::Data(data) => data,
130            MappedRead::Chr(addr) => self.chr[addr],
131            MappedRead::PrgRom(mapped) => {
132                tracing::warn!("unexpected mapped PRG-ROM read at ${addr:04X} ${mapped:04X}");
133                0
134            }
135            MappedRead::PrgRam(mapped) => {
136                tracing::warn!("unexpected mapped PRG-RAM read at ${addr:04X} ${mapped:04X}");
137                0
138            }
139        }
140    }
141
142    pub fn read_chr(&mut self, addr: u16) -> u8 {
143        let addr = if let MappedRead::Chr(addr) = self.mapper.map_read(addr) {
144            addr
145        } else {
146            addr.into()
147        };
148        self.chr[addr]
149    }
150
151    pub fn peek_chr(&self, addr: u16) -> u8 {
152        let addr = if let MappedRead::Chr(addr) = self.mapper.map_peek(addr) {
153            addr
154        } else {
155            addr.into()
156        };
157        self.chr[addr]
158    }
159
160    #[inline]
161    #[allow(clippy::missing_const_for_fn)] // false positive on non-const deref coercion
162    pub fn peek_palette(&self, addr: u16) -> u8 {
163        self.palette[self.palette_mirror(addr)]
164    }
165}
166
167impl Read for Bus {
168    fn read(&mut self, addr: u16) -> u8 {
169        let val = match addr {
170            0x2000..=0x3EFF => self.read_ciram(addr),
171            0x0000..=0x1FFF => self.read_chr(addr),
172            0x3F00..=0x3FFF => self.peek_palette(addr),
173            _ => {
174                error!("unexpected PPU memory access at ${:04X}", addr);
175                0x00
176            }
177        };
178        self.open_bus = val;
179        val
180    }
181
182    fn peek(&self, addr: u16) -> u8 {
183        match addr {
184            0x2000..=0x3EFF => self.peek_ciram(addr),
185            0x0000..=0x1FFF => self.peek_chr(addr),
186            0x3F00..=0x3FFF => self.peek_palette(addr),
187            _ => {
188                error!("unexpected PPU memory access at ${:04X}", addr);
189                0x00
190            }
191        }
192    }
193}
194
195impl Write for Bus {
196    fn write(&mut self, addr: u16, val: u8) {
197        match addr {
198            0x0000..=0x3EFF => match self.mapper.map_write(addr, val) {
199                MappedWrite::Bus => {
200                    let addr = Self::ciram_mirror(addr, self.mirroring());
201                    self.ciram[addr] = val;
202                }
203                MappedWrite::CIRam(addr, val) => self.ciram[addr] = val,
204                MappedWrite::ExRam(addr, val) => self.exram[addr] = val,
205                MappedWrite::ChrRam(addr, val) => {
206                    if self.chr.is_ram() {
207                        self.chr[addr] = val;
208                    }
209                }
210                MappedWrite::PrgRam(mapped, val) => {
211                    tracing::warn!(
212                        "unexpected mapped PRG-RAM write at ${addr:04X} for ${mapped:04X} with ${val:02X}"
213                    );
214                }
215                MappedWrite::PrgRamProtect(val) => {
216                    tracing::warn!(
217                        "unexpected mapped PRG-RAM Protect write at ${addr:04X} with {val}"
218                    );
219                }
220                MappedWrite::None => (),
221            },
222            0x3F00..=0x3FFF => {
223                let addr = self.palette_mirror(addr);
224                self.palette[addr] = val;
225            }
226            _ => error!("unexpected PPU memory access at ${:04X}", addr),
227        }
228        self.mapper.on_bus_write(addr, val, BusKind::Ppu);
229        self.open_bus = val;
230    }
231}
232
233impl Regional for Bus {
234    fn region(&self) -> NesRegion {
235        self.mapper.region()
236    }
237
238    fn set_region(&mut self, region: NesRegion) {
239        self.mapper.set_region(region);
240    }
241}
242
243impl Reset for Bus {
244    fn reset(&mut self, kind: ResetKind) {
245        self.open_bus = 0x00;
246        if self.chr.is_ram() {
247            self.chr.reset(kind);
248        }
249        self.mapper.reset(kind);
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn ciram_mirror_horizontal() {
259        assert_eq!(Bus::ciram_mirror(0x2000, Mirroring::Horizontal), 0x0000);
260        assert_eq!(Bus::ciram_mirror(0x2005, Mirroring::Horizontal), 0x0005);
261        assert_eq!(Bus::ciram_mirror(0x23FF, Mirroring::Horizontal), 0x03FF);
262        assert_eq!(Bus::ciram_mirror(0x2400, Mirroring::Horizontal), 0x0000);
263        assert_eq!(Bus::ciram_mirror(0x2405, Mirroring::Horizontal), 0x0005);
264        assert_eq!(Bus::ciram_mirror(0x27FF, Mirroring::Horizontal), 0x03FF);
265        assert_eq!(Bus::ciram_mirror(0x2800, Mirroring::Horizontal), 0x0400);
266        assert_eq!(Bus::ciram_mirror(0x2805, Mirroring::Horizontal), 0x0405);
267        assert_eq!(Bus::ciram_mirror(0x2BFF, Mirroring::Horizontal), 0x07FF);
268        assert_eq!(Bus::ciram_mirror(0x2C00, Mirroring::Horizontal), 0x0400);
269        assert_eq!(Bus::ciram_mirror(0x2C05, Mirroring::Horizontal), 0x0405);
270        assert_eq!(Bus::ciram_mirror(0x2FFF, Mirroring::Horizontal), 0x07FF);
271    }
272
273    #[test]
274    fn ciram_mirror_vertical() {
275        assert_eq!(Bus::ciram_mirror(0x2000, Mirroring::Vertical), 0x0000);
276        assert_eq!(Bus::ciram_mirror(0x2005, Mirroring::Vertical), 0x0005);
277        assert_eq!(Bus::ciram_mirror(0x23FF, Mirroring::Vertical), 0x03FF);
278        assert_eq!(Bus::ciram_mirror(0x2800, Mirroring::Vertical), 0x0000);
279        assert_eq!(Bus::ciram_mirror(0x2805, Mirroring::Vertical), 0x0005);
280        assert_eq!(Bus::ciram_mirror(0x2BFF, Mirroring::Vertical), 0x03FF);
281        assert_eq!(Bus::ciram_mirror(0x2400, Mirroring::Vertical), 0x0400);
282        assert_eq!(Bus::ciram_mirror(0x2405, Mirroring::Vertical), 0x0405);
283        assert_eq!(Bus::ciram_mirror(0x27FF, Mirroring::Vertical), 0x07FF);
284        assert_eq!(Bus::ciram_mirror(0x2C00, Mirroring::Vertical), 0x0400);
285        assert_eq!(Bus::ciram_mirror(0x2C05, Mirroring::Vertical), 0x0405);
286        assert_eq!(Bus::ciram_mirror(0x2FFF, Mirroring::Vertical), 0x07FF);
287    }
288
289    #[test]
290    fn ciram_mirror_single_screen_a() {
291        assert_eq!(Bus::ciram_mirror(0x2000, Mirroring::SingleScreenA), 0x0000);
292        assert_eq!(Bus::ciram_mirror(0x2005, Mirroring::SingleScreenA), 0x0005);
293        assert_eq!(Bus::ciram_mirror(0x23FF, Mirroring::SingleScreenA), 0x03FF);
294        assert_eq!(Bus::ciram_mirror(0x2800, Mirroring::SingleScreenA), 0x0000);
295        assert_eq!(Bus::ciram_mirror(0x2805, Mirroring::SingleScreenA), 0x0005);
296        assert_eq!(Bus::ciram_mirror(0x2BFF, Mirroring::SingleScreenA), 0x03FF);
297        assert_eq!(Bus::ciram_mirror(0x2400, Mirroring::SingleScreenA), 0x0000);
298        assert_eq!(Bus::ciram_mirror(0x2405, Mirroring::SingleScreenA), 0x0005);
299        assert_eq!(Bus::ciram_mirror(0x27FF, Mirroring::SingleScreenA), 0x03FF);
300        assert_eq!(Bus::ciram_mirror(0x2C00, Mirroring::SingleScreenA), 0x0000);
301        assert_eq!(Bus::ciram_mirror(0x2C05, Mirroring::SingleScreenA), 0x0005);
302        assert_eq!(Bus::ciram_mirror(0x2FFF, Mirroring::SingleScreenA), 0x03FF);
303    }
304
305    #[test]
306    fn ciram_mirror_single_screen_b() {
307        assert_eq!(Bus::ciram_mirror(0x2000, Mirroring::SingleScreenB), 0x0400);
308        assert_eq!(Bus::ciram_mirror(0x2005, Mirroring::SingleScreenB), 0x0405);
309        assert_eq!(Bus::ciram_mirror(0x23FF, Mirroring::SingleScreenB), 0x07FF);
310        assert_eq!(Bus::ciram_mirror(0x2800, Mirroring::SingleScreenB), 0x0400);
311        assert_eq!(Bus::ciram_mirror(0x2805, Mirroring::SingleScreenB), 0x0405);
312        assert_eq!(Bus::ciram_mirror(0x2BFF, Mirroring::SingleScreenB), 0x07FF);
313        assert_eq!(Bus::ciram_mirror(0x2400, Mirroring::SingleScreenB), 0x0400);
314        assert_eq!(Bus::ciram_mirror(0x2405, Mirroring::SingleScreenB), 0x0405);
315        assert_eq!(Bus::ciram_mirror(0x27FF, Mirroring::SingleScreenB), 0x07FF);
316        assert_eq!(Bus::ciram_mirror(0x2C00, Mirroring::SingleScreenB), 0x0400);
317        assert_eq!(Bus::ciram_mirror(0x2C05, Mirroring::SingleScreenB), 0x0405);
318        assert_eq!(Bus::ciram_mirror(0x2FFF, Mirroring::SingleScreenB), 0x07FF);
319    }
320}