mirror of
https://github.com/FranLMSP/snes.git
synced 2026-08-03 08:30:01 -04:00
676 lines
24 KiB
Rust
676 lines
24 KiB
Rust
// PPU Control
|
|
pub const INIDISP: u16 = 0x2100; // Display Control 1 (W)
|
|
|
|
// PPU Sprites
|
|
pub const OBSEL: u16 = 0x2101; // Object Size and Object Base (W)
|
|
|
|
// OAM Read/Write
|
|
pub const OAMADDL: u16 = 0x2102; // OAM Address (L)
|
|
pub const OAMADDH: u16 = 0x2103; // OAM Address (H)
|
|
pub const OAMDATA: u16 = 0x2104; // OAM Data Write
|
|
|
|
// PPU BG Control
|
|
pub const BGMODE: u16 = 0x2105; // BG Mode and BG Character Size (W)
|
|
pub const MOSAIC: u16 = 0x2106; // Mosaic Size and Mosaic Enable (W)
|
|
pub const BG1SC: u16 = 0x2107; // BG1 Screen Base and Screen Size (W)
|
|
pub const BG2SC: u16 = 0x2108; // BG2 Screen Base and Screen Size (W)
|
|
pub const BG3SC: u16 = 0x2109; // BG3 Screen Base and Screen Size (W)
|
|
pub const BG4SC: u16 = 0x210A; // BG4 Screen Base and Screen Size (W)
|
|
pub const BG12NBA: u16 = 0x210B; // BG Character Data Area Designation (W)
|
|
pub const BG34NBA: u16 = 0x210C; // BG Character Data Area Designation (W)
|
|
pub const BG1HOFS: u16 = 0x210D; // BG1 Horizontal Scroll (X) (W) and M7HOFS
|
|
pub const BG1VOFS: u16 = 0x210E; // BG1 Vertical Scroll (Y) (W) and M7VOFS
|
|
pub const BG2HOFS: u16 = 0x210F; // BG2 Horizontal Scroll (X) (W)
|
|
pub const BG2VOFS: u16 = 0x2110; // BG2 Vertical Scroll (Y) (W)
|
|
pub const BG3HOFS: u16 = 0x2111; // BG3 Horizontal Scroll (X) (W)
|
|
pub const BG3VOFS: u16 = 0x2112; // BG3 Vertical Scroll (Y) (W)
|
|
pub const BG4HOFS: u16 = 0x2113; // BG4 Horizontal Scroll (X) (W)
|
|
pub const BG4VOFS: u16 = 0x2114; // BG4 Horizontal Scroll (X) (W)
|
|
|
|
// PPU VRAM Access
|
|
pub const VMAIN: u16 = 0x2115; // VRAM Address Increment
|
|
pub const VMADDL: u16 = 0x2116; // VRAM Address Low
|
|
pub const VMADDH: u16 = 0x2117; // VRAM Address High
|
|
pub const VMDATAL: u16 = 0x2118; // VRAM Write Low
|
|
pub const VMDATAH: u16 = 0x2119; // VRAM Write High
|
|
|
|
// PPU Rotating/Scaling
|
|
pub const M7SEL: u16 = 0x211A; // Rotation/Scaling Mode Settings (W)
|
|
pub const M7A: u16 = 0x211B; // Rotation/Scaling Parameter A (and Maths 16bit operand) (W)
|
|
pub const M7B: u16 = 0x211C; // Rotation/Scaling Parameter B (and Maths 8bit operand) (W)
|
|
pub const M7C: u16 = 0x211D; // Rotation/Scaling Parameter C (W)
|
|
pub const M7D: u16 = 0x211E; // Rotation/Scaling Parameter D (W)
|
|
pub const M7X: u16 = 0x211F; // Rotation/Scaling Center Coordinate X (W)
|
|
pub const M7Y: u16 = 0x2120; // Rotation/Scaling Center Coordinate Y (W)
|
|
|
|
// PPU CGRAM
|
|
pub const CGADD: u16 = 0x2121; // Palette CGRAM Address
|
|
pub const CGDATA: u16 = 0x2122; // Palette CGRAM Address
|
|
|
|
// PPU Window
|
|
pub const W12SEL: u16 = 0x2123; // Window BG1/BG2 Mask Settings (W)
|
|
pub const W34SEL: u16 = 0x2124; // Window BG3/BG4 Mask Settings (W)
|
|
pub const WOBJSEL: u16 = 0x2125; // Window OBJ/MATH Mask Settings (W)
|
|
pub const WH0: u16 = 0x2126; // Window 1 Left Position (X1) (W)
|
|
pub const WH1: u16 = 0x2127; // Window 1 Right Position (X2) (W)
|
|
pub const WH2: u16 = 0x2128; // Window 2 Left Position (X1) (W)
|
|
pub const WH3: u16 = 0x2129; // Window 2 Right Position (X2) (W)
|
|
pub const WBGLOG: u16 = 0x212A; // Window 1 Mask Logic (W)
|
|
pub const WOBJLOG: u16 = 0x212B; // Window 2 Mask Logic (W)
|
|
pub const TM: u16 = 0x212C; // Main Screen Designation (W)
|
|
pub const TS: u16 = 0x212D; // Sub Screen Designation (W)
|
|
pub const TMW: u16 = 0x212E; // Window Area Main Screen Disable (W)
|
|
pub const TSW: u16 = 0x212F; // Window Area Sub Screen Disable (W)
|
|
|
|
// PPU Color Math
|
|
pub const CGWSEL: u16 = 0x2130; // Color Math Control Register A
|
|
pub const CGADSUB: u16 = 0x2131; // Color Math Control Register B
|
|
pub const COLDATA: u16 = 0x2132; // Color Math Sub Screen Backdrop Color
|
|
|
|
// Display Control
|
|
pub const SETINI: u16 = 0x2133; // Display Control 2 (W)
|
|
|
|
// PPU Read-only ports
|
|
pub const MPYL: u16 = 0x2134; // Signed Multiply Result (lower 8bit)
|
|
pub const MPYM: u16 = 0x2135; // Signed Multiply Result (middle 8bit)
|
|
pub const MPYH: u16 = 0x2136; // Signed Multiply Result (upper 8bit)
|
|
pub const SLHV: u16 = 0x2137; // Latch H/V-Counter by Software (Read=Strobe)
|
|
pub const RDOAM: u16 = 0x2138; // OAM Data Read (read-twice)
|
|
pub const RDVRAML: u16 = 0x2139; // VRAM Read Low
|
|
pub const RDVRAMH: u16 = 0x213A; // VRAM Read High
|
|
pub const RDCGRAM: u16 = 0x213B; // CGRAM Data Read (Palette)(read-twice)
|
|
pub const OPHCT: u16 = 0x213C; // Horizontal Counter Latch (read-twice)
|
|
pub const OPVCT: u16 = 0x213D; // Vertical Counter Latch (read-twice)
|
|
pub const STAT77: u16 = 0x213E; // PPU1 Status and PPU1 Version Number
|
|
pub const STAT78: u16 = 0x213F; // PPU1 Status and PPU1 Version Number
|
|
|
|
|
|
pub const MAX_BG_WIDTH: usize = 16 * 64;
|
|
pub const MAX_BG_HEIGHT: usize = 16 * 64;
|
|
|
|
pub const MAX_TV_WIDTH: usize = 512;
|
|
pub const MAX_TV_HEIGHT: usize = 448;
|
|
|
|
|
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
|
pub enum TileSize {
|
|
P8x8,
|
|
P16x16,
|
|
P16x8,
|
|
}
|
|
|
|
impl TileSize {
|
|
pub fn to_usize(&self) -> (usize, usize) {
|
|
match self {
|
|
Self::P8x8 => (8, 8),
|
|
Self::P16x16 => (16, 16),
|
|
Self::P16x8 => (16, 8),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
|
pub enum BgSize {
|
|
T32x32,
|
|
T64x32, // V Mirror
|
|
T32x64, // H Mirror
|
|
T64x64, // H/V Mirror
|
|
}
|
|
|
|
impl BgSize {
|
|
pub fn to_usize(&self) -> (usize, usize) {
|
|
match self {
|
|
Self::T32x32 => (32, 32),
|
|
Self::T64x32 => (64, 32),
|
|
Self::T32x64 => (32, 64),
|
|
Self::T64x64 => (64, 64),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
|
pub enum BgMode{
|
|
Color2BPP,
|
|
Color4BPP,
|
|
Color8BPP,
|
|
OffsetPerTile,
|
|
ExtBg,
|
|
}
|
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub enum Background {
|
|
Bg1,
|
|
Bg2,
|
|
Bg3,
|
|
Bg4,
|
|
}
|
|
|
|
|
|
pub struct PPURegisters {
|
|
data: [u8; 64],
|
|
vram: [u16; 0x8000],
|
|
pub vblank_nmi: bool,
|
|
pub h_count: u16,
|
|
pub v_count: u16,
|
|
}
|
|
|
|
impl PPURegisters {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
data: [0x00; 64],
|
|
vram: [0; 0x8000],
|
|
vblank_nmi: false,
|
|
h_count: 0,
|
|
v_count: 0,
|
|
}
|
|
}
|
|
|
|
pub fn registers(&self) -> &[u8] {
|
|
&self.data
|
|
}
|
|
|
|
pub fn vram(&self) -> &[u16] {
|
|
&self.vram
|
|
}
|
|
|
|
fn _read(&self, address: u16) -> u8 {
|
|
match address {
|
|
0x2100..=0x213F => self.data[(address as usize) - 0x2100],
|
|
_ => 0x00,
|
|
}
|
|
}
|
|
|
|
pub fn _write(&mut self, address: u16, value: u8) {
|
|
if let 0x2100..=0x213F = address {
|
|
self.data[(address as usize) - 0x2100] = value
|
|
}
|
|
}
|
|
|
|
pub fn read_external(&self, address: u16) -> u8 {
|
|
self._read(address)
|
|
}
|
|
|
|
pub fn read(&mut self, address: u16) -> u8 {
|
|
let result = self._read(address);
|
|
match address {
|
|
VMDATAH => self.handle_vram_addr_auto_increment(Some(result), None),
|
|
VMDATAL => self.handle_vram_addr_auto_increment(None, Some(result)),
|
|
_ => {},
|
|
};
|
|
self._read(address)
|
|
}
|
|
|
|
pub fn write(&mut self, address: u16, value: u8) {
|
|
match address {
|
|
VMDATAH => {
|
|
self._write(address, value);
|
|
self.handle_write_vram(Some(value), None);
|
|
},
|
|
VMDATAL => {
|
|
self._write(address, value);
|
|
self.handle_write_vram(None, Some(value));
|
|
},
|
|
RDVRAML | RDVRAMH => {},
|
|
_ => self._write(address, value),
|
|
};
|
|
}
|
|
|
|
fn handle_write_vram(&mut self, byte_hi: Option<u8>, byte_lo: Option<u8>) {
|
|
let address = (((self.read(VMADDH) as u16) << 8) | (self.read(VMADDL) as u16)) & 0x7FFF;
|
|
let current_word = self.vram[address as usize];
|
|
if let Some(byte) = byte_hi {
|
|
self.vram[address as usize] = (current_word & 0x00FF) | ((byte as u16) << 8);
|
|
self._write(RDVRAMH, byte);
|
|
}
|
|
if let Some(byte) = byte_lo {
|
|
self.vram[address as usize] = (current_word & 0xFF00) | (byte as u16);
|
|
self._write(RDVRAML, byte);
|
|
}
|
|
self.handle_vram_addr_auto_increment(byte_hi, byte_lo);
|
|
}
|
|
|
|
fn handle_vram_addr_auto_increment(&mut self, byte_hi: Option<u8>, byte_lo: Option<u8>) {
|
|
let register = self._read(VMAIN);
|
|
let amount_to_increment = match register & 0b11 {
|
|
0b00 => 1,
|
|
0b01 => 32,
|
|
0b10 => 128,
|
|
0b11 => 128,
|
|
_ => unreachable!(),
|
|
};
|
|
let increment_when_lo = (register >> 7) != 1;
|
|
let increment_when_hi = !increment_when_lo;
|
|
let current_value = self.get_current_vram_address();
|
|
if increment_when_hi && byte_hi.is_some() {
|
|
self.set_current_vram_address(current_value.wrapping_add(amount_to_increment));
|
|
}
|
|
if increment_when_lo && byte_lo.is_some() {
|
|
self.set_current_vram_address(current_value.wrapping_add(amount_to_increment));
|
|
}
|
|
}
|
|
|
|
fn get_current_vram_address(&self) -> u16 {
|
|
((self._read(VMADDH) as u16) << 8) | (self._read(VMADDL) as u16)
|
|
}
|
|
|
|
fn set_current_vram_address(&mut self, value: u16) {
|
|
let bytes = value.to_be_bytes();
|
|
self._write(VMADDH, bytes[0]);
|
|
self._write(VMADDL, bytes[1]);
|
|
}
|
|
|
|
/// 7 BG4 Tile Size (0=8x8, 1=16x16) ;\(BgMode0..4: variable 8x8 or 16x16)
|
|
/// 6 BG3 Tile Size (0=8x8, 1=16x16) ; (BgMode5: 8x8 acts as 16x8)
|
|
/// 5 BG2 Tile Size (0=8x8, 1=16x16) ; (BgMode6: fixed 16x8?)
|
|
/// 4 BG1 Tile Size (0=8x8, 1=16x16) ;/(BgMode7: fixed 8x8)
|
|
/// 3 BG3 Priority in Mode 1 (0=Normal, 1=High)
|
|
/// 2-0 BG Screen Mode (0..7 = see below)
|
|
pub fn get_bg_tile_size(&self, background: Background) -> TileSize {
|
|
let byte = self._read(BGMODE);
|
|
let bit = match background {
|
|
Background::Bg1 => byte >> 4 & 0b1 == 1, // Bit 4
|
|
Background::Bg2 => byte >> 5 & 0b1 == 1, // Bit 5
|
|
Background::Bg3 => byte >> 6 & 0b1 == 1, // Bit 6
|
|
Background::Bg4 => byte >> 7 & 0b1 == 1, // Bit 7
|
|
};
|
|
match bit {
|
|
true => TileSize::P16x16,
|
|
false => TileSize::P8x8,
|
|
}
|
|
}
|
|
|
|
pub fn get_bg_size(&self, background: Background) -> BgSize {
|
|
let byte = match background {
|
|
Background::Bg1 => self._read(BG1SC),
|
|
Background::Bg2 => self._read(BG2SC),
|
|
Background::Bg3 => self._read(BG3SC),
|
|
Background::Bg4 => self._read(BG4SC),
|
|
};
|
|
match byte & 0b11 {
|
|
0 => BgSize::T32x32,
|
|
1 => BgSize::T64x32,
|
|
2 => BgSize::T32x64,
|
|
3 => BgSize::T64x64,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
pub fn get_bg_modes(&self) -> (Option<BgMode>, Option<BgMode>, Option<BgMode>, Option<BgMode>) {
|
|
let byte = self._read(BGMODE);
|
|
match byte & 0b111 {
|
|
0 => (
|
|
Some(BgMode::Color2BPP),
|
|
Some(BgMode::Color2BPP),
|
|
Some(BgMode::Color2BPP),
|
|
Some(BgMode::Color2BPP),
|
|
),
|
|
1 => (
|
|
Some(BgMode::Color4BPP),
|
|
Some(BgMode::Color4BPP),
|
|
Some(BgMode::Color2BPP),
|
|
None,
|
|
),
|
|
2 => (
|
|
Some(BgMode::Color4BPP),
|
|
Some(BgMode::Color4BPP),
|
|
Some(BgMode::OffsetPerTile),
|
|
None,
|
|
),
|
|
3 => (
|
|
Some(BgMode::Color8BPP),
|
|
Some(BgMode::Color4BPP),
|
|
None,
|
|
None,
|
|
),
|
|
4 => (
|
|
Some(BgMode::Color8BPP),
|
|
Some(BgMode::Color2BPP),
|
|
Some(BgMode::OffsetPerTile),
|
|
None,
|
|
),
|
|
5 => (
|
|
Some(BgMode::Color4BPP),
|
|
Some(BgMode::Color2BPP),
|
|
None,
|
|
None,
|
|
),
|
|
6 => (
|
|
Some(BgMode::Color4BPP),
|
|
None,
|
|
Some(BgMode::OffsetPerTile),
|
|
None,
|
|
),
|
|
7 => (
|
|
Some(BgMode::Color8BPP),
|
|
Some(BgMode::ExtBg),
|
|
None,
|
|
None,
|
|
),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
pub fn get_bg_tile_base_address(&self, background: Background) -> u16 {
|
|
let register = match background {
|
|
Background::Bg1 => BG1SC,
|
|
Background::Bg2 => BG2SC,
|
|
Background::Bg3 => BG3SC,
|
|
Background::Bg4 => BG4SC,
|
|
};
|
|
// Most significant bit is unused
|
|
let base_address = (self._read(register) & 0b01111111) >> 2;
|
|
(base_address as u16) * 0x400
|
|
}
|
|
|
|
pub fn get_bg_char_base_address(&self, background: Background) -> u16 {
|
|
let register = match background {
|
|
Background::Bg1 => self._read(BG12NBA),
|
|
Background::Bg2 => self._read(BG12NBA) >> 4,
|
|
Background::Bg3 => self._read(BG34NBA),
|
|
Background::Bg4 => self._read(BG34NBA) >> 4,
|
|
};
|
|
// Most significant bit is unused
|
|
((register as u16) & 0b111) * 0x1000
|
|
}
|
|
|
|
pub fn is_vblanking(&self) -> bool {
|
|
!(self.v_count >= 1 && self.v_count <= 224)
|
|
}
|
|
|
|
pub fn is_hblanking(&self) -> bool {
|
|
!(self.h_count >= 22 && self.h_count <= 277)
|
|
}
|
|
|
|
pub fn get_current_res(&self) -> (u16, u16) {
|
|
let w = if self.is_true_high_res_mode_enabled() {512} else {256};
|
|
let h = if self.is_interlace_mode_enabled() {448} else {224};
|
|
(w, h)
|
|
}
|
|
|
|
pub fn is_interlace_mode_enabled(&self) -> bool {
|
|
self._read(SETINI) & 0x01 == 0b1
|
|
}
|
|
|
|
pub fn is_true_high_res_mode_enabled(&self) -> bool {
|
|
let current_bg_mode = self._read(BGMODE);
|
|
current_bg_mode == 5 || current_bg_mode == 6
|
|
}
|
|
}
|
|
|
|
impl Default for PPURegisters {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
mod ppu_registers_test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_get_bg_tile_size() {
|
|
let mut registers = PPURegisters::new();
|
|
registers.write(BGMODE, 0b00000000);
|
|
assert_eq!(registers.get_bg_tile_size(Background::Bg1), TileSize::P8x8);
|
|
registers.write(BGMODE, 0b00010000);
|
|
assert_eq!(registers.get_bg_tile_size(Background::Bg1), TileSize::P16x16);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_bg_size() {
|
|
let mut registers = PPURegisters::new();
|
|
registers.write(BG1SC, 2);
|
|
assert_eq!(registers.get_bg_size(Background::Bg1), BgSize::T32x64);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_bg_modes() {
|
|
let mut registers = PPURegisters::new();
|
|
registers.write(BGMODE, 0);
|
|
assert_eq!(
|
|
registers.get_bg_modes(),
|
|
(Some(BgMode::Color2BPP), Some(BgMode::Color2BPP), Some(BgMode::Color2BPP), Some(BgMode::Color2BPP)),
|
|
);
|
|
registers.write(BGMODE, 1);
|
|
assert_eq!(
|
|
registers.get_bg_modes(),
|
|
(Some(BgMode::Color4BPP), Some(BgMode::Color4BPP), Some(BgMode::Color2BPP), None),
|
|
);
|
|
registers.write(BGMODE, 2);
|
|
assert_eq!(
|
|
registers.get_bg_modes(),
|
|
(Some(BgMode::Color4BPP), Some(BgMode::Color4BPP), Some(BgMode::OffsetPerTile), None),
|
|
);
|
|
registers.write(BGMODE, 3);
|
|
assert_eq!(
|
|
registers.get_bg_modes(),
|
|
(Some(BgMode::Color8BPP), Some(BgMode::Color4BPP), None, None),
|
|
);
|
|
registers.write(BGMODE, 4);
|
|
assert_eq!(
|
|
registers.get_bg_modes(),
|
|
(Some(BgMode::Color8BPP), Some(BgMode::Color2BPP), Some(BgMode::OffsetPerTile), None),
|
|
);
|
|
registers.write(BGMODE, 5);
|
|
assert_eq!(
|
|
registers.get_bg_modes(),
|
|
(Some(BgMode::Color4BPP), Some(BgMode::Color2BPP), None, None),
|
|
);
|
|
registers.write(BGMODE, 6);
|
|
assert_eq!(
|
|
registers.get_bg_modes(),
|
|
(Some(BgMode::Color4BPP), None, Some(BgMode::OffsetPerTile), None),
|
|
);
|
|
registers.write(BGMODE, 7);
|
|
assert_eq!(
|
|
registers.get_bg_modes(),
|
|
(Some(BgMode::Color8BPP), Some(BgMode::ExtBg), None, None),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_bg_tile_base_address() {
|
|
let mut registers = PPURegisters::new();
|
|
registers.write(BG1SC, 0x00);
|
|
assert_eq!(registers.get_bg_tile_base_address(Background::Bg1), 0x0000);
|
|
registers.write(BG2SC, 0b00000100);
|
|
assert_eq!(registers.get_bg_tile_base_address(Background::Bg2), 0x0400);
|
|
registers.write(BG3SC, 0b10000100);
|
|
assert_eq!(registers.get_bg_tile_base_address(Background::Bg3), 0x0400);
|
|
registers.write(BG4SC, 0b11111100);
|
|
assert_eq!(registers.get_bg_tile_base_address(Background::Bg4), 0x7C00);
|
|
registers.write(BG1SC, 0b00001000);
|
|
assert_eq!(registers.get_bg_tile_base_address(Background::Bg1), 0x0800);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_bg_char_base_address() {
|
|
let mut registers = PPURegisters::new();
|
|
registers.write(BG12NBA, 0b10100001);
|
|
registers.write(BG34NBA, 0b01011111);
|
|
assert_eq!(registers.get_bg_char_base_address(Background::Bg1), 0x1000);
|
|
assert_eq!(registers.get_bg_char_base_address(Background::Bg2), 0x2000);
|
|
assert_eq!(registers.get_bg_char_base_address(Background::Bg3), 0x7000);
|
|
assert_eq!(registers.get_bg_char_base_address(Background::Bg4), 0x5000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_vram_registers() {
|
|
let mut registers = PPURegisters::new();
|
|
registers.write(VMADDL, 0x00);
|
|
registers.write(VMADDH, 0x00);
|
|
registers.write(VMDATAH, 0xAB);
|
|
registers.write(VMDATAL, 0xCD);
|
|
assert_eq!(registers.vram[0x0000], 0xABCD);
|
|
|
|
registers.write(VMADDH, 0x12);
|
|
registers.write(VMADDL, 0x34);
|
|
registers.write(VMDATAH, 0xAB);
|
|
registers.write(VMDATAL, 0xCD);
|
|
assert_eq!(registers.vram[0x1234], 0xABCD);
|
|
assert_eq!(registers.read(RDVRAMH), 0xAB);
|
|
assert_eq!(registers.read(RDVRAML), 0xCD);
|
|
|
|
registers.write(VMADDH, 0x7F);
|
|
registers.write(VMADDL, 0xFF);
|
|
registers.write(VMDATAH, 0xAB);
|
|
registers.write(VMDATAL, 0xCD);
|
|
assert_eq!(registers.vram[0x7FFF], 0xABCD);
|
|
assert_eq!(registers.read(RDVRAMH), 0xAB);
|
|
assert_eq!(registers.read(RDVRAML), 0xCD);
|
|
}
|
|
|
|
#[test]
|
|
fn test_auto_increment_vram_address() {
|
|
let mut registers = PPURegisters::new();
|
|
// Increment after writing
|
|
registers.write(VMADDL, 0x00);
|
|
registers.write(VMADDH, 0x00);
|
|
|
|
// Increment when low bit is written to
|
|
registers.write(VMAIN, 0x00);
|
|
|
|
registers.write(VMDATAH, 0xAA);
|
|
assert_eq!(registers.get_current_vram_address(), 0x0000);
|
|
registers.write(VMDATAL, 0xAA);
|
|
assert_eq!(registers.get_current_vram_address(), 0x0001);
|
|
|
|
// Increment when hi bit is written to
|
|
registers.write(VMAIN, 0b1000_0000);
|
|
registers.write(VMDATAH, 0xAA);
|
|
assert_eq!(registers.get_current_vram_address(), 0x0002);
|
|
registers.write(VMDATAL, 0xAA);
|
|
assert_eq!(registers.get_current_vram_address(), 0x0002);
|
|
|
|
|
|
// Increment after reading
|
|
registers.write(VMADDL, 0x00);
|
|
registers.write(VMADDH, 0x00);
|
|
|
|
// Increment when low bit is read from
|
|
registers.write(VMAIN, 0x00);
|
|
|
|
registers.read(VMDATAH);
|
|
assert_eq!(registers.get_current_vram_address(), 0x0000);
|
|
registers.read(VMDATAL);
|
|
assert_eq!(registers.get_current_vram_address(), 0x0001);
|
|
|
|
// Increment when hi bit is read from
|
|
registers.write(VMAIN, 0b1000_0000);
|
|
registers.read(VMDATAH);
|
|
assert_eq!(registers.get_current_vram_address(), 0x0002);
|
|
registers.read(VMDATAL);
|
|
assert_eq!(registers.get_current_vram_address(), 0x0002);
|
|
|
|
// Increment amounts
|
|
registers.write(VMAIN, 0b1000_0000);
|
|
registers.write(VMADDL, 0x00);
|
|
registers.write(VMADDH, 0x00);
|
|
registers.write(VMDATAH, 0xAA);
|
|
assert_eq!(registers.get_current_vram_address(), 1);
|
|
|
|
registers.write(VMAIN, 0b1000_0001);
|
|
registers.write(VMADDL, 0x00);
|
|
registers.write(VMADDH, 0x00);
|
|
registers.write(VMDATAH, 0xAA);
|
|
assert_eq!(registers.get_current_vram_address(), 32);
|
|
|
|
registers.write(VMAIN, 0b1000_0010);
|
|
registers.write(VMADDL, 0x00);
|
|
registers.write(VMADDH, 0x00);
|
|
registers.write(VMDATAH, 0xAA);
|
|
assert_eq!(registers.get_current_vram_address(), 128);
|
|
|
|
registers.write(VMAIN, 0b1000_0011);
|
|
registers.write(VMADDL, 0x00);
|
|
registers.write(VMADDH, 0x00);
|
|
registers.write(VMDATAH, 0xAA);
|
|
assert_eq!(registers.get_current_vram_address(), 128);
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_vblanking() {
|
|
let mut registers = PPURegisters::new();
|
|
registers.v_count = 339;
|
|
assert!(registers.is_vblanking());
|
|
registers.v_count = 0;
|
|
assert!(registers.is_vblanking());
|
|
registers.v_count = 225;
|
|
assert!(registers.is_vblanking());
|
|
registers.v_count = 224;
|
|
assert!(!registers.is_vblanking());
|
|
registers.v_count = 2;
|
|
assert!(!registers.is_vblanking());
|
|
registers.v_count = 50;
|
|
assert!(!registers.is_vblanking());
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_hblanking() {
|
|
let mut registers = PPURegisters::new();
|
|
registers.h_count = 0;
|
|
assert!(registers.is_hblanking());
|
|
registers.h_count = 20;
|
|
assert!(registers.is_hblanking());
|
|
registers.h_count = 21;
|
|
assert!(registers.is_hblanking());
|
|
registers.h_count = 278;
|
|
assert!(registers.is_hblanking());
|
|
registers.h_count = 300;
|
|
assert!(registers.is_hblanking());
|
|
registers.h_count = 22;
|
|
assert!(!registers.is_hblanking());
|
|
registers.h_count = 100;
|
|
assert!(!registers.is_hblanking());
|
|
registers.h_count = 277;
|
|
assert!(!registers.is_hblanking());
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_interlace_mode_enabled() {
|
|
let mut registers = PPURegisters::new();
|
|
registers._write(SETINI, 0x01);
|
|
assert!(registers.is_interlace_mode_enabled());
|
|
registers._write(SETINI, 0x00);
|
|
assert!(!registers.is_interlace_mode_enabled());
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_true_high_res_mode_enabled() {
|
|
let mut registers = PPURegisters::new();
|
|
registers._write(BGMODE, 1);
|
|
assert!(!registers.is_true_high_res_mode_enabled());
|
|
registers._write(BGMODE, 2);
|
|
assert!(!registers.is_true_high_res_mode_enabled());
|
|
registers._write(BGMODE, 3);
|
|
assert!(!registers.is_true_high_res_mode_enabled());
|
|
registers._write(BGMODE, 4);
|
|
assert!(!registers.is_true_high_res_mode_enabled());
|
|
registers._write(BGMODE, 5);
|
|
assert!(registers.is_true_high_res_mode_enabled());
|
|
registers._write(BGMODE, 6);
|
|
assert!(registers.is_true_high_res_mode_enabled());
|
|
registers._write(BGMODE, 7);
|
|
assert!(!registers.is_true_high_res_mode_enabled());
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_current_res() {
|
|
let mut registers = PPURegisters::new();
|
|
registers._write(BGMODE, 1);
|
|
registers._write(SETINI, 0x00);
|
|
assert_eq!(registers.get_current_res(), (256, 224));
|
|
registers._write(BGMODE, 1);
|
|
registers._write(SETINI, 0x01);
|
|
assert_eq!(registers.get_current_res(), (256, 448));
|
|
registers._write(BGMODE, 5);
|
|
registers._write(SETINI, 0x00);
|
|
assert_eq!(registers.get_current_res(), (512, 224));
|
|
registers._write(BGMODE, 6);
|
|
registers._write(SETINI, 0x01);
|
|
assert_eq!(registers.get_current_res(), (512, 448));
|
|
}
|
|
}
|