cge_nes 0.1.2

Cycle-accurate NES (Nintendo Entertainment System) emulator library: CPU, PPU, cartridge, input, and iNES ROM loading.
Documentation
//! Memory mapping and address space management for the NES system.
//!
//! This module implements the CPU's view of the system memory map, including:
//! - Internal RAM (2KB mirrored 4 times from $0000-$1FFF)
//! - PPU registers (mirrored from $2000-$3FFF)
//! - APU and IO registers ($4000-$401F)
//! - Cartridge space ($4020-$FFFF)

use crate::cartridge::{Cartridge, ChrRomContentStatus};
use crate::input::{ConnectedSocket, HostInput, InputRegisters};
use crate::nes::dma::Dma;
use crate::nes::ppu_cart_memory::PpuCartMemory;
use cpu6502::MemorySpace;
use devices6502::size_const::*;
use devices6502::Device;

type Ram = devices6502::Mirror<devices6502::Ram<SIZE_2K>, 4>;

/// Represents the NES system memory map and handles memory-mapped I/O.
///
/// The NES uses memory mapping to communicate with various system components:
/// - $0000-$1FFF: Internal RAM (2KB mirrored 4 times)
/// - $2000-$3FFF: PPU registers (mirrored)
/// - $4000-$4013: APU registers
/// - $4014: OAM DMA register
/// - $4015: APU status/control
/// - $4016-$4017: Input port registers
/// - $4018-$401F: APU and I/O functionality that was removed
/// - $4020-$FFFF: Cartridge space
pub struct NesMemorySpace<'a, THostInput> {
    internal_ram: &'a mut Ram,
    cartridge: &'a mut dyn Cartridge,
    ppu: &'a mut crate::ppu::Ppu,
    input_registers: &'a mut InputRegisters,
    host_input: &'a mut THostInput,
    ppu_dma: &'a mut Dma,
    chr_rom_changed: bool,
}

impl<'a, THostInput> MemorySpace for NesMemorySpace<'a, THostInput>
where
    THostInput: HostInput,
{
    /// Reads a byte from the specified memory address.
    ///
    /// The address space is mapped as follows:
    /// - $0000-$1FFF: Internal RAM (2KB mirrored 4 times)
    /// - $2000-$3FFF: PPU registers (mirrored)
    /// - $4000-$4013: APU registers (currently returns 0)
    /// - $4014: OAM DMA register (currently returns 0)
    /// - $4015: APU status (currently returns 0)
    /// - $4016-$4017: Input port registers
    /// - $4018-$401F: Disabled (returns 0)
    /// - $4020-$FFFF: Cartridge space
    fn read(&mut self, addr: u16) -> u8 {
        match addr {
            0x0000..=0x1FFF => self.internal_ram.read(addr),
            0x2000..=0x3FFF => {
                let mut ppu_memory = PpuCartMemory::new(self.cartridge);
                self.ppu.read_ppu_register_by_addr(addr, &mut ppu_memory)
            }
            0x4000..=0x4013 => 0, //apu
            0x4014 => 0,          //dma
            0x4015 => 0,          //apu
            0x4016 => self
                .input_registers
                .read_register(self.host_input, ConnectedSocket::Player1),
            0x4017 => self
                .input_registers
                .read_register(self.host_input, ConnectedSocket::Player2),
            0x4018..=0x401F => 0, // disabled
            0x4020..=0xFFFF => self.cartridge.read_cpu_mapped(addr - 0x4020),
        }
    }

    /// Writes a byte to the specified memory address.
    ///
    /// The address space is mapped as follows:
    /// - $0000-$1FFF: Internal RAM (2KB mirrored 4 times)
    /// - $2000-$3FFF: PPU registers (mirrored)
    /// - $4000-$4013: APU registers (currently ignored)
    /// - $4014: OAM DMA register (triggers DMA transfer)
    /// - $4015: APU control (currently ignored)
    /// - $4016: Input port control
    /// - $4017: APU frame counter (currently ignored)
    /// - $4018-$401F: Disabled
    /// - $4020-$FFFF: Cartridge space
    fn write(&mut self, data: u8, addr: u16) {
        let mut chr_rom_status = ChrRomContentStatus::Unchanged;
        match addr {
            0x0000..=0x1FFF => self.internal_ram.write(data, addr),
            0x2000..=0x3FFF => {
                let mut ppu_memory = PpuCartMemory::new(self.cartridge);
                self.ppu
                    .write_ppu_register_by_addr(data, addr, &mut ppu_memory);
            }
            0x4000..=0x4013 => (), //apu
            0x4014 => self.ppu_dma.initiate_dma(data),
            0x4015 => (), //apu
            0x4016 => {
                if (data & 1) != 0 {
                    self.input_registers.set_strobe_mode_on();
                } else {
                    self.input_registers.set_strobe_mode_off(self.host_input);
                }
            }
            0x4017 => (),          // APU Frame Counter
            0x4018..=0x401F => (), // disabled
            0x4020..=0xFFFF => {
                chr_rom_status = self.cartridge.write_cpu_mapped(data, addr - 0x4020)
            }
        };
        self.chr_rom_changed = chr_rom_status == ChrRomContentStatus::Changed;
    }
}

impl<'a, THostInput> NesMemorySpace<'a, THostInput>
where
    THostInput: HostInput,
{
    /// Creates a new NES memory space with the given system components.
    ///
    /// # Arguments
    ///
    /// * `internal_ram` - The system's 2KB internal RAM
    /// * `cartridge` - The currently inserted game cartridge
    /// * `ppu` - The Picture Processing Unit
    /// * `input_registers` - The input port registers
    /// * `host_input` - The host system's input handler
    /// * `ppu_dma` - The DMA controller for PPU transfers
    pub fn new(
        internal_ram: &'a mut Ram,
        cartridge: &'a mut dyn Cartridge,
        ppu: &'a mut crate::ppu::Ppu,
        input_registers: &'a mut InputRegisters,
        host_input: &'a mut THostInput,
        ppu_dma: &'a mut Dma,
    ) -> Self {
        Self {
            internal_ram,
            cartridge,
            ppu,
            input_registers,
            host_input,
            ppu_dma,
            chr_rom_changed: false,
        }
    }

    /// Returns whether the cartridge's CHR ROM content has changed during the last write.
    pub fn chr_rom_changed(&self) -> bool {
        self.chr_rom_changed
    }
}