Skip to content

Commit

Permalink
[RUW-13] Implement VRAM mock and nametables (#8)
Browse files Browse the repository at this point in the history
* Added VRAM Nametables

* Fix formatting issues

* [RUW-11] Added Palette RAM

* [RUW-11] Added Palette RAM

* Fixed too long eq() function
  • Loading branch information
HikariIT authored Dec 7, 2024
1 parent 623c845 commit daaed26
Show file tree
Hide file tree
Showing 10 changed files with 501 additions and 42 deletions.
2 changes: 1 addition & 1 deletion emulator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod file_loader;
mod i_nes;
pub mod logging;
pub mod memory;
mod mirroring;
mod nes_2;
pub mod ppu;
mod prg_rom;
pub mod vram;
7 changes: 6 additions & 1 deletion emulator/src/logging/nes_logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ pub fn init_logging() {
)
.unwrap();

log4rs::init_config(config).unwrap();
match log4rs::init_config(config) {
Ok(_) => (),
Err(e) => {
panic!("Error initializing log4rs: {}", e);
}
}
}
25 changes: 25 additions & 0 deletions emulator/src/mirroring.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use std::fmt::Debug;

pub enum Mirroring {
Horizontal,
Vertical,
}

impl PartialEq for Mirroring {
fn eq(&self, other: &Self) -> bool {
matches!(
(self, other),
(Mirroring::Horizontal, Mirroring::Horizontal)
| (Mirroring::Vertical, Mirroring::Vertical)
)
}
}

impl Debug for Mirroring {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Mirroring::Horizontal => write!(f, "Mirroring::Horizontal"),
Mirroring::Vertical => write!(f, "Mirroring::Vertical"),
}
}
}
2 changes: 2 additions & 0 deletions emulator/src/ppu/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pub mod palette_ram;
pub mod ppu;
mod registers;
pub mod vram;
1 change: 1 addition & 0 deletions emulator/src/ppu/palette_ram/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod palette_ram;
267 changes: 267 additions & 0 deletions emulator/src/ppu/palette_ram/palette_ram.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
use crate::addressing::Addressable;
use log::{debug, info};
use std::fmt::Debug;

pub static SYSTEM_PALETTE: [(u8, u8, u8); 64] = [
(0x80, 0x80, 0x80),
(0x00, 0x3D, 0xA6),
(0x00, 0x12, 0xB0),
(0x44, 0x00, 0x96),
(0xA1, 0x00, 0x5E),
(0xC7, 0x00, 0x28),
(0xBA, 0x06, 0x00),
(0x8C, 0x17, 0x00),
(0x5C, 0x2F, 0x00),
(0x10, 0x45, 0x00),
(0x05, 0x4A, 0x00),
(0x00, 0x47, 0x2E),
(0x00, 0x41, 0x66),
(0x00, 0x00, 0x00),
(0x05, 0x05, 0x05),
(0x05, 0x05, 0x05),
(0xC7, 0xC7, 0xC7),
(0x00, 0x77, 0xFF),
(0x21, 0x55, 0xFF),
(0x82, 0x37, 0xFA),
(0xEB, 0x2F, 0xB5),
(0xFF, 0x29, 0x50),
(0xFF, 0x22, 0x00),
(0xD6, 0x32, 0x00),
(0xC4, 0x62, 0x00),
(0x35, 0x80, 0x00),
(0x05, 0x8F, 0x00),
(0x00, 0x8A, 0x55),
(0x00, 0x99, 0xCC),
(0x21, 0x21, 0x21),
(0x09, 0x09, 0x09),
(0x09, 0x09, 0x09),
(0xFF, 0xFF, 0xFF),
(0x0F, 0xD7, 0xFF),
(0x69, 0xA2, 0xFF),
(0xD4, 0x80, 0xFF),
(0xFF, 0x45, 0xF3),
(0xFF, 0x61, 0x8B),
(0xFF, 0x88, 0x33),
(0xFF, 0x9C, 0x12),
(0xFA, 0xBC, 0x20),
(0x9F, 0xE3, 0x0E),
(0x2B, 0xF0, 0x35),
(0x0C, 0xF0, 0xA4),
(0x05, 0xFB, 0xFF),
(0x5E, 0x5E, 0x5E),
(0x0D, 0x0D, 0x0D),
(0x0D, 0x0D, 0x0D),
(0xFF, 0xFF, 0xFF),
(0xA6, 0xFC, 0xFF),
(0xB3, 0xEC, 0xFF),
(0xDA, 0xAB, 0xEB),
(0xFF, 0xA8, 0xF9),
(0xFF, 0xAB, 0xB3),
(0xFF, 0xD2, 0xB0),
(0xFF, 0xEF, 0xA6),
(0xFF, 0xF7, 0x9C),
(0xD7, 0xE8, 0x95),
(0xA6, 0xED, 0xAF),
(0xA2, 0xF2, 0xDA),
(0x99, 0xFF, 0xFC),
(0xDD, 0xDD, 0xDD),
(0x11, 0x11, 0x11),
(0x11, 0x11, 0x11),
];

enum PaletteType {
Background,
Sprite,
}

impl Debug for PaletteType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PaletteType::Background => write!(f, "PaletteType::Background"),
PaletteType::Sprite => write!(f, "PaletteType::Sprite"),
}
}
}

struct Palette {
index: u8,
background_entries: [u8; 4],
sprite_entries: [u8; 4],
}

