Skip to content

Commit

Permalink
Merge pull request #269 from NoxHarmonium/ppu-bg-impl
Browse files Browse the repository at this point in the history
 feat(video): added PPU control registers
  • Loading branch information
NoxHarmonium authored Jul 3, 2024
2 parents 6553e38 + 6b0dfb1 commit d7292d7
Show file tree
Hide file tree
Showing 15 changed files with 247 additions and 24 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/sirc-vm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ jobs:
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings

# Currently separate until llvm-cov supports them
- name: Run doctests
run: cargo test --doc --verbose --all-features --workspace

# TODO: Include doc tests in coverage once it is stabilised
# https://github.com/taiki-e/cargo-llvm-cov/issues/2
- name: Run tests
run: cargo llvm-cov --verbose --all-targets --all-features --workspace --codecov --output-path codecov-main.json

Expand Down Expand Up @@ -95,4 +101,4 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
files: codecov-*.json
fail_ci_if_error: true
fail_ci_if_error: true
5 changes: 4 additions & 1 deletion sirc-vm/device-debug/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::any::Any;

use log::debug;
use peripheral_bus::memory_mapped_device::MemoryMapped;
use peripheral_bus::{
device::BusAssertions, device::Device, memory_mapped_device::MemoryMappedDevice,
};
Expand Down Expand Up @@ -53,7 +54,7 @@ impl Device for DebugDevice {
}
}

