Chirp: Bus Schism: Split into Mem (internal) and Screen (external)
This commit is contained in:
@@ -3,14 +3,15 @@
|
||||
|
||||
//! Contains implementations for each Chip-8 [Insn]
|
||||
|
||||
use super::{bus::Region, *};
|
||||
use super::*;
|
||||
use crate::traits::Grab;
|
||||
use rand::random;
|
||||
|
||||
impl CPU {
|
||||
/// Executes a single [Insn]
|
||||
#[rustfmt::skip]
|
||||
#[inline(always)]
|
||||
pub(super) fn execute(&mut self, screen: &mut Bus, instruction: Insn) {
|
||||
pub(super) fn execute(&mut self, screen: &mut Screen, instruction: Insn) {
|
||||
match instruction {
|
||||
// Core Chip-8 instructions
|
||||
Insn::cls => self.clear_screen(screen),
|
||||
@@ -73,8 +74,8 @@ impl CPU {
|
||||
/// |`00e0`| Clears the screen memory to 0
|
||||
/// Corresponds to [Insn::cls]
|
||||
#[inline(always)]
|
||||
pub(super) fn clear_screen(&mut self, bus: &mut Bus) {
|
||||
bus.clear_region(Region::Screen);
|
||||
pub(super) fn clear_screen(&mut self, screen: &mut Screen) {
|
||||
screen.clear()
|
||||
}
|
||||
/// |`00ee`| Returns from subroutine
|
||||
/// Corresponds to [Insn::ret]
|
||||
@@ -99,7 +100,7 @@ impl CPU {
|
||||
///
|
||||
/// Corresponds to [Insn::scd]
|
||||
#[inline(always)]
|
||||
pub(super) fn scroll_down(&mut self, n: Nib, screen: &mut Bus) {
|
||||
pub(super) fn scroll_down(&mut self, n: Nib, screen: &mut Screen) {
|
||||
match self.flags.draw_mode {
|
||||
true => {
|
||||
// Get a line from the bus
|
||||
@@ -148,18 +149,18 @@ impl CPU {
|
||||
/// Initialize lores mode
|
||||
///
|
||||
/// Corresponds to [Insn::lores]
|
||||
pub(super) fn init_lores(&mut self, screen: &mut Bus) {
|
||||
pub(super) fn init_lores(&mut self, screen: &mut Screen) {
|
||||
self.flags.draw_mode = false;
|
||||
screen.set_region(Region::Screen, 0..256);
|
||||
screen.with_size(256);
|
||||
self.clear_screen(screen);
|
||||
}
|
||||
/// # |`00ff`|
|
||||
/// Initialize hires mode
|
||||
///
|
||||
/// Corresponds to [Insn::hires]
|
||||
pub(super) fn init_hires(&mut self, screen: &mut Bus) {
|
||||
pub(super) fn init_hires(&mut self, screen: &mut Screen) {
|
||||
self.flags.draw_mode = true;
|
||||
screen.set_region(Region::Screen, 0..1024);
|
||||
screen.with_size(1024);
|
||||
self.clear_screen(screen);
|
||||
}
|
||||
}
|
||||
@@ -439,7 +440,7 @@ impl CPU {
|
||||
/// # Quirk
|
||||
/// On the original chip-8 interpreter, this will wait for a VBI
|
||||
#[inline(always)]
|
||||
pub(super) fn draw(&mut self, x: Reg, y: Reg, n: Nib, screen: &mut Bus) {
|
||||
pub(super) fn draw(&mut self, x: Reg, y: Reg, n: Nib, screen: &mut Screen) {
|
||||
if !self.flags.quirks.draw_wait {
|
||||
self.flags.draw_wait = true;
|
||||
}
|
||||
@@ -453,12 +454,20 @@ impl CPU {
|
||||
|
||||
/// |`Dxyn`| Chip-8: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
||||
#[inline(always)]
|
||||
pub(super) fn draw_lores(&mut self, x: Reg, y: Reg, n: Nib, scr: &mut Bus) {
|
||||
pub(super) fn draw_lores(&mut self, x: Reg, y: Reg, n: Nib, scr: &mut Screen) {
|
||||
self.draw_sprite(self.v[x] as u16 % 64, self.v[y] as u16 % 32, n, 64, 32, scr);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(super) fn draw_sprite(&mut self, x: u16, y: u16, n: Nib, w: u16, h: u16, screen: &mut Bus) {
|
||||
pub(super) fn draw_sprite(
|
||||
&mut self,
|
||||
x: u16,
|
||||
y: u16,
|
||||
n: Nib,
|
||||
w: u16,
|
||||
h: u16,
|
||||
screen: &mut Screen,
|
||||
) {
|
||||
let w_bytes = w / 8;
|
||||
self.v[0xf] = 0;
|
||||
if let Some(sprite) = self.mem.grab(self.i as usize..(self.i + n as u16) as usize) {
|
||||
@@ -492,7 +501,7 @@ impl CPU {
|
||||
impl CPU {
|
||||
/// |`Dxyn`| Super-Chip extension high-resolution graphics mode
|
||||
#[inline(always)]
|
||||
pub(super) fn draw_hires(&mut self, x: Reg, y: Reg, n: Nib, screen: &mut Bus) {
|
||||
pub(super) fn draw_hires(&mut self, x: Reg, y: Reg, n: Nib, screen: &mut Screen) {
|
||||
if !self.flags.quirks.draw_wait {
|
||||
self.flags.draw_wait = true;
|
||||
}
|
||||
@@ -508,7 +517,7 @@ impl CPU {
|
||||
}
|
||||
/// Draws a 16x16 Super Chip sprite
|
||||
#[inline(always)]
|
||||
pub(super) fn draw_schip_sprite(&mut self, x: u16, y: u16, w: u16, screen: &mut Bus) {
|
||||
pub(super) fn draw_schip_sprite(&mut self, x: u16, y: u16, w: u16, screen: &mut Screen) {
|
||||
self.v[0xf] = 0;
|
||||
let w_bytes = w / 8;
|
||||
if let Some(sprite) = self.mem.grab(self.i as usize..(self.i + 32) as usize) {
|
||||
|
||||
@@ -1,45 +1,43 @@
|
||||
// (c) 2023 John A. Breaux
|
||||
// This code is licensed under MIT license (see LICENSE for details)
|
||||
|
||||
//! The Bus connects the CPU to Memory
|
||||
//! The Mem represents the CPU's memory
|
||||
//!
|
||||
//! This is more of a memory management unit + some utils for reading/writing
|
||||
//! Contains some handy utils for reading and writing
|
||||
|
||||
use crate::error::{Error::MissingRegion, Result};
|
||||
use crate::{error::Result, traits::Grab};
|
||||
use std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
ops::Range,
|
||||
slice::SliceIndex,
|
||||
};
|
||||
|
||||
/// Creates a new bus, growing the backing memory as needed
|
||||
/// Creates a new [Mem], growing as needed
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use chirp::*;
|
||||
/// let mut bus = bus! {
|
||||
/// let mut mem = mem! {
|
||||
/// Charset [0x0000..0x0800] = b"ABCDEF",
|
||||
/// Program [0x0800..0xf000] = include_bytes!("bus.rs"),
|
||||
/// Program [0x0800..0xf000] = include_bytes!("mem.rs"),
|
||||
/// };
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! bus {
|
||||
macro_rules! mem {
|
||||
($($name:path $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => {
|
||||
$crate::cpu::bus::Bus::default()$(.add_region_owned($name, $range)$(.load_region_owned($name, $data))?)*
|
||||
$crate::cpu::mem::Mem::default()$(.add_region_owned($name, $range)$(.load_region_owned($name, $data))?)*
|
||||
};
|
||||
}
|
||||
|
||||
pub use crate::traits::auto_cast::{AutoCast, Grab};
|
||||
|
||||
// Traits Read and Write are here purely to make implementing other things more bearable
|
||||
impl Grab<u8> for Bus {
|
||||
/// Gets a slice of [Bus] memory
|
||||
impl Grab for Mem {
|
||||
/// Gets a slice of [Mem] memory
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// let mem = Mem::new()
|
||||
/// .add_region_owned(Program, 0..10);
|
||||
/// assert!([0;10].as_slice() == bus.get(0..10).unwrap());
|
||||
/// assert!([0;10].as_slice() == mem.grab(0..10).unwrap());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
@@ -51,14 +49,14 @@ impl Grab<u8> for Bus {
|
||||
self.memory.get(index)
|
||||
}
|
||||
|
||||
/// Gets a mutable slice of [Bus] memory
|
||||
/// Gets a mutable slice of [Mem] memory
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let mut bus = Bus::new()
|
||||
/// let mut mem = Mem::new()
|
||||
/// .add_region_owned(Program, 0..10);
|
||||
/// assert!([0;10].as_slice() == bus.get_mut(0..10).unwrap());
|
||||
/// assert!([0;10].as_slice() == mem.grab_mut(0..10).unwrap());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
@@ -80,8 +78,6 @@ pub enum Region {
|
||||
Charset,
|
||||
/// Program memory
|
||||
Program,
|
||||
/// Screen buffer
|
||||
Screen,
|
||||
#[doc(hidden)]
|
||||
/// Total number of named regions
|
||||
Count,
|
||||
@@ -95,7 +91,6 @@ impl Display for Region {
|
||||
match self {
|
||||
Region::Charset => "Charset",
|
||||
Region::Program => "Program",
|
||||
Region::Screen => "Screen",
|
||||
_ => "",
|
||||
}
|
||||
)
|
||||
@@ -105,35 +100,35 @@ impl Display for Region {
|
||||
/// Stores memory in a series of named regions with ranges
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Bus {
|
||||
pub struct Mem {
|
||||
memory: Vec<u8>,
|
||||
region: [Option<Range<usize>>; Region::Count as usize],
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
// TODO: make bus::new() give a properly set up bus with a default memory map
|
||||
/// Constructs a new bus
|
||||
impl Mem {
|
||||
// TODO: make mem::new() give a properly set up mem with a default memory map
|
||||
/// Constructs a new mem
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new();
|
||||
/// assert!(bus.is_empty());
|
||||
/// let mem = Mem::new();
|
||||
/// assert!(mem.is_empty());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Bus::default()
|
||||
Mem::default()
|
||||
}
|
||||
|
||||
/// Gets the length of the bus' backing memory
|
||||
/// Gets the length of the mem' backing memory
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// let mem = Mem::new()
|
||||
/// .add_region_owned(Program, 0..1234);
|
||||
/// assert_eq!(1234, bus.len());
|
||||
/// assert_eq!(1234, mem.len());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
@@ -146,47 +141,48 @@ impl Bus {
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new();
|
||||
/// assert!(bus.is_empty());
|
||||
/// let mem = Mem::new();
|
||||
/// assert!(mem.is_empty());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.memory.is_empty()
|
||||
}
|
||||
/// Grows the Bus backing memory to at least size bytes, but does not truncate
|
||||
|
||||
/// Grows the Mem backing memory to at least size bytes, but does not truncate
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let mut bus = Bus::new();
|
||||
/// bus.with_size(1234);
|
||||
/// assert_eq!(1234, bus.len());
|
||||
/// bus.with_size(0);
|
||||
/// assert_eq!(1234, bus.len());
|
||||
/// let mut mem = Mem::new();
|
||||
/// mem.with_size(1234);
|
||||
/// assert_eq!(1234, mem.len());
|
||||
/// mem.with_size(0);
|
||||
/// assert_eq!(1234, mem.len());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
pub fn with_size(&mut self, size: usize) {
|
||||
fn with_size(&mut self, size: usize) {
|
||||
if self.len() < size {
|
||||
self.memory.resize(size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a new names range ([Region]) to an owned [Bus]
|
||||
/// Adds a new names range ([Region]) to an owned [Mem]
|
||||
pub fn add_region_owned(mut self, name: Region, range: Range<usize>) -> Self {
|
||||
self.add_region(name, range);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a new named range ([Region]) to a [Bus]
|
||||
/// Adds a new named range ([Region]) to a [Mem]
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let mut bus = Bus::new();
|
||||
/// bus.add_region(Program, 0..1234);
|
||||
/// assert_eq!(1234, bus.len());
|
||||
/// let mut mem = Mem::new();
|
||||
/// mem.add_region(Program, 0..1234);
|
||||
/// assert_eq!(1234, mem.len());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
@@ -203,9 +199,9 @@ impl Bus {
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let mut bus = Bus::new().add_region_owned(Program, 0..1234);
|
||||
/// bus.set_region(Program, 1234..2345);
|
||||
/// assert_eq!(2345, bus.len());
|
||||
/// let mut mem = Mem::new().add_region_owned(Program, 0..1234);
|
||||
/// mem.set_region(Program, 1234..2345);
|
||||
/// assert_eq!(2345, mem.len());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
@@ -217,7 +213,7 @@ impl Bus {
|
||||
self
|
||||
}
|
||||
|
||||
/// Loads data into a [Region] on an *owned* [Bus], for use during initialization
|
||||
/// Loads data into a [Region] on an *owned* [Mem], for use during initialization
|
||||
pub fn load_region_owned(mut self, name: Region, data: &[u8]) -> Self {
|
||||
self.load_region(name, data).ok();
|
||||
self
|
||||
@@ -228,7 +224,7 @@ impl Bus {
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// let mem = Mem::new()
|
||||
/// .add_region_owned(Program, 0..1234)
|
||||
/// .load_region(Program, b"Hello, world!")?;
|
||||
///# // TODO: Test if region actually contains "Hello, world!"
|
||||
@@ -248,7 +244,7 @@ impl Bus {
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// let mem = Mem::new()
|
||||
/// .add_region_owned(Program, 0..1234)
|
||||
/// .clear_region(Program);
|
||||
///# // TODO: test if region actually clear
|
||||
@@ -259,7 +255,7 @@ impl Bus {
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// let mem = Mem::new()
|
||||
/// .add_region_owned(Program, 0..1234)
|
||||
/// .clear_region(Screen);
|
||||
///# // TODO: test if region actually clear
|
||||
@@ -278,9 +274,9 @@ impl Bus {
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// let mem = Mem::new()
|
||||
/// .add_region_owned(Program, 0..10);
|
||||
/// assert!([0;10].as_slice() == bus.get_region(Program).unwrap());
|
||||
/// assert!([0;10].as_slice() == mem.get_region(Program).unwrap());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
@@ -295,9 +291,9 @@ impl Bus {
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let mut bus = Bus::new()
|
||||
/// let mut mem = Mem::new()
|
||||
/// .add_region_owned(Program, 0..10);
|
||||
/// assert!([0;10].as_slice() == bus.get_region_mut(Program).unwrap());
|
||||
/// assert!([0;10].as_slice() == mem.get_region_mut(Program).unwrap());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
@@ -306,79 +302,10 @@ impl Bus {
|
||||
debug_assert!(self.region.get(name as usize).is_some());
|
||||
self.grab_mut(self.region.get(name as usize)?.clone()?)
|
||||
}
|
||||
|
||||
/// Prints the region of memory called `Screen` at 1bpp using box characters
|
||||
/// # Examples
|
||||
///
|
||||
/// [Bus::print_screen] will print the screen
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// .add_region_owned(Screen, 0x000..0x100);
|
||||
/// bus.print_screen()?;
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
/// If there is no Screen region, it will return Err([MissingRegion])
|
||||
/// ```rust,should_panic
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let mut bus = Bus::new()
|
||||
/// .add_region_owned(Program, 0..10);
|
||||
/// bus.print_screen()?;
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
pub fn print_screen(&self) -> Result<()> {
|
||||
const REGION: Region = Region::Screen;
|
||||
if let Some(screen) = self.get_region(REGION) {
|
||||
let len_log2 = screen.len().ilog2() / 2;
|
||||
#[allow(unused_variables)]
|
||||
let (width, height) = (2u32.pow(len_log2 - 1), 2u32.pow(len_log2 + 1) - 1);
|
||||
// draw with the drawille library, if available
|
||||
#[cfg(feature = "drawille")]
|
||||
{
|
||||
use drawille::Canvas;
|
||||
let mut canvas = Canvas::new(width * 8, height);
|
||||
let width = width * 8;
|
||||
screen
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(bytei, byte)| {
|
||||
(0..8).enumerate().filter_map(move |(biti, bit)| {
|
||||
if (byte << bit) & 0x80 != 0 {
|
||||
Some(bytei * 8 + biti)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.for_each(|index| canvas.set(index as u32 % (width), index as u32 / (width)));
|
||||
println!("{}", canvas.frame());
|
||||
}
|
||||
#[cfg(not(feature = "drawille"))]
|
||||
for (index, byte) in screen.iter().enumerate() {
|
||||
if index % width as usize == 0 {
|
||||
print!("{index:03x}|");
|
||||
}
|
||||
print!(
|
||||
"{}",
|
||||
format!("{byte:08b}").replace('0', " ").replace('1', "█")
|
||||
);
|
||||
if index % width as usize == width as usize - 1 {
|
||||
println!("|");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(MissingRegion { region: REGION });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_feature = "rhexdump")]
|
||||
impl Display for Bus {
|
||||
impl Display for Mem {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
use rhexdump::Rhexdump;
|
||||
let mut rhx = Rhexdump::default();
|
||||
@@ -13,15 +13,12 @@
|
||||
//! Some of these tests run >16M times, which is very silly
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
bus,
|
||||
cpu::bus::{Bus, Region::*},
|
||||
};
|
||||
use crate::Screen;
|
||||
use rand::random;
|
||||
|
||||
mod decode;
|
||||
|
||||
fn setup_environment() -> (CPU, Bus) {
|
||||
fn setup_environment() -> (CPU, Screen) {
|
||||
let mut ch8 = (
|
||||
CPU {
|
||||
flags: Flags {
|
||||
@@ -31,10 +28,7 @@ fn setup_environment() -> (CPU, Bus) {
|
||||
},
|
||||
..CPU::default()
|
||||
},
|
||||
bus! {
|
||||
// Create a screen
|
||||
Screen [0x000..0x100] = include_bytes!("../../chip8Archive/roms/1dcell.ch8"),
|
||||
},
|
||||
Screen::default(),
|
||||
);
|
||||
ch8.0
|
||||
.load_program_bytes(include_bytes!("tests/roms/jumptest.ch8"))
|
||||
@@ -43,9 +37,7 @@ fn setup_environment() -> (CPU, Bus) {
|
||||
}
|
||||
|
||||
fn print_screen(bytes: &[u8]) {
|
||||
bus! {Screen [0..0x100] = bytes}
|
||||
.print_screen()
|
||||
.expect("Printing screen should not fail if Screen exists.")
|
||||
Screen::from(bytes).print_screen()
|
||||
}
|
||||
|
||||
/// Unused instructions
|
||||
@@ -57,9 +49,9 @@ mod unimplemented {
|
||||
$pub:vis test $name:ident { $($insn:literal),+$(,)? }
|
||||
);+ $(;)?) => {
|
||||
$( $(#[$attr])* #[test] $pub fn $name () {$(
|
||||
let (mut cpu, mut bus) = setup_environment();
|
||||
let (mut cpu, mut screen) = setup_environment();
|
||||
cpu.mem.write(0x200u16, $insn as u16);
|
||||
cpu.tick(&mut bus)
|
||||
cpu.tick(&mut screen)
|
||||
.expect_err(stringify!($insn is not an instruction));
|
||||
)*} )+
|
||||
};
|
||||
@@ -95,10 +87,11 @@ mod sys {
|
||||
/// 00e0: Clears the screen memory to 0
|
||||
#[test]
|
||||
fn clear_screen() {
|
||||
let (mut cpu, mut bus) = setup_environment();
|
||||
cpu.clear_screen(&mut bus);
|
||||
bus.get_region(Screen)
|
||||
.expect("Expected screen, got None")
|
||||
let (mut cpu, mut screen) = setup_environment();
|
||||
cpu.clear_screen(&mut screen);
|
||||
screen
|
||||
.grab(..)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.for_each(|byte| assert_eq!(*byte, 0));
|
||||
}
|
||||
@@ -751,7 +744,7 @@ mod io {
|
||||
#[test]
|
||||
fn draw() {
|
||||
for test in SCREEN_TESTS {
|
||||
let (mut cpu, mut bus) = setup_environment();
|
||||
let (mut cpu, mut screen) = setup_environment();
|
||||
cpu.flags.quirks = test.quirks;
|
||||
// Debug mode is 5x slower
|
||||
cpu.flags.debug = false;
|
||||
@@ -759,18 +752,13 @@ mod io {
|
||||
cpu.mem.load_region(Program, test.program).unwrap();
|
||||
// Run the test program for the specified number of steps
|
||||
while cpu.cycle() < test.steps {
|
||||
cpu.multistep(&mut bus, 10.min(test.steps - cpu.cycle()))
|
||||
cpu.multistep(&mut screen, 10.min(test.steps - cpu.cycle()))
|
||||
.expect("Draw tests should not contain undefined instructions");
|
||||
}
|
||||
// Compare the screen to the reference screen buffer
|
||||
bus.print_screen()
|
||||
.expect("Printing screen should not fail if screen exists");
|
||||
screen.print_screen();
|
||||
print_screen(test.screen);
|
||||
assert_eq!(
|
||||
bus.get_region(Screen)
|
||||
.expect("Getting screen should not fail if screen exists"),
|
||||
test.screen
|
||||
);
|
||||
assert_eq!(screen.grab(..).unwrap(), test.screen);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1028,15 +1016,15 @@ mod io {
|
||||
// Perform DMA store
|
||||
cpu.i = addr as u16;
|
||||
cpu.store_dma(len);
|
||||
// Check that bus grabbed the correct data
|
||||
let bus = cpu
|
||||
// Check that screen grabbed the correct data
|
||||
let screen = cpu
|
||||
.mem
|
||||
.grab_mut(addr..addr + DATA.len())
|
||||
.expect("Getting a mutable slice at addr 0x0456 should not fail");
|
||||
assert_eq!(bus[0..=len], DATA[0..=len]);
|
||||
assert_eq!(bus[len + 1..], [0; 16][len + 1..]);
|
||||
assert_eq!(screen[0..=len], DATA[0..=len]);
|
||||
assert_eq!(screen[len + 1..], [0; 16][len + 1..]);
|
||||
// clear
|
||||
bus.fill(0);
|
||||
screen.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1074,11 +1062,11 @@ mod behavior {
|
||||
use std::time::Duration;
|
||||
#[test]
|
||||
fn delay() {
|
||||
let (mut cpu, mut bus) = setup_environment();
|
||||
let (mut cpu, mut screen) = setup_environment();
|
||||
cpu.flags.monotonic = false;
|
||||
cpu.delay = 10;
|
||||
for _ in 0..2 {
|
||||
cpu.multistep(&mut bus, 8)
|
||||
cpu.multistep(&mut screen, 8)
|
||||
.expect("Running valid instructions should always succeed");
|
||||
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
|
||||
}
|
||||
@@ -1087,11 +1075,11 @@ mod behavior {
|
||||
}
|
||||
#[test]
|
||||
fn sound() {
|
||||
let (mut cpu, mut bus) = setup_environment();
|
||||
let (mut cpu, mut screen) = setup_environment();
|
||||
cpu.flags.monotonic = false; // disable monotonic timing
|
||||
cpu.sound = 10;
|
||||
for _ in 0..2 {
|
||||
cpu.multistep(&mut bus, 8)
|
||||
cpu.multistep(&mut screen, 8)
|
||||
.expect("Running valid instructions should always succeed");
|
||||
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
|
||||
}
|
||||
@@ -1100,11 +1088,11 @@ mod behavior {
|
||||
}
|
||||
#[test]
|
||||
fn vbi_wait() {
|
||||
let (mut cpu, mut bus) = setup_environment();
|
||||
let (mut cpu, mut screen) = setup_environment();
|
||||
cpu.flags.monotonic = false; // disable monotonic timing
|
||||
cpu.flags.draw_wait = true;
|
||||
for _ in 0..2 {
|
||||
cpu.multistep(&mut bus, 8)
|
||||
cpu.multistep(&mut screen, 8)
|
||||
.expect("Running valid instructions should always succeed");
|
||||
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
|
||||
}
|
||||
@@ -1118,9 +1106,9 @@ mod behavior {
|
||||
#[test]
|
||||
#[cfg_attr(feature = "unstable", no_coverage)]
|
||||
fn hit_break() {
|
||||
let (mut cpu, mut bus) = setup_environment();
|
||||
let (mut cpu, mut screen) = setup_environment();
|
||||
cpu.set_break(0x202);
|
||||
match cpu.multistep(&mut bus, 10) {
|
||||
match cpu.multistep(&mut screen, 10) {
|
||||
Err(crate::error::Error::BreakpointHit { addr, next }) => {
|
||||
assert_eq!(0x202, addr); // current address is 202
|
||||
assert_eq!(0x1204, next); // next insn is `jmp 204`
|
||||
@@ -1133,9 +1121,9 @@ mod behavior {
|
||||
#[test]
|
||||
#[cfg_attr(feature = "unstable", no_coverage)]
|
||||
fn hit_break_singlestep() {
|
||||
let (mut cpu, mut bus) = setup_environment();
|
||||
let (mut cpu, mut screen) = setup_environment();
|
||||
cpu.set_break(0x202);
|
||||
match cpu.singlestep(&mut bus) {
|
||||
match cpu.singlestep(&mut screen) {
|
||||
Err(crate::error::Error::BreakpointHit { addr, next }) => {
|
||||
assert_eq!(0x202, addr); // current address is 202
|
||||
assert_eq!(0x1204, next); // next insn is `jmp 204`
|
||||
@@ -1150,10 +1138,10 @@ mod behavior {
|
||||
#[test]
|
||||
#[cfg_attr(feature = "unstable", no_coverage)]
|
||||
fn invalid_pc() {
|
||||
let (mut cpu, mut bus) = setup_environment();
|
||||
// The bus extends from 0x0..0x1000
|
||||
let (mut cpu, mut screen) = setup_environment();
|
||||
// The screen extends from 0x0..0x1000
|
||||
cpu.pc = 0x1001;
|
||||
match cpu.tick(&mut bus) {
|
||||
match cpu.tick(&mut screen) {
|
||||
Err(Error::InvalidAddressRange { range }) => {
|
||||
eprintln!("InvalidAddressRange {{ {range:04x?} }}")
|
||||
}
|
||||
|
||||
@@ -9,17 +9,15 @@ const INDX: &[u8; 16] = b"\0\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d
|
||||
/// runs one arbitrary operation on a brand new CPU
|
||||
/// returns the CPU for inspection
|
||||
fn run_single_op(op: &[u8]) -> CPU {
|
||||
let (mut cpu, mut bus) = (
|
||||
let (mut cpu, mut screen) = (
|
||||
CPU::default(),
|
||||
bus! {
|
||||
Screen[0x0..0x1000],
|
||||
},
|
||||
Screen::default(),
|
||||
);
|
||||
cpu.mem
|
||||
.load_region(Program, op).unwrap();
|
||||
cpu.v = *INDX;
|
||||
cpu.flags.quirks = Quirks::from(false);
|
||||
cpu.tick(&mut bus).unwrap(); // will panic if unimplemented
|
||||
cpu.tick(&mut screen).unwrap(); // will panic if unimplemented
|
||||
cpu
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user