impl Palette {
pub fn new(index: u8) -> Self {
Palette {
index,
background_entries: [0; 4],
sprite_entries: [0; 4],
}
}

pub fn set_palette(&mut self, palette_type: PaletteType, palette_index: u8, value: u8) {
debug!(
"[Palette #{}] Setting palette entry for type {:?} at index {} to value: {:#4X}",
self.index, palette_type, palette_index, value
);
match palette_type {
PaletteType::Background => self.background_entries[palette_index as usize] = value,
PaletteType::Sprite => self.sprite_entries[palette_index as usize] = value,
}
}

pub fn get_palette(&self, palette_type: PaletteType, palette_index: u8) -> u8 {
debug!(
"[Palette #{}] Getting palette entry for type {:?} at index {}",
self.index, palette_type, palette_index
);
match palette_type {
PaletteType::Background => self.background_entries[palette_index as usize],
PaletteType::Sprite => self.sprite_entries[palette_index as usize],
}
}
}

pub struct PaletteRAM {
palettes: [Palette; 4],
}

impl PaletteRAM {
pub fn new() -> Self {
info!("PaletteRAM is initializing");
PaletteRAM {
palettes: [
Palette::new(0),
Palette::new(1),
Palette::new(2),
Palette::new(3),
],
}
}

fn read_from_palette(&self, address: u16) -> u8 {
let palette_type = match address {
0x3F00..=0x3F0F => PaletteType::Background,
0x3F10..=0x3F1F => PaletteType::Sprite,
_ => unreachable!(),
};

let index_in_palette = ((address & 0x0F) % 4) as u8;
let index = ((address & 0x0F) >> 4) as usize;

self.palettes[index].get_palette(palette_type, index_in_palette)
}

fn write_to_palette(&mut self, address: u16, data: u8) {
let palette_type = match address {
0x3F00..=0x3F0F => PaletteType::Background,
0x3F10..=0x3F1F => PaletteType::Sprite,
_ => unreachable!(),
};

let index_in_palette = ((address & 0x0F) % 4) as u8;
let index = ((address & 0x0F) >> 4) as usize;

self.palettes[index].set_palette(palette_type, index_in_palette, data);
}

fn mirror_address(&self, address: u16) -> u16 {
// Reduces the address to the range 0x3F00 - 0x3F1F
debug!(
"Mirroring address: {:#6X} down to {:#6X}",
address,
0x3F00 + (address & 0x1F)
);
0x3F00 + (address & 0x1F)
}
}

// Source for PPU Palette Reference can be found here: https://www.nesdev.org/wiki/PPU_palettes

impl Addressable for PaletteRAM {
fn read(&mut self, address: u16) -> u8 {
debug!("Reading from palette address: {:#6X}", address);
match address {
0x3F00..=0x3F1F => self.read_from_palette(address),
0x3F20..=0x3FFF => self.read_from_palette(self.mirror_address(address)),
_ => panic!("Invalid palette address: {:#6X}", address),
}
}

fn write(&mut self, address: u16, data: u8) {
debug!("Reading from palette address: {:#6X}", address);
match address {
0x3F00..=0x3F1F => self.write_to_palette(address, data),
0x3F20..=0x3FFF => self.write_to_palette(self.mirror_address(address), data),
_ => panic!("Invalid palette address: {:#6X}", address),
}
}
}

impl Debug for PaletteRAM {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "PaletteRAM")
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn palette_initializes_correctly() {
let palette = Palette::new(0);
assert_eq!(palette.background_entries, [0; 4]);
assert_eq!(palette.sprite_entries, [0; 4]);
}

#[test]
fn set_and_get_background_palette_entry() {
let mut palette = Palette::new(0);
palette.set_palette(PaletteType::Background, 2, 0x3F);
assert_eq!(palette.get_palette(PaletteType::Background, 2), 0x3F);
}

#[test]
fn set_and_get_sprite_palette_entry() {
let mut palette = Palette::new(0);
palette.set_palette(PaletteType::Sprite, 1, 0x1F);
assert_eq!(palette.get_palette(PaletteType::Sprite, 1), 0x1F);
}

#[test]
fn palette_ram_initializes_correctly() {
let palette_ram = PaletteRAM::new();
for palette in &palette_ram.palettes {
assert_eq!(palette.background_entries, [0; 4]);
assert_eq!(palette.sprite_entries, [0; 4]);
}
}

#[test]
fn read_write_palette_ram_within_bounds() {
let mut palette_ram = PaletteRAM::new();
palette_ram.write(0x3F00, 0x12);
assert_eq!(palette_ram.read(0x3F00), 0x12);
}

#[test]
fn read_write_palette_ram_mirrored_address() {
let mut palette_ram = PaletteRAM::new();
palette_ram.write(0x3F20, 0x34);
assert_eq!(palette_ram.read(0x3F00), 0x34);
}

#[test]
#[should_panic(expected = "Invalid palette address: 0x4000")]
fn read_palette_ram_out_of_bounds() {
let mut palette_ram = PaletteRAM::new();
palette_ram.read(0x4000);
}

#[test]
#[should_panic(expected = "Invalid palette address: 0x4000")]
fn write_palette_ram_out_of_bounds() {
let mut palette_ram = PaletteRAM::new();
palette_ram.write(0x4000, 0x56);
}
}
File renamed without changes.
Loading

0 comments on commit daaed26

Please sign in to comment.