impl MemoryMappedDevice for DebugDevice {
impl MemoryMapped for DebugDevice {
fn read_address(&self, address: u32) -> u16 {
debug!("Reading from address 0x{address:X}");
match address {
Expand All @@ -78,3 +79,5 @@ impl MemoryMappedDevice for DebugDevice {
}
}
}

impl MemoryMappedDevice for DebugDevice {}
5 changes: 4 additions & 1 deletion sirc-vm/device-ram/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{
};

use memmap::{MmapMut, MmapOptions};
use peripheral_bus::memory_mapped_device::MemoryMapped;
use peripheral_bus::{
device::BusAssertions, device::Device, memory_mapped_device::MemoryMappedDevice,
};
Expand Down Expand Up @@ -61,7 +62,7 @@ impl Device for RamDevice {
}
}

impl MemoryMappedDevice for RamDevice {
impl MemoryMapped for RamDevice {
fn read_address(&self, address: u32) -> u16 {
let address_pointer = address as usize * 2;

Expand Down Expand Up @@ -91,7 +92,9 @@ impl MemoryMappedDevice for RamDevice {
raw_memory[address_pointer] = byte_pair[0];
raw_memory[address_pointer + 1] = byte_pair[1];
}
}

impl MemoryMappedDevice for RamDevice {
fn read_raw_bytes(&self, limit: u32) -> Vec<u8> {
let cell = self.mem_cell.borrow();

Expand Down
7 changes: 4 additions & 3 deletions sirc-vm/device-terminal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use log::error;

use peripheral_bus::device::BusAssertions;
use peripheral_bus::device::Device;
use peripheral_bus::memory_mapped_device::MemoryMappedDevice;
use peripheral_bus::memory_mapped_device::{MemoryMapped, MemoryMappedDevice};

use std::any::Any;
use std::collections::VecDeque;
Expand Down Expand Up @@ -157,8 +157,7 @@ impl Device for TerminalDevice {
}
}

#[allow(clippy::cast_possible_truncation)]
impl MemoryMappedDevice for TerminalDevice {
impl MemoryMapped for TerminalDevice {
fn read_address(&self, address: u32) -> u16 {
match address {
0x0 => self.control_registers.baud,
Expand Down Expand Up @@ -186,3 +185,5 @@ impl MemoryMappedDevice for TerminalDevice {
}
}
}

impl MemoryMappedDevice for TerminalDevice {}
3 changes: 2 additions & 1 deletion sirc-vm/device-video/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ edition = "2021"
[dependencies]
peripheral_bus = { path = "../peripheral-bus" }
log = "0.4.21"
minifb = "0.27"
minifb = "0.27"
modular-bitfield = "0.11.2"
17 changes: 11 additions & 6 deletions sirc-vm/device-video/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
mod types;

use std::{any::Any, time::Duration};

use crate::types::PpuRegisters;
use log::{debug, info};
use minifb::{Window, WindowOptions};
use peripheral_bus::memory_mapped_device::MemoryMapped;
use peripheral_bus::{
device::BusAssertions, device::Device, memory_mapped_device::MemoryMappedDevice,
};

// Some reference:
// https://www.raphnet.net/divers/retro_challenge_2019_03/qsnesdoc.html#Reg2115
// https://martin.hinner.info/vga/pal.html#:~:text=PAL%20details&text=CCIR%2FPAL%20standard%20video%20signal,Thus%20field%20rate%20is%2050.
Expand Down Expand Up @@ -55,6 +58,7 @@ pub struct VideoDevice {

// Native
vram: Vec<u16>,
ppu_registers: PpuRegisters,

// Other
frame_count: usize,
Expand Down Expand Up @@ -124,6 +128,7 @@ pub fn new_video_device(master_clock_freq: usize) -> VideoDevice {
buffer: vec![0; total_pixels],
window,
vram: vec![0; VRAM_SIZE],
ppu_registers: PpuRegisters::default(),
frame_count: 0,
frame_clock: 0,
clocks_per_line,
Expand Down Expand Up @@ -182,14 +187,12 @@ impl Device for VideoDevice {
}
}

impl MemoryMappedDevice for VideoDevice {
impl MemoryMapped for VideoDevice {
fn read_address(&self, address: u32) -> u16 {
debug!("Reading from address 0x{address:X}");
match address {
// First FF addresses are control registers
// TODO: Design and implement PPU control registers
// category=Features
0x0000..=0x00FF => 0x0,
0x0000..=0x00FF => self.ppu_registers.read_address(address),
// After that range
_ => self.vram[(address as usize) - 0x00FF],
}
Expand All @@ -201,10 +204,12 @@ impl MemoryMappedDevice for VideoDevice {
match address {
// First FF addresses are control registers
0x0000..=0x00FF => {
// TODO
self.ppu_registers.write_address(address, value);
}
// After that range
_ => self.vram[(address as usize) - 0x00FF] = value,
}
}
}

impl MemoryMappedDevice for VideoDevice {}
200 changes: 200 additions & 0 deletions sirc-vm/device-video/src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Suppress warnings caused by macro code
#![allow(redundant_semicolons, dead_code)]

use modular_bitfield::prelude::*;
use peripheral_bus::memory_mapped_device::MemoryMapped;

const READONLY_STATUS_REGISTER_ADDR: u32 = 0x0013;
// TODO: Use exclusive range in match when rust 1.80 is released
// category=Refactoring
// Then we can get rid of this. See also: https://github.com/rust-lang/rust/issues/37854
const READONLY_STATUS_REGISTER_ADDR_MINUS_ONE: u32 = READONLY_STATUS_REGISTER_ADDR - 1;
#[bitfield(bytes = 2)]
#[derive(Debug, Default)]
pub struct BaseConfigRegister {
pub graphics_disable: B1,
pub b1_disable: B1,
pub b2_disable: B1,
pub b3_disable: B1,
pub reserved_1: B1,
pub s1_disable: B1,
pub s2_disable: B1,
pub s3_disable: B1,
pub reserved_2: B1,
pub screen_brightness: B4,
pub reserved_3: B3,
}

#[derive(BitfieldSpecifier, Debug)]
pub enum TileSize {
EightByEight,
SixteenBySixteen,
}

#[derive(BitfieldSpecifier, Debug)]
pub enum TilemapSize {
Single,
Double,
}

#[bitfield(bytes = 2)]
#[derive(Debug, Default)]
pub struct TileSizeRegister {
#[bits = 1]
pub b1_size: TileSize,
#[bits = 1]
pub b2_size: TileSize,
#[bits = 1]
pub b3_size: TileSize,
pub reserved_1: B1,
//
#[bits = 1]
pub s1_size: TileSize,
#[bits = 1]
pub s2_size: TileSize,
#[bits = 1]
pub s3_size: TileSize,
pub reserved_2: B1,
//
#[bits = 1]
pub b1_tilemap_size_x: TilemapSize,
#[bits = 1]
pub b2_tilemap_size_x: TilemapSize,
#[bits = 1]
pub b3_tilemap_size_x: TilemapSize,
pub reserved_3: B1,
//
#[bits = 1]
pub b1_tilemap_size_y: TilemapSize,
#[bits = 1]
pub b2_tilemap_size_y: TilemapSize,
#[bits = 1]
pub b3_tilemap_size_y: TilemapSize,
pub reserved_4: B1,
}

#[bitfield(bytes = 2)]
#[derive(Debug, Default)]
pub struct ScrollRegister {
pub scroll_amount: B10,
pub reserved: B6,
}

#[derive(BitfieldSpecifier, Debug)]
pub enum OutputMode {
Ntsc,
Pal,
}

#[bitfield(bytes = 2)]
#[derive(Debug, Default)]
pub struct StatusRegister {
pub ppu_version: B4,
#[bits = 1]
pub output_mode: OutputMode,
pub reserved: B11,
}

#[derive(Debug, Default)]
pub struct PpuRegisters {
pub base_config: BaseConfigRegister,
pub tile_size: TileSizeRegister,
pub b1_tilemap_addr: u16,
pub b2_tilemap_addr: u16,
pub b3_tilemap_addr: u16,
pub reserved: u16,
pub b1_tile_addr: u16,
pub b2_tile_addr: u16,
pub b3_tile_addr: u16,
pub reserved2: u16,
pub b1_scroll_x: ScrollRegister,
pub b2_scroll_x: ScrollRegister,
pub b3_scroll_x: ScrollRegister,
pub reserved3: u16,
pub b1_scroll_y: ScrollRegister,
pub b2_scroll_y: ScrollRegister,
pub b3_scroll_y: ScrollRegister,
pub reserved4: u16,
pub s_tile_addr: u16,
pub status: StatusRegister,
}

impl MemoryMapped for PpuRegisters {
fn read_address(&self, address: u32) -> u16 {
// The modular_bitfield crate only provides bytes for raw output, hopefully the compiler can
// optimise it so it doesn't matter
match address {
0x0000 => u16::from_be_bytes(self.base_config.bytes),
0x0001 => u16::from_be_bytes(self.tile_size.bytes),
0x0002 => self.b1_tilemap_addr,
0x0003 => self.b2_tilemap_addr,
0x0004 => self.b3_tilemap_addr,
0x0005 => self.reserved,
0x0006 => self.b1_tile_addr,
0x0007 => self.b2_tile_addr,
0x0008 => self.b3_tile_addr,
0x0009 => self.reserved2,
0x000A => u16::from_be_bytes(self.b1_scroll_x.bytes),
0x000B => u16::from_be_bytes(self.b2_scroll_x.bytes),
0x000C => u16::from_be_bytes(self.b3_scroll_x.bytes),
0x000D => self.reserved3,
0x000E => u16::from_be_bytes(self.b1_scroll_y.bytes),
0x000F => u16::from_be_bytes(self.b2_scroll_y.bytes),
0x0010 => u16::from_be_bytes(self.b3_scroll_y.bytes),
0x0011 => self.reserved4,
0x0012 => self.s_tile_addr,
READONLY_STATUS_REGISTER_ADDR => u16::from_be_bytes(self.status.bytes),
_ => 0x0, // Open bus
}
}

fn write_address(&mut self, address: u32, value: u16) {
// The modular_bitfield crate only accepts bytes for raw input, hopefully the compiler can
// optimise it so it doesn't matter
let bytes = u16::to_be_bytes(value);
match address {
0x0000 => self.base_config.bytes = bytes,
0x0001 => self.tile_size.bytes = bytes,
0x0002 => self.b1_tilemap_addr = value,
0x0003 => self.b2_tilemap_addr = value,
0x0004 => self.b3_tilemap_addr = value,
0x0005 => self.reserved = value,
0x0006 => self.b1_tile_addr = value,
0x0007 => self.b2_tile_addr = value,
0x0008 => self.b3_tile_addr = value,
0x0009 => self.reserved2 = value,
0x000A => self.b1_scroll_x.bytes = bytes,
0x000B => self.b2_scroll_x.bytes = bytes,
0x000C => self.b3_scroll_x.bytes = bytes,
0x000D => self.reserved3 = value,
0x000E => self.b1_scroll_y.bytes = bytes,
0x000F => self.b2_scroll_y.bytes = bytes,
0x0010 => self.b3_scroll_y.bytes = bytes,
0x0011 => self.reserved4 = value,
0x0012 => self.s_tile_addr = value,
// 0x0013 Read Only (status)
_ => {} // Open bus
}
}
}

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

#[test]
fn test_address_round_trip() {
let mut ppu_registers = PpuRegisters::default();
for addr in 0..=0xFF {
let sentinel_value = 0xFF - addr as u16;
ppu_registers.write_address(addr, sentinel_value);
let stored_value = ppu_registers.read_address(addr);
match addr {
0x0..=READONLY_STATUS_REGISTER_ADDR_MINUS_ONE => {
assert_eq!(sentinel_value, stored_value)
}
_ => assert_eq!(0x0, stored_value),
}
}
}
}
Loading

0 comments on commit d7292d7

Please sign in to comment.