Fix some bugs in memory
This commit is contained in:
parent
8e97961955
commit
3d35fe8ae7
210
src/memory.rs
210
src/memory.rs
@ -19,7 +19,6 @@
|
||||
use self::{
|
||||
banked::{Banked, UpperBanked},
|
||||
io::BusIO,
|
||||
mapper::Mapper,
|
||||
};
|
||||
pub mod banked;
|
||||
pub mod io;
|
||||
@ -44,25 +43,21 @@ impl Mode {
|
||||
}
|
||||
}
|
||||
|
||||
/// The [Bus] controls the memory map from the CPU's perspective
|
||||
#[derive(Debug)]
|
||||
pub struct Bus {
|
||||
mode: Mode,
|
||||
// TODO: define a way to have an arbitrary memory map
|
||||
// TODO: Replace the cart entirely
|
||||
// TODO: Separate into external and vram bus
|
||||
// TODO: Implement multiple views into memory
|
||||
cart: Cart,
|
||||
// VRAM is a 0x2000 B window from 0x8000..0xa000, with two banks
|
||||
vram: Banked<0x8000, 0x2000, 2>,
|
||||
// WRAM is a 0x2000 B window from 0xc000..0xe000. The upper half is banked.
|
||||
wram: UpperBanked<0xc000, 0x1000, 8>,
|
||||
// Memory mapped IO registers, HRAM
|
||||
// HRAM is a 0x80 B window from 0xff80..0xffff
|
||||
hram: [u8; 0x80],
|
||||
// Joypad driver
|
||||
// Serial driver
|
||||
// Timer and divider
|
||||
// Interrupt controller
|
||||
// Audio controller
|
||||
// PPU/LCD controller
|
||||
mmio: [u8; 0x80],
|
||||
//
|
||||
hmem: [u8; 0x100],
|
||||
}
|
||||
|
||||
impl BusIO for Bus {
|
||||
@ -82,10 +77,7 @@ impl BusIO for Bus {
|
||||
0xfe00..=0xfe9f => Some(0x0a),
|
||||
// Illegal
|
||||
0xfea0..=0xfeff => None,
|
||||
// Memory mapped IO
|
||||
0xff00..=0xff7f => self.mmio.read(addr & 0x7f),
|
||||
// HiRAM
|
||||
0xff80..=0xffff => self.hram.read(addr & 0x7f),
|
||||
0xff00..=0xffff => self.hmem.read(addr & 0xff),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -106,10 +98,8 @@ impl BusIO for Bus {
|
||||
0xfe00..=0xfe9f => Some(()),
|
||||
// Illegal
|
||||
0xfea0..=0xfeff => None,
|
||||
// Memory mapped IO
|
||||
0xff00..=0xff7f => self.mmio.write(addr & 0x7f, data),
|
||||
// HiRAM
|
||||
0xff80..=0xffff => self.hram.write(addr & 0x7f, data),
|
||||
// Memory mapped IO & HRAM
|
||||
0xff00..=0xffff => self.hmem.write(addr & 0xff, data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -122,8 +112,7 @@ impl Bus {
|
||||
mode: Mode::CGB,
|
||||
vram: Default::default(),
|
||||
wram: Default::default(),
|
||||
mmio: [0; 128],
|
||||
hram: [0; 128],
|
||||
hmem: [0; 256],
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,7 +145,7 @@ impl Bus {
|
||||
Mode::DMG => {}
|
||||
},
|
||||
}
|
||||
self.mmio.get(addr % 0x80).copied()
|
||||
self.hmem.get(addr % 0x80 + 0x80).copied()
|
||||
}
|
||||
|
||||
pub fn mmio_read_cgb(&self, addr: usize) -> Option<u8> {
|
||||
@ -209,7 +198,7 @@ impl Bus {
|
||||
Mode::DMG => {}
|
||||
},
|
||||
}
|
||||
self.mmio.write(addr % 0x80, data)
|
||||
self.hmem.write(addr % 0x80 + 0x80, data)
|
||||
}
|
||||
|
||||
pub fn mmio_write_cgb(&mut self, addr: usize, data: u8) -> Option<()> {
|
||||
@ -242,17 +231,22 @@ impl Bus {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Cart {
|
||||
pub rom: Vec<u8>,
|
||||
pub ram: Vec<u8>,
|
||||
mapper: Box<dyn Mapper>,
|
||||
}
|
||||
pub use cart::Cart;
|
||||
|
||||
mod cart {
|
||||
use crate::memory::io::BusAux;
|
||||
|
||||
use super::{mapper::*, BusIO, Cart};
|
||||
use super::{mapper::*, BusIO};
|
||||
use crate::{
|
||||
constants::{RAM_BANK, ROM_BANK},
|
||||
memory::io::BusAux,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub struct Cart {
|
||||
pub rom: Vec<u8>,
|
||||
pub ram: Vec<u8>,
|
||||
mapper: MBC,
|
||||
}
|
||||
|
||||
impl BusIO for Cart {
|
||||
fn read(&self, addr: usize) -> Option<u8> {
|
||||
match self.mapper.read(addr) {
|
||||
@ -276,23 +270,28 @@ mod cart {
|
||||
}
|
||||
/// Read cartridge header, as defined in the [Pan docs](https://gbdev.io/pandocs/The_Cartridge_Header.html)
|
||||
impl Cart {
|
||||
pub fn new(rom: Vec<u8>) -> Self {
|
||||
let rom_size = rom.romsize().expect("ROM should have cart header!");
|
||||
let ram_size = rom.ramsize().expect("ROM should have cart header!");
|
||||
pub fn new(mut rom: Vec<u8>, mut ram: Vec<u8>) -> Self {
|
||||
let rom_size = ROM_BANK * rom.romsize().expect("ROM should have cart header!");
|
||||
let ram_size = RAM_BANK * rom.ramsize().expect("ROM should have cart header!");
|
||||
if rom_size > rom.len() {
|
||||
eprintln!("Rom claims to be {rom_size}, but is actually {}", rom.len());
|
||||
}
|
||||
|
||||
rom.resize(rom_size, 0);
|
||||
ram.resize(ram_size, 0);
|
||||
|
||||
let cart_type = rom.carttype().expect("ROM should have cart header!");
|
||||
Self {
|
||||
mapper: match cart_type {
|
||||
0 => Box::<no_mbc::NoMBC>::default(),
|
||||
1 => Box::new(mbc1::MBC1::new(rom_size, 0)), // ROM
|
||||
2 => Box::new(mbc1::MBC1::new(rom_size, ram_size)), // ROM + RAM
|
||||
3 => Box::new(mbc1::MBC1::new(rom_size, ram_size)), // ROM + RAM + Battery
|
||||
// _ => Box::<NoMBC>::default(),
|
||||
n => todo!("Mapper 0x{n:02x}"),
|
||||
},
|
||||
mapper: MBC::from_cart_type(cart_type).expect("MAPPER NOT FOUND: {cart_type}"),
|
||||
rom,
|
||||
ram: vec![],
|
||||
ram,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_save(&mut self, save: &[u8]) {
|
||||
self.ram.resize(save.len(), 0);
|
||||
self.ram.copy_from_slice(save)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Cart {
|
||||
@ -306,19 +305,19 @@ mod cart {
|
||||
|
||||
impl From<Vec<u8>> for Cart {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Self::new(value)
|
||||
Self::new(value, vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for Cart {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self::new(Vec::from(value))
|
||||
Self::new(Vec::from(value), vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl<Item: AsRef<u8>> FromIterator<Item> for Cart {
|
||||
fn from_iter<T: IntoIterator<Item = Item>>(iter: T) -> Self {
|
||||
Self::new(iter.into_iter().map(|item| *(item.as_ref())).collect())
|
||||
Self::new(iter.into_iter().map(|item| *(item.as_ref())).collect(), vec![])
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,6 +367,48 @@ pub mod mapper {
|
||||
fn write(&mut self, addr: usize, val: u8) -> Response;
|
||||
}
|
||||
|
||||
pub enum MBC {
|
||||
None,
|
||||
MBC1(mbc1::MBC1),
|
||||
// TODO: MBC2, 3, 5
|
||||
}
|
||||
|
||||
impl MBC {
|
||||
pub fn from_cart_type(cart_type: u8) -> Option<Self> {
|
||||
Some(match cart_type {
|
||||
0 => Self::None,
|
||||
1 => Self::MBC1(mbc1::MBC1::new()), // ROM
|
||||
2 => Self::MBC1(mbc1::MBC1::new()), // ROM + RAM
|
||||
3 => Self::MBC1(mbc1::MBC1::new()), // ROM + RAM + Battery
|
||||
n => {
|
||||
eprintln!("Mapper 0x{n:02x}");
|
||||
None?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read(&self, addr: usize) -> Response {
|
||||
match self {
|
||||
MBC::None => match addr {
|
||||
0x0000..=0x7fff => Response::Rom(addr),
|
||||
0xa000..=0xbfff => Response::Ram(addr - 0xa000),
|
||||
_ => Response::None,
|
||||
},
|
||||
MBC::MBC1(mbc) => mbc.read(addr),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, addr: usize, val: u8) -> Response {
|
||||
match self {
|
||||
MBC::None => match addr {
|
||||
0xa000..=0xbfff => Response::Ram(addr),
|
||||
_ => Response::None,
|
||||
},
|
||||
MBC::MBC1(mbc) => mbc.write(addr, val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod no_mbc {
|
||||
use super::*;
|
||||
|
||||
@ -378,14 +419,14 @@ pub mod mapper {
|
||||
fn read(&self, addr: usize) -> Response {
|
||||
match addr {
|
||||
0x0000..=0x7fff => Response::Rom(addr),
|
||||
0xa000..=0xbfff => Response::Ram(addr - 0x8000),
|
||||
0xa000..=0xbfff => Response::Ram(addr - 0xa000),
|
||||
_ => Response::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, addr: usize, _val: u8) -> Response {
|
||||
match addr {
|
||||
0xa000..=0xbfff => Response::Ram(addr - 0xA000),
|
||||
0xa000..=0xbfff => Response::Ram(addr - 0xa000),
|
||||
_ => Response::None,
|
||||
}
|
||||
}
|
||||
@ -394,71 +435,66 @@ pub mod mapper {
|
||||
|
||||
pub mod mbc1 {
|
||||
use super::*;
|
||||
use crate::constants::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct MBC1 {
|
||||
rom_mask: u8,
|
||||
ram_mask: u8,
|
||||
mode: u8,
|
||||
rom_bank: u8,
|
||||
ram_bank: u8,
|
||||
bank_enable: bool,
|
||||
ram_enable: bool,
|
||||
bank_lo: u8,
|
||||
bank_hi: u8,
|
||||
}
|
||||
|
||||
impl MBC1 {
|
||||
pub fn new(rom_size: usize, ram_size: usize) -> Self {
|
||||
Self {
|
||||
rom_mask: (rom_size).wrapping_sub(1) as u8,
|
||||
ram_mask: (ram_size).wrapping_sub(1) as u8,
|
||||
rom_bank: 1,
|
||||
..Default::default()
|
||||
pub fn new() -> Self {
|
||||
Self { bank_lo: 1, ..Default::default() }
|
||||
}
|
||||
|
||||
pub fn rom_lower(&self) -> usize {
|
||||
match self.bank_enable {
|
||||
true => ((self.bank_hi << 5) as usize) * ROM_BANK,
|
||||
false => 0,
|
||||
}
|
||||
}
|
||||
pub fn rom_bank_lower(&self) -> usize {
|
||||
(((self.ram_bank << 5) & self.rom_mask) as usize) << 14
|
||||
|
||||
pub fn rom_upper(&self) -> usize {
|
||||
((self.bank_lo | (self.bank_hi << 5)) as usize) * ROM_BANK
|
||||
}
|
||||
pub fn rom_bank_upper(&self) -> usize {
|
||||
((self.rom_bank | (self.ram_bank << 5) & self.rom_mask) as usize) << 14
|
||||
|
||||
pub fn ram_base(&self) -> usize {
|
||||
match self.bank_enable {
|
||||
true => (self.bank_hi as usize) * RAM_BANK,
|
||||
false => 0,
|
||||
}
|
||||
pub fn ram_bank_base(&self) -> usize {
|
||||
((self.ram_bank & self.ram_mask) as usize) << 13
|
||||
}
|
||||
}
|
||||
|
||||
impl Mapper for MBC1 {
|
||||
fn read(&self, addr: usize) -> Response {
|
||||
match (addr, self.mode) {
|
||||
(0x0000..=0x3fff, 0) => Response::Rom(addr),
|
||||
(0x0000..=0x3fff, _) => Response::Rom(self.rom_bank_lower() | (addr & 0x3fff)),
|
||||
|
||||
(0x4000..=0x7fff, _) => Response::Rom(self.rom_bank_upper() | (addr & 0x3fff)),
|
||||
|
||||
(0xa000..=0xbfff, 0) => Response::Ram(addr & 0x1fff),
|
||||
(0xa000..=0xbfff, _) => Response::Ram(self.ram_bank_base() | (addr & 0x1fff)),
|
||||
|
||||
match addr >> 12 {
|
||||
0..=3 => Response::Rom(self.rom_lower() | addr),
|
||||
4..=7 => Response::Rom(self.rom_upper() | (addr % ROM_BANK)),
|
||||
0xa | 0xb => Response::Ram(self.ram_base() | (addr % RAM_BANK)),
|
||||
_ => Response::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, addr: usize, val: u8) -> Response {
|
||||
match (addr, self.mode) {
|
||||
(0x0000..=0x1fff, _) => {
|
||||
match addr >> 12 {
|
||||
0x0 | 0x1 => {
|
||||
self.ram_enable = val & 0xf == 0xa;
|
||||
}
|
||||
(0x2000..=0x3fff, _) => {
|
||||
self.rom_bank = (val & 0x1f).max(1);
|
||||
0x2 | 0x3 => {
|
||||
self.bank_lo = (val & 0x1f).max(1);
|
||||
}
|
||||
(0x4000..=0x5fff, _) => {
|
||||
self.ram_bank = val & 0x3;
|
||||
0x4 | 0x5 => {
|
||||
self.bank_hi = val & 0x3;
|
||||
}
|
||||
(0x6000..=0x7fff, _) => {
|
||||
self.mode = val & 1;
|
||||
0x6 | 0x7 => {
|
||||
self.bank_enable = val & 1 != 0;
|
||||
}
|
||||
(0xa000..=0xbfff, 0) if self.ram_enable => {
|
||||
return Response::Ram(addr & 0x1fff);
|
||||
}
|
||||
(0xa000..=0xbfff, 1) if self.ram_enable => {
|
||||
return Response::Ram((self.ram_bank as usize & 0b11 << 13) | addr & 0x1fff)
|
||||
0xa | 0xb if self.ram_enable => {
|
||||
return Response::Ram(self.ram_base() | (addr % RAM_BANK))
|
||||
}
|
||||
_ => return Response::None,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user