tetanes_core/ppu/
ctrl.rs

1//! PPUCTRL register implementation.
2//!
3//! See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUCTRL>
4
5use crate::common::{Reset, ResetKind};
6use bitflags::bitflags;
7use serde::{Deserialize, Serialize};
8
9const NAMETABLE1: u16 = 0x2000;
10const NAMETABLE2: u16 = 0x2400;
11const NAMETABLE3: u16 = 0x2800;
12const NAMETABLE4: u16 = 0x2C00;
13
14/// PPUCTRL register.
15///
16/// See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUCTRL>
17#[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]
18#[must_use]
19pub struct Ctrl {
20    pub spr_select: u16,
21    pub bg_select: u16,
22    pub spr_height: u32,
23    pub master_slave: u8,
24    pub nmi_enabled: bool,
25    pub nametable_addr: u16,
26    pub vram_increment: u16,
27    bits: Bits,
28}
29
30bitflags! {
31    // $2000 PPUCTRL
32    //
33    // https://wiki.nesdev.org/w/index.php/PPU_registers#PPUCTRL
34    // VPHB SINN
35    // |||| ||++- Nametable Select: 0b00 = $2000 (upper-left); 0b01 = $2400 (upper-right);
36    // |||| ||                      0b10 = $2800 (lower-left); 0b11 = $2C00 (lower-right)
37    // |||| |||+-   Also For PPUSCROLL: 1 = Add 256 to X scroll
38    // |||| ||+--   Also For PPUSCROLL: 1 = Add 240 to Y scroll
39    // |||| |+--- VRAM Increment Mode: 0 = add 1, going across; 1 = add 32, going down
40    // |||| +---- Sprite Pattern Select for 8x8: 0 = $0000, 1 = $1000, ignored in 8x16 mode
41    // |||+------ Background Pattern Select: 0 = $0000, 1 = $1000
42    // ||+------- Sprite Height: 0 = 8x8, 1 = 8x16
43    // |+-------- PPU Master/Slave: 0 = read from EXT, 1 = write to EXT
44    // +--------- NMI Enable: NMI at next vblank: 0 = off, 1: on
45    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]
46    #[must_use]
47    pub struct Bits: u8 {
48        const NAMETABLE1 = 0x01;
49        const NAMETABLE2 = 0x02;
50        const VRAM_INCREMENT = 0x04;
51        const SPR_SELECT = 0x08;
52        const BG_SELECT = 0x10;
53        const SPR_HEIGHT = 0x20;
54        const MASTER_SLAVE = 0x40;
55        const NMI_ENABLE = 0x80;
56    }
57}
58
59impl Ctrl {
60    pub fn new() -> Self {
61        let mut ctrl = Self::default();
62        ctrl.write(0);
63        ctrl
64    }
65
66    pub fn write(&mut self, val: u8) {
67        self.bits = Bits::from_bits_truncate(val);
68        // 0x1000 or 0x0000
69        self.spr_select = self.bits.contains(Bits::SPR_SELECT) as u16 * 0x1000;
70        // 0x1000 or 0x0000
71        self.bg_select = self.bits.contains(Bits::BG_SELECT) as u16 * 0x1000;
72        // 16 or 8
73        self.spr_height = self.bits.contains(Bits::SPR_HEIGHT) as u32 * 8 + 8;
74        // 1 or 0
75        self.master_slave = self.bits.contains(Bits::MASTER_SLAVE) as u8;
76        self.nmi_enabled = self.bits.contains(Bits::NMI_ENABLE);
77        self.nametable_addr = match self.bits.bits() & 0b11 {
78            0b00 => NAMETABLE1,
79            0b01 => NAMETABLE2,
80            0b10 => NAMETABLE3,
81            0b11 => NAMETABLE4,
82            _ => unreachable!("impossible nametable_addr"),
83        };
84        // 32 or 1
85        self.vram_increment = self.bits.contains(Bits::VRAM_INCREMENT) as u16 * 31 + 1
86    }
87
88    pub const fn nametable_select(&self) -> u8 {
89        self.bits.bits() & 0b11
90    }
91}
92
93impl Reset for Ctrl {
94    // https://www.nesdev.org/wiki/PPU_power_up_state
95    fn reset(&mut self, _kind: ResetKind) {
96        self.write(0);
97    }
98}