const GENERATE_NMI: u8 = 0b1000_0000;
const SPRITE_SIZE: u8 = 0b0010_0000;
const BG_PATTERN_TABLE_ADDR: u8 = 0b0001_0000;
const SPRITE_PATTERN_TABLE_ADDR: u8 = 0b0000_1000;
const VRAM_ADDR_INCREMENT: u8 = 0b0000_0100;
const BASE_NAMETABLE_ADDR: u8 = 0b0000_0011;
const SHOW_BACKGROUND: u8 = 0b0000_1000;
const SHOW_SPRITES: u8 = 0b0001_0000;
const SHOW_BACKGROUND_LEFT: u8 = 0b0000_0010;
const SHOW_SPRITES_LEFT: u8 = 0b0000_0100;
const GRAYSCALE: u8 = 0b0000_0001;
#[derive(Debug)]
pub struct Registers {
control_register: u8,
mask_register: u8,
pub oam_address: u8,
data_buffer: u8,
io_bus: u8,
io_bus_refresh_time: [u64; 8],
cycle_count: u64,
v: u16,
t: u16,
x: u8,
w: bool,
}
impl Default for Registers {
fn default() -> Self {
Self::new()
}
}
impl Registers {
pub fn new() -> Self {
Self {
control_register: 0,
mask_register: 0,
oam_address: 0,
data_buffer: 0,
io_bus: 0,
io_bus_refresh_time: [0; 8],
cycle_count: 0,
v: 0,
t: 0,
x: 0,
w: false,
}
}
pub fn reset(&mut self) {
self.control_register = 0;
self.mask_register = 0;
self.oam_address = 0;
self.data_buffer = 0;
self.io_bus = 0;
self.io_bus_refresh_time = [0; 8];
self.cycle_count = 0;
self.v = 0;
self.t = 0;
self.x = 0;
self.w = false;
}
pub fn write_control(&mut self, value: u8) {
self.control_register = value;
let nametable_bits = (value & BASE_NAMETABLE_ADDR) as u16;
self.t = (self.t & 0xF3FF) | (nametable_bits << 10);
}
pub fn write_mask(&mut self, value: u8) {
self.mask_register = value;
}
pub fn io_bus(&self) -> u8 {
const DECAY_CYCLES: u64 = 3_216_312;
let mut result = self.io_bus;
for i in 0..8 {
if (result & (1 << i)) != 0 {
let cycles_since_refresh =
self.cycle_count.saturating_sub(self.io_bus_refresh_time[i]);
if cycles_since_refresh > DECAY_CYCLES {
result &= !(1 << i); }
}
}
result
}
pub fn set_io_bus(&mut self, value: u8) {
self.io_bus = value;
for i in 0..8 {
if (value & (1 << i)) != 0 {
self.io_bus_refresh_time[i] = self.cycle_count;
}
}
}
pub fn set_io_bus_with_mask(&mut self, value: u8, refresh_mask: u8) {
self.io_bus = value;
for i in 0..8 {
if (value & (1 << i)) != 0 && (refresh_mask & (1 << i)) != 0 {
self.io_bus_refresh_time[i] = self.cycle_count;
}
}
}
pub fn tick(&mut self) {
self.cycle_count = self.cycle_count.wrapping_add(1);
}
pub fn write_scroll(&mut self, value: u8, is_dummy_write: bool) {
let _ = is_dummy_write;
if !self.w {
self.t = (self.t & 0xFFE0) | ((value as u16) >> 3);
self.x = value & 0x07;
self.w = true;
} else {
self.t = (self.t & 0x8FFF) | (((value as u16) & 0x07) << 12);
self.t = (self.t & 0xFC1F) | (((value as u16) & 0xF8) << 2);
self.w = false;
}
}
pub fn write_address(&mut self, value: u8, is_dummy_write: bool) {
let _ = is_dummy_write;
if !self.w {
self.t = (self.t & 0x80FF) | (((value & 0x3F) as u16) << 8);
self.w = true;
} else {
self.t = (self.t & 0xFF00) | (value as u16);
self.v = self.t;
self.w = false;
}
}
pub fn data_buffer(&self) -> u8 {
self.data_buffer
}
pub fn set_data_buffer(&mut self, value: u8) {
self.data_buffer = value;
}
pub fn increment_vram_address(&mut self) {
let increment = if (self.control_register & VRAM_ADDR_INCREMENT) != 0 {
32
} else {
1
};
self.v = self.v.wrapping_add(increment) & 0x3FFF;
}
pub fn increment_coarse_x(&mut self) {
if (self.v & 0x001F) == 31 {
self.v &= !0x001F;
self.v ^= 0x0400;
} else {
self.v += 1;
}
}
pub fn increment_fine_y(&mut self) {
if (self.v & 0x7000) != 0x7000 {
self.v += 0x1000;
} else {
self.v &= !0x7000;
let mut y = (self.v & 0x03E0) >> 5;
if y == 29 {
y = 0;
self.v ^= 0x0800;
} else if y == 31 {
y = 0;
} else {
y += 1;
}
self.v = (self.v & !0x03E0) | (y << 5);
}
}
pub fn copy_horizontal_bits(&mut self) {
self.v = (self.v & 0xFBE0) | (self.t & 0x041F);
}
pub fn copy_vertical_bits(&mut self) {
self.v = (self.v & 0x841F) | (self.t & 0x7BE0);
}
pub fn inc_address_with_rendering_glitch(&mut self) {
self.increment_coarse_x();
self.increment_fine_y();
}
pub fn v(&self) -> u16 {
self.v
}
pub fn t(&self) -> u16 {
self.t
}
pub fn x(&self) -> u8 {
self.x
}
pub fn w(&self) -> bool {
self.w
}
pub fn clear_w(&mut self) {
self.w = false;
}
pub fn should_generate_nmi(&self) -> bool {
(self.control_register & GENERATE_NMI) != 0
}
pub fn is_background_enabled(&self) -> bool {
(self.mask_register & SHOW_BACKGROUND) != 0
}
pub fn is_sprite_enabled(&self) -> bool {
(self.mask_register & SHOW_SPRITES) != 0
}
pub fn is_rendering_enabled(&self) -> bool {
self.is_background_enabled() || self.is_sprite_enabled()
}
pub fn show_background_left(&self) -> bool {
(self.mask_register & SHOW_BACKGROUND_LEFT) != 0
}
pub fn show_sprites_left(&self) -> bool {
(self.mask_register & SHOW_SPRITES_LEFT) != 0
}
pub fn is_grayscale(&self) -> bool {
(self.mask_register & GRAYSCALE) != 0
}
pub fn color_emphasis(&self) -> u8 {
(self.mask_register >> 5) & 0x07
}
pub fn sprite_height(&self) -> u8 {
if (self.control_register & SPRITE_SIZE) != 0 {
16
} else {
8
}
}
pub fn bg_pattern_table_addr(&self) -> u16 {
if (self.control_register & BG_PATTERN_TABLE_ADDR) != 0 {
0x1000
} else {
0x0000
}
}
pub fn sprite_pattern_table_addr(&self) -> u16 {
if (self.control_register & SPRITE_PATTERN_TABLE_ADDR) != 0 {
0x1000
} else {
0x0000
}
}
pub fn control(&self) -> u8 {
self.control_register
}
#[cfg(test)]
#[allow(dead_code)]
pub fn base_nametable_addr(&self) -> u16 {
let nametable_select = (self.control_register & BASE_NAMETABLE_ADDR) as u16;
0x2000 | (nametable_select << 10)
}
pub fn mask(&self) -> u8 {
self.mask_register
}
#[allow(clippy::too_many_arguments)]
pub fn restore_state(
&mut self,
control: u8,
mask: u8,
oam_address: u8,
v: u16,
t: u16,
fine_x: u8,
w: bool,
io_bus: u8,
data_buffer: u8,
) {
self.control_register = control;
self.mask_register = mask;
self.oam_address = oam_address;
self.v = v;
self.t = t;
self.x = fine_x;
self.w = w;
self.io_bus = io_bus;
self.data_buffer = data_buffer;
}
pub fn io_bus_refresh_time(&self) -> [u64; 8] {
self.io_bus_refresh_time
}
pub fn cycle_count(&self) -> u64 {
self.cycle_count
}
pub fn set_io_bus_refresh_time(&mut self, times: [u64; 8]) {
self.io_bus_refresh_time = times;
}
pub fn set_cycle_count(&mut self, cycles: u64) {
self.cycle_count = cycles;
}
#[allow(dead_code)]
pub fn scroll_state(&self) -> (u16, u16, u8, bool) {
(self.t, self.v, self.x, self.w)
}
}
#[cfg(test)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegistersDebugState {
pub control_register: u8,
pub mask_register: u8,
pub oam_address: u8,
pub data_buffer: u8,
pub io_bus: u8,
pub io_bus_refresh_time: [u64; 8],
pub cycle_count: u64,
pub v: u16,
pub t: u16,
pub x: u8,
pub w: bool,
}
#[cfg(test)]
impl Registers {
pub fn debug_state(&self) -> RegistersDebugState {
RegistersDebugState {
control_register: self.control_register,
mask_register: self.mask_register,
oam_address: self.oam_address,
data_buffer: self.data_buffer,
io_bus: self.io_bus,
io_bus_refresh_time: self.io_bus_refresh_time,
cycle_count: self.cycle_count,
v: self.v,
t: self.t,
x: self.x,
w: self.w,
}
}
pub fn set_debug_state(&mut self, state: RegistersDebugState) {
self.control_register = state.control_register;
self.mask_register = state.mask_register;
self.oam_address = state.oam_address;
self.data_buffer = state.data_buffer;
self.io_bus = state.io_bus;
self.io_bus_refresh_time = state.io_bus_refresh_time;
self.cycle_count = state.cycle_count;
self.v = state.v;
self.t = state.t;
self.x = state.x;
self.w = state.w;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registers_new() {
let regs = Registers::new();
assert_eq!(regs.control(), 0);
assert_eq!(regs.mask(), 0);
assert_eq!(regs.v(), 0);
assert_eq!(regs.t(), 0);
}
#[test]
fn test_write_control() {
let mut regs = Registers::new();
regs.write_control(0b0000_0011);
assert_eq!(regs.t() & 0x0C00, 0x0C00);
}
#[test]
fn test_write_scroll_first() {
let mut regs = Registers::new();
regs.write_scroll(0b11111111, false);
assert_eq!(regs.t() & 0x1F, 0b11111);
assert_eq!(regs.x(), 0b111);
assert!(regs.w());
}
#[test]
fn test_write_scroll_second() {
let mut regs = Registers::new();
regs.write_scroll(0, false);
regs.write_scroll(0b11111111, false);
assert!(!regs.w());
}
#[test]
fn test_write_address() {
let mut regs = Registers::new();
regs.write_address(0x3F, false);
regs.write_address(0x00, false);
assert_eq!(regs.v(), 0x3F00);
}
#[test]
fn test_write_control_only_updates_nametable_bits_in_t() {
let mut regs = Registers::new();
regs.write_address(0x3F, false);
regs.write_address(0xFF, false);
let before = regs.t();
regs.write_control(0x00);
let after = regs.t();
assert_eq!(after & !0x0C00, before & !0x0C00);
assert_eq!(after & 0x0C00, 0x0000);
regs.write_control(0x03);
let after2 = regs.t();
assert_eq!(after2 & !0x0C00, before & !0x0C00);
assert_eq!(after2 & 0x0C00, 0x0C00);
}
#[test]
fn test_scroll_state_reports_internal_registers() {
let mut regs = Registers::new();
regs.write_scroll(0b1001_0110, false);
let (t, v, x, w) = regs.scroll_state();
assert_eq!(t, regs.t());
assert_eq!(v, regs.v());
assert_eq!(x, regs.x());
assert_eq!(w, regs.w());
}
#[test]
fn test_increment_coarse_x() {
let mut regs = Registers::new();
regs.v = 0;
regs.increment_coarse_x();
assert_eq!(regs.v() & 0x1F, 1);
}
#[test]
fn test_increment_coarse_x_wrap() {
let mut regs = Registers::new();
regs.v = 31;
regs.increment_coarse_x();
assert_eq!(regs.v() & 0x1F, 0);
assert_eq!(regs.v() & 0x0400, 0x0400);
}
#[test]
fn test_increment_fine_y() {
let mut regs = Registers::new();
regs.v = 0;
regs.increment_fine_y();
assert_eq!(regs.v() & 0x7000, 0x1000);
}
#[test]
fn test_copy_horizontal_bits() {
let mut regs = Registers::new();
regs.t = 0x041F;
regs.v = 0;
regs.copy_horizontal_bits();
assert_eq!(regs.v() & 0x041F, 0x041F);
}
#[test]
fn test_copy_vertical_bits() {
let mut regs = Registers::new();
regs.t = 0x7BE0;
regs.v = 0;
regs.copy_vertical_bits();
assert_eq!(regs.v() & 0x7BE0, 0x7BE0);
}
#[test]
fn test_sprite_height_8x8() {
let mut regs = Registers::new();
regs.write_control(0);
assert_eq!(regs.sprite_height(), 8);
}
#[test]
fn test_sprite_height_8x16() {
let mut regs = Registers::new();
regs.write_control(SPRITE_SIZE);
assert_eq!(regs.sprite_height(), 16);
}
#[test]
fn test_is_grayscale() {
let mut regs = Registers::new();
regs.write_mask(GRAYSCALE);
assert!(regs.is_grayscale());
}
const EMPHASIZE_RED: u8 = 0b0010_0000;
#[test]
fn test_color_emphasis() {
let mut regs = Registers::new();
regs.write_mask(EMPHASIZE_RED);
assert_eq!(regs.color_emphasis() & 0x01, 0x01);
}
const IO_BUS_DECAY_CYCLES: u64 = 3_216_312;
#[test]
fn test_io_bus_decay_after_threshold() {
let mut regs = Registers::new();
regs.set_cycle_count(0);
regs.set_io_bus(0xF0);
regs.set_cycle_count(IO_BUS_DECAY_CYCLES + 1);
assert_eq!(regs.io_bus(), 0x00);
}
#[test]
fn test_io_bus_decay_refreshes_on_update() {
let mut regs = Registers::new();
regs.set_cycle_count(0);
regs.set_io_bus(0x01);
regs.set_cycle_count(IO_BUS_DECAY_CYCLES + 1);
assert_eq!(regs.io_bus(), 0x00);
regs.set_io_bus(0x01);
regs.set_cycle_count(IO_BUS_DECAY_CYCLES + 1 + (IO_BUS_DECAY_CYCLES / 2));
assert_eq!(regs.io_bus(), 0x01);
}
}