ROM insertion into system
This commit is contained in:
parent
8d5192ff43
commit
d4715bbf62
@ -3,6 +3,7 @@ mod op;
|
|||||||
mod operations;
|
mod operations;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
use crate::memory::MemoryBus;
|
use crate::memory::MemoryBus;
|
||||||
use crate::Clock;
|
use crate::Clock;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
@ -15,7 +16,7 @@ pub struct Cpu {
|
|||||||
registers: CpuRegisters,
|
registers: CpuRegisters,
|
||||||
|
|
||||||
/// The memory bus accessible by the CPU
|
/// The memory bus accessible by the CPU
|
||||||
memory_bus: RefCell<MemoryBus>,
|
memory_bus: Rc<RefCell<MemoryBus>>,
|
||||||
|
|
||||||
/// The number of cycles ran on the CPU
|
/// The number of cycles ran on the CPU
|
||||||
cycle: usize,
|
cycle: usize,
|
||||||
@ -133,7 +134,7 @@ pub trait CpuInternals {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
pub fn new(memory_bus: RefCell<MemoryBus>) -> Self {
|
pub fn new(memory_bus: Rc<RefCell<MemoryBus>>) -> Self {
|
||||||
Cpu {
|
Cpu {
|
||||||
registers: CpuRegisters {
|
registers: CpuRegisters {
|
||||||
pc: 0x8000,
|
pc: 0x8000,
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
mod cpu;
|
mod cpu;
|
||||||
|
mod mappers;
|
||||||
mod memory;
|
mod memory;
|
||||||
pub mod rom;
|
mod rom;
|
||||||
mod system;
|
pub mod system;
|
||||||
|
|
||||||
pub trait Clock {
|
pub trait Clock {
|
||||||
/// Run a clock cycle
|
/// Run a clock cycle
|
||||||
|
31
core/src/mappers/mod.rs
Normal file
31
core/src/mappers/mod.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use crate::mappers::nrom::NRom;
|
||||||
|
use crate::rom::Rom;
|
||||||
|
|
||||||
|
mod nrom;
|
||||||
|
|
||||||
|
pub trait Mapper {
|
||||||
|
/// Gets a byte from the program ROM.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * addr - The address of the byte to read
|
||||||
|
fn get_prg_byte(&self, addr: u16) -> u8;
|
||||||
|
|
||||||
|
/// Gets a byte from the character ROM.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * addr - The address of the byte to read
|
||||||
|
fn get_chr_byte(&self, addr: u16) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mapper for a ROM.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * rom - The ROM to get the mapper for
|
||||||
|
pub fn get_mapper(rom: Rom) -> Box<dyn Mapper> {
|
||||||
|
let instance = match rom.mapper {
|
||||||
|
0 => NRom::new(rom),
|
||||||
|
_ => unreachable!("Unsupported mapper {}", rom.mapper),
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::new(instance)
|
||||||
|
}
|
39
core/src/mappers/nrom.rs
Normal file
39
core/src/mappers/nrom.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use simplelog::info;
|
||||||
|
use crate::mappers::Mapper;
|
||||||
|
use crate::rom::Rom;
|
||||||
|
|
||||||
|
const PRG_BANK_SIZE: usize = 0x4000;
|
||||||
|
const CHR_BANK_SIZE: usize = 0x2000;
|
||||||
|
|
||||||
|
pub struct NRom {
|
||||||
|
rom: Rom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NRom {
|
||||||
|
/// Creates an instance of the NRom mapper.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * rom - The ROM
|
||||||
|
pub fn new(rom: Rom) -> Self {
|
||||||
|
info!("Using NRom mapper");
|
||||||
|
Self { rom }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mapper for NRom {
|
||||||
|
fn get_prg_byte(&self, addr: u16) -> u8 {
|
||||||
|
let addr_usize = addr as usize;
|
||||||
|
if addr_usize < PRG_BANK_SIZE || self.rom.prg_rom_size > PRG_BANK_SIZE {
|
||||||
|
return self.rom.prg_rom[addr_usize];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rom.prg_rom[addr_usize - PRG_BANK_SIZE]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_chr_byte(&self, addr: u16) -> u8 {
|
||||||
|
let addr_usize = addr as usize;
|
||||||
|
|
||||||
|
assert!(addr_usize < CHR_BANK_SIZE);
|
||||||
|
self.rom.chr_rom[addr_usize]
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,61 @@
|
|||||||
const MEMORY_SIZE: usize = 0x0800;
|
use simplelog::warn;
|
||||||
|
use crate::mappers::Mapper;
|
||||||
|
|
||||||
|
const MEMORY_MAX_ADDR: u16 = 0xFFFF;
|
||||||
|
const PPU_MAX_ADDR: u16 = 0x4000;
|
||||||
|
const APU_MAX_ADDR: u16 = 0x4020;
|
||||||
|
const UNMAPPED_MAX_ADDR: u16 = 0x6000;
|
||||||
|
const CARTRIDGE_RAM_MAX_ADDR: u16 = 0x8000;
|
||||||
|
const RAM_SIZE: usize = 0x0800;
|
||||||
|
const RAM_MAX_ADDR: u16 = 0x2000;
|
||||||
|
|
||||||
pub struct MemoryBus {
|
pub struct MemoryBus {
|
||||||
ram: [u8; MEMORY_SIZE],
|
mapper: Option<Box<dyn Mapper>>,
|
||||||
|
ram: [u8; RAM_SIZE],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryBus {
|
impl MemoryBus {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let ram = [0; MEMORY_SIZE];
|
let ram = [0; RAM_SIZE];
|
||||||
Self { ram }
|
Self { mapper: None, ram }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a mapper in the memory bus, simulates plugging in a cartridge.
|
||||||
|
pub fn register_mapper(&mut self, mapper: Box<dyn Mapper>) {
|
||||||
|
self.mapper = Some(mapper);
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a byte from memory.
|
/// Gets a byte from memory.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `addr` - The address of the byte
|
/// * `addr` - The address of the byte
|
||||||
pub fn get_byte(&self, addr: u16) -> u8 {
|
pub fn get_byte(&self, addr: u16) -> u8 {
|
||||||
unimplemented!("MemoryBus::get_byte");
|
assert!(addr < MEMORY_MAX_ADDR);
|
||||||
|
if addr < RAM_MAX_ADDR {
|
||||||
|
let ram_addr = (addr as usize) % RAM_SIZE;
|
||||||
|
self.ram[ram_addr]
|
||||||
|
} else if addr < PPU_MAX_ADDR {
|
||||||
|
let relative_addr = addr - RAM_MAX_ADDR;
|
||||||
|
let ppu_register = relative_addr % 8;
|
||||||
|
todo!()
|
||||||
|
} else if addr < APU_MAX_ADDR {
|
||||||
|
todo!()
|
||||||
|
} else if addr < UNMAPPED_MAX_ADDR {
|
||||||
|
assert!(false);
|
||||||
|
0
|
||||||
|
} else if addr < CARTRIDGE_RAM_MAX_ADDR {
|
||||||
|
todo!()
|
||||||
|
} else {
|
||||||
|
let prg_addr = addr - CARTRIDGE_RAM_MAX_ADDR;
|
||||||
|
|
||||||
|
if self.mapper.is_none() {
|
||||||
|
warn!("Tried to read memory address {} before setting a mapper", addr);
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let mapper = self.mapper.as_ref().unwrap();
|
||||||
|
mapper.get_prg_byte(prg_addr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a word from memory.
|
/// Gets a word from memory.
|
||||||
|
@ -202,6 +202,18 @@ impl INesHeader {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_mapper(&self) -> u16 {
|
||||||
|
let mapper_low = (self.flag_6 >> 4) as u16;
|
||||||
|
let mapper_mid = (self.flag_7 & 0xF0) as u16;
|
||||||
|
let mapper_high = (self.mapper_msb_submapper & 0x0F) as u16;
|
||||||
|
|
||||||
|
mapper_high << 8 | mapper_mid | mapper_low
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_sub_mapper(&self) -> u8 {
|
||||||
|
self.mapper_msb_submapper >> 4
|
||||||
|
}
|
||||||
|
|
||||||
fn parse(header: &[u8]) -> Self {
|
fn parse(header: &[u8]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
prg_rom_size_lsb: header[4],
|
prg_rom_size_lsb: header[4],
|
||||||
@ -252,6 +264,8 @@ impl RomLoader for INesHeader {
|
|||||||
let (chr_ram_size, chr_nvram_size) = ines_header.get_chr_ram_size();
|
let (chr_ram_size, chr_nvram_size) = ines_header.get_chr_ram_size();
|
||||||
let nametable_mirroring = ines_header.get_nametable_mirroring();
|
let nametable_mirroring = ines_header.get_nametable_mirroring();
|
||||||
let cpu_timing = ines_header.get_cpu_timing()?;
|
let cpu_timing = ines_header.get_cpu_timing()?;
|
||||||
|
let mapper = ines_header.get_mapper();
|
||||||
|
let sub_mapper = ines_header.get_sub_mapper();
|
||||||
|
|
||||||
let prg_rom = file_data[trainer_size..prg_rom_size]
|
let prg_rom = file_data[trainer_size..prg_rom_size]
|
||||||
.to_vec()
|
.to_vec()
|
||||||
@ -272,6 +286,8 @@ impl RomLoader for INesHeader {
|
|||||||
chr_rom,
|
chr_rom,
|
||||||
nametable_mirroring,
|
nametable_mirroring,
|
||||||
cpu_timing,
|
cpu_timing,
|
||||||
|
mapper,
|
||||||
|
sub_mapper,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,13 @@ pub struct Rom {
|
|||||||
|
|
||||||
/// The timing of the CPU, also the region of the ROM
|
/// The timing of the CPU, also the region of the ROM
|
||||||
pub cpu_timing: CpuTiming,
|
pub cpu_timing: CpuTiming,
|
||||||
|
|
||||||
|
/// The number of the mapper
|
||||||
|
pub mapper: u16,
|
||||||
|
|
||||||
|
/// The number of the sub-mapper
|
||||||
|
/// Not supported for now
|
||||||
|
pub sub_mapper: u8
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum NametableMirroring {
|
pub enum NametableMirroring {
|
||||||
@ -106,6 +113,7 @@ pub enum RomReadError {
|
|||||||
InvalidHeader(String),
|
InvalidHeader(String),
|
||||||
MissingHeader,
|
MissingHeader,
|
||||||
RomLengthMismatch(usize, usize),
|
RomLengthMismatch(usize, usize),
|
||||||
|
UnsupportedMapper(u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for RomReadError {
|
impl Display for RomReadError {
|
||||||
@ -117,6 +125,7 @@ impl Display for RomReadError {
|
|||||||
RomReadError::MissingHeader => write!(f, "File was too short to contain a header"),
|
RomReadError::MissingHeader => write!(f, "File was too short to contain a header"),
|
||||||
RomReadError::RomLengthMismatch(required_length, actual_length) =>
|
RomReadError::RomLengthMismatch(required_length, actual_length) =>
|
||||||
write!(f, "ROM didn't contain enough data according to the header; required {} bytes but contained {} bytes", required_length, actual_length),
|
write!(f, "ROM didn't contain enough data according to the header; required {} bytes but contained {} bytes", required_length, actual_length),
|
||||||
|
RomReadError::UnsupportedMapper(mapper) => write!(f, "Unsupported mapper: {}", mapper),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use simplelog::info;
|
||||||
use crate::cpu::Cpu;
|
use crate::cpu::Cpu;
|
||||||
|
use crate::mappers::get_mapper;
|
||||||
use crate::memory::MemoryBus;
|
use crate::memory::MemoryBus;
|
||||||
|
use crate::rom::{Rom, RomReadError};
|
||||||
|
|
||||||
pub struct System {
|
pub struct System {
|
||||||
cpu: Cpu,
|
cpu: Cpu,
|
||||||
@ -11,11 +14,21 @@ pub struct System {
|
|||||||
impl System {
|
impl System {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let memory_bus = Rc::new(RefCell::new(MemoryBus::new()));
|
let memory_bus = Rc::new(RefCell::new(MemoryBus::new()));
|
||||||
let cpu = Cpu::new(memory_bus);
|
let cpu = Cpu::new(Rc::clone(&memory_bus));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
cpu,
|
cpu,
|
||||||
memory_bus
|
memory_bus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_rom(&self, path: &str) -> Result<(), RomReadError> {
|
||||||
|
let rom = Rom::read(path)?;
|
||||||
|
let mapper = get_mapper(rom);
|
||||||
|
|
||||||
|
self.memory_bus.borrow_mut().register_mapper(mapper);
|
||||||
|
|
||||||
|
info!("Successfully inserted ROM into system");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,12 +1,19 @@
|
|||||||
use core::rom::Rom;
|
use core::system::System;
|
||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
|
|
||||||
|
const ROM_PATH: &'static str = "./roms/dk.nes";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
CombinedLogger::init(vec![
|
CombinedLogger::init(vec![TermLogger::new(
|
||||||
TermLogger::new(LevelFilter::Debug, Config::default(), TerminalMode::Mixed, ColorChoice::Auto),
|
LevelFilter::Debug,
|
||||||
]).unwrap();
|
Config::default(),
|
||||||
|
TerminalMode::Mixed,
|
||||||
let path = "./roms/dk.nes";
|
ColorChoice::Auto,
|
||||||
let rom = Rom::read(path).expect("Failed to read ROM file");
|
)])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut system = System::new();
|
||||||
|
system.insert_rom(ROM_PATH).expect("Failed to insert ROM");
|
||||||
|
|
||||||
print!("Do stuff");
|
print!("Do stuff");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user