cpu.rs: Make quirks individually configurable
This commit is contained in:
parent
85956504d7
commit
784845b6f5
@ -1,54 +0,0 @@
|
|||||||
//! Connects a BusConnectible to the Bus
|
|
||||||
|
|
||||||
use super::BusConnectible;
|
|
||||||
use std::{
|
|
||||||
fmt::{Display, Formatter, Result},
|
|
||||||
ops::Range,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// BusDevice performs address translation for BusConnectibles.
|
|
||||||
/// It is an implementation detail of Bus.connect()
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BusDevice {
|
|
||||||
pub name: String,
|
|
||||||
pub range: Range<u16>,
|
|
||||||
device: Box<dyn BusConnectible>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BusDevice {
|
|
||||||
pub fn new(name: &str, range: Range<u16>, device: Box<dyn BusConnectible>) -> Self {
|
|
||||||
BusDevice {
|
|
||||||
name: name.to_string(),
|
|
||||||
range,
|
|
||||||
device,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn translate_address(&self, addr: u16) -> Option<u16> {
|
|
||||||
let addr = addr.wrapping_sub(self.range.start);
|
|
||||||
if addr < self.range.end {
|
|
||||||
Some(addr)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BusConnectible for BusDevice {
|
|
||||||
fn read_at(&self, addr: u16) -> Option<u8> {
|
|
||||||
self.device.read_at(self.translate_address(addr)?)
|
|
||||||
}
|
|
||||||
fn write_to(&mut self, addr: u16, data: u8) {
|
|
||||||
if let Some(addr) = self.translate_address(addr) {
|
|
||||||
self.device.write_to(addr, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn get_mut(&mut self, addr: u16) -> Option<&mut u8> {
|
|
||||||
return self.device.get_mut(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for BusDevice {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
|
||||||
writeln!(f, "{} [{:04x?}]:\n{}", self.name, self.range, self.device)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
//! Iterators for working with Busses
|
|
||||||
|
|
||||||
use super::{Bus, Read};
|
|
||||||
use std::ops::Range;
|
|
||||||
|
|
||||||
pub trait IterMut<'a> {
|
|
||||||
type Item;
|
|
||||||
fn next(&'a mut self) -> Option<&'a mut Self::Item>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IntoIterMut<'a> {
|
|
||||||
type Item;
|
|
||||||
|
|
||||||
type IntoIter;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BusIterator<'a> {
|
|
||||||
range: Range<u16>,
|
|
||||||
addr: u16,
|
|
||||||
bus: &'a Bus,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> BusIterator<'a> {
|
|
||||||
/// Creates a new BusIterator with a specified range
|
|
||||||
pub fn new(range: Range<u16>, bus: &'a Bus) -> BusIterator<'a> {
|
|
||||||
BusIterator {
|
|
||||||
addr: range.start,
|
|
||||||
range,
|
|
||||||
bus,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn range(mut self, range: Range<u16>) -> Self {
|
|
||||||
self.range = range;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for BusIterator<'a> {
|
|
||||||
type Item = u8;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let mut res = None;
|
|
||||||
if self.range.contains(&self.addr) {
|
|
||||||
res = Some(self.bus.read(self.addr));
|
|
||||||
self.addr += 1;
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
266
src/cpu.rs
266
src/cpu.rs
@ -15,12 +15,58 @@ type Reg = usize;
|
|||||||
type Adr = u16;
|
type Adr = u16;
|
||||||
type Nib = u8;
|
type Nib = u8;
|
||||||
|
|
||||||
|
/// Controls the authenticity behavior of the CPU on a granular level.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Quirks {
|
||||||
|
/// Binary ops in `8xy`(`1`, `2`, `3`) should set vF to 0
|
||||||
|
pub bin_ops: bool,
|
||||||
|
/// Shift ops in `8xy`(`6`, `E`) should source from vY instead of vX
|
||||||
|
pub shift: bool,
|
||||||
|
/// Draw operations should pause execution until the next timer tick
|
||||||
|
pub draw_wait: bool,
|
||||||
|
/// DMA instructions `Fx55`/`Fx65` should change I to I + x + 1
|
||||||
|
pub dma_inc: bool,
|
||||||
|
/// Indexed jump instructions should go to ADR + v[N] where N is high nibble of adr
|
||||||
|
pub stupid_jumps: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for Quirks {
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
if value {
|
||||||
|
Quirks {
|
||||||
|
bin_ops: true,
|
||||||
|
shift: true,
|
||||||
|
draw_wait: true,
|
||||||
|
dma_inc: true,
|
||||||
|
stupid_jumps: false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Quirks {
|
||||||
|
bin_ops: false,
|
||||||
|
shift: false,
|
||||||
|
draw_wait: false,
|
||||||
|
dma_inc: false,
|
||||||
|
stupid_jumps: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Quirks {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::from(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ControlFlags {
|
pub struct ControlFlags {
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub pause: bool,
|
pub pause: bool,
|
||||||
pub keypause: bool,
|
pub keypause: bool,
|
||||||
pub authentic: bool,
|
pub vbi_wait: bool,
|
||||||
|
pub lastkey: Option<usize>,
|
||||||
|
pub quirks: Quirks,
|
||||||
|
pub monotonic: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControlFlags {
|
impl ControlFlags {
|
||||||
@ -37,8 +83,10 @@ pub struct Keys {
|
|||||||
keys: [bool; 16],
|
keys: [bool; 16],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the internal state of the CPU interpreter
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct CPU {
|
pub struct CPU {
|
||||||
|
pub flags: ControlFlags,
|
||||||
// memory map info
|
// memory map info
|
||||||
screen: Adr,
|
screen: Adr,
|
||||||
font: Adr,
|
font: Adr,
|
||||||
@ -47,12 +95,12 @@ pub struct CPU {
|
|||||||
sp: Adr,
|
sp: Adr,
|
||||||
i: Adr,
|
i: Adr,
|
||||||
v: [u8; 16],
|
v: [u8; 16],
|
||||||
delay: u8,
|
delay: f64,
|
||||||
sound: u8,
|
sound: f64,
|
||||||
// I/O
|
// I/O
|
||||||
pub keys: [bool; 16],
|
keys: [bool; 16],
|
||||||
pub flags: ControlFlags,
|
|
||||||
// Execution data
|
// Execution data
|
||||||
|
timer: Instant,
|
||||||
cycle: usize,
|
cycle: usize,
|
||||||
breakpoints: Vec<Adr>,
|
breakpoints: Vec<Adr>,
|
||||||
disassembler: Disassemble,
|
disassembler: Disassemble,
|
||||||
@ -60,36 +108,6 @@ pub struct CPU {
|
|||||||
|
|
||||||
// public interface
|
// public interface
|
||||||
impl CPU {
|
impl CPU {
|
||||||
/// Press keys (where `keys` is a bitmap of the keys [F-0])
|
|
||||||
pub fn press(&mut self, key: usize) {
|
|
||||||
if (0..16).contains(&key) {
|
|
||||||
self.keys[key] = true;
|
|
||||||
self.flags.keypause = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Release all keys
|
|
||||||
pub fn release(&mut self) {
|
|
||||||
for key in &mut self.keys {
|
|
||||||
*key = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a general purpose register in the CPU
|
|
||||||
/// # Examples
|
|
||||||
/// ```rust
|
|
||||||
/// # use chirp::prelude::*;
|
|
||||||
/// // Create a new CPU, and set v4 to 0x41
|
|
||||||
/// let mut cpu = CPU::default();
|
|
||||||
/// cpu.set_gpr(0x4, 0x41);
|
|
||||||
/// // Dump the CPU registers
|
|
||||||
/// cpu.dump();
|
|
||||||
/// ```
|
|
||||||
pub fn set_gpr(&mut self, gpr: Reg, value: u8) {
|
|
||||||
if let Some(gpr) = self.v.get_mut(gpr) {
|
|
||||||
*gpr = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constructs a new CPU, taking all configurable parameters
|
/// Constructs a new CPU, taking all configurable parameters
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -111,14 +129,42 @@ impl CPU {
|
|||||||
font,
|
font,
|
||||||
pc,
|
pc,
|
||||||
sp,
|
sp,
|
||||||
i: 0,
|
|
||||||
v: [0; 16],
|
|
||||||
delay: 0,
|
|
||||||
sound: 0,
|
|
||||||
cycle: 0,
|
|
||||||
keys: [false; 16],
|
|
||||||
breakpoints,
|
breakpoints,
|
||||||
flags,
|
flags,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Press a key
|
||||||
|
pub fn press(&mut self, key: usize) {
|
||||||
|
if (0..16).contains(&key) {
|
||||||
|
self.keys[key] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Release a key
|
||||||
|
pub fn release(&mut self, key: usize) {
|
||||||
|
if (0..16).contains(&key) {
|
||||||
|
self.keys[key] = false;
|
||||||
|
if self.flags.keypause {
|
||||||
|
self.flags.lastkey = Some(key);
|
||||||
|
}
|
||||||
|
self.flags.keypause = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a general purpose register in the CPU
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// # use chirp::prelude::*;
|
||||||
|
/// // Create a new CPU, and set v4 to 0x41
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// cpu.set_gpr(0x4, 0x41);
|
||||||
|
/// // Dump the CPU registers
|
||||||
|
/// cpu.dump();
|
||||||
|
/// ```
|
||||||
|
pub fn set_gpr(&mut self, gpr: Reg, value: u8) {
|
||||||
|
if let Some(gpr) = self.v.get_mut(gpr) {
|
||||||
|
*gpr = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,10 +173,15 @@ impl CPU {
|
|||||||
self.pc
|
self.pc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cycle(&self) -> usize {
|
||||||
|
self.cycle
|
||||||
|
}
|
||||||
|
|
||||||
/// Soft resets the CPU, releasing keypause and reinitializing the program counter to 0x200
|
/// Soft resets the CPU, releasing keypause and reinitializing the program counter to 0x200
|
||||||
pub fn soft_reset(&mut self) {
|
pub fn soft_reset(&mut self) {
|
||||||
self.pc = 0x200;
|
self.pc = 0x200;
|
||||||
self.flags.keypause = false;
|
self.flags.keypause = false;
|
||||||
|
self.flags.vbi_wait = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a breakpoint
|
/// Set a breakpoint
|
||||||
@ -162,38 +213,64 @@ impl CPU {
|
|||||||
pub fn singlestep(&mut self, bus: &mut Bus) -> &mut Self {
|
pub fn singlestep(&mut self, bus: &mut Bus) -> &mut Self {
|
||||||
self.flags.pause = false;
|
self.flags.pause = false;
|
||||||
self.tick(bus);
|
self.tick(bus);
|
||||||
|
self.flags.vbi_wait = false;
|
||||||
self.flags.pause = true;
|
self.flags.pause = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unpauses the emulator for `steps` ticks
|
/// Unpauses the emulator for `steps` ticks
|
||||||
/// Ticks the timers every `rate` ticks
|
/// Ticks the timers every `rate` ticks
|
||||||
pub fn multistep(&mut self, bus: &mut Bus, steps: usize, rate: usize) -> &mut Self {
|
pub fn multistep(&mut self, bus: &mut Bus, steps: usize) -> &mut Self {
|
||||||
for _ in 0..steps {
|
for _ in 0..steps {
|
||||||
self.tick(bus);
|
self.tick(bus);
|
||||||
if rate != 0 && self.cycle % rate == rate - 1 {
|
self.vertical_blank();
|
||||||
self.tick_timer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ticks the delay and sound timers
|
/// Signals the start of a vertical blank
|
||||||
pub fn tick_timer(&mut self) -> &mut Self {
|
///
|
||||||
|
/// - Ticks the sound and delay timers
|
||||||
|
/// - Disables framepause
|
||||||
|
pub fn vertical_blank(&mut self) -> &mut Self {
|
||||||
if self.flags.pause {
|
if self.flags.pause {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
self.delay = self.delay.saturating_sub(1);
|
// Use a monotonic counter when testing
|
||||||
self.sound = self.sound.saturating_sub(1);
|
if let Some(speed) = self.flags.monotonic {
|
||||||
|
if self.flags.vbi_wait {
|
||||||
|
self.flags.vbi_wait = !(self.cycle % speed == 0);
|
||||||
|
}
|
||||||
|
self.delay -= 1.0 / speed as f64;
|
||||||
|
self.sound -= 1.0 / speed as f64;
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
|
||||||
|
let time = self.timer.elapsed().as_secs_f64() * 60.0;
|
||||||
|
self.timer = Instant::now();
|
||||||
|
if time > 1.0 {
|
||||||
|
self.flags.vbi_wait = false;
|
||||||
|
}
|
||||||
|
if self.delay > 0.0 {
|
||||||
|
self.delay -= time;
|
||||||
|
}
|
||||||
|
if self.sound > 0.0 {
|
||||||
|
self.sound -= time;
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a single instruction
|
/// Runs a single instruction
|
||||||
pub fn tick(&mut self, bus: &mut Bus) -> &mut Self {
|
pub fn tick(&mut self, bus: &mut Bus) -> &mut Self {
|
||||||
// Do nothing if paused
|
// Do nothing if paused
|
||||||
if self.flags.pause || self.flags.keypause {
|
if self.flags.pause || self.flags.vbi_wait || self.flags.keypause {
|
||||||
|
// always tick in test mode
|
||||||
|
if self.flags.monotonic.is_some() {
|
||||||
|
self.cycle += 1;
|
||||||
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
let time = Instant::now();
|
self.cycle += 1;
|
||||||
// fetch opcode
|
// fetch opcode
|
||||||
let opcode: u16 = bus.read(self.pc);
|
let opcode: u16 = bus.read(self.pc);
|
||||||
let pc = self.pc;
|
let pc = self.pc;
|
||||||
@ -318,9 +395,9 @@ impl CPU {
|
|||||||
0x65 => self.load_dma(x, bus),
|
0x65 => self.load_dma(x, bus),
|
||||||
_ => self.unimplemented(opcode),
|
_ => self.unimplemented(opcode),
|
||||||
},
|
},
|
||||||
_ => unimplemented!("Extracted nibble from byte, got >nibble?"),
|
_ => unreachable!("Extracted nibble from byte, got >nibble?"),
|
||||||
}
|
}
|
||||||
let elapsed = time.elapsed();
|
let elapsed = self.timer.elapsed();
|
||||||
// Print opcode disassembly:
|
// Print opcode disassembly:
|
||||||
if self.flags.debug {
|
if self.flags.debug {
|
||||||
std::println!(
|
std::println!(
|
||||||
@ -331,7 +408,6 @@ impl CPU {
|
|||||||
elapsed.dimmed()
|
elapsed.dimmed()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.cycle += 1;
|
|
||||||
// process breakpoints
|
// process breakpoints
|
||||||
if self.breakpoints.contains(&self.pc) {
|
if self.breakpoints.contains(&self.pc) {
|
||||||
self.flags.pause = true;
|
self.flags.pause = true;
|
||||||
@ -346,8 +422,7 @@ impl CPU {
|
|||||||
self.pc,
|
self.pc,
|
||||||
self.sp,
|
self.sp,
|
||||||
self.i,
|
self.i,
|
||||||
self
|
self.v
|
||||||
.v
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, gpr)| {
|
.map(|(i, gpr)| {
|
||||||
@ -384,14 +459,15 @@ impl Default for CPU {
|
|||||||
sp: 0xefe,
|
sp: 0xefe,
|
||||||
i: 0,
|
i: 0,
|
||||||
v: [0; 16],
|
v: [0; 16],
|
||||||
delay: 0,
|
delay: 0.0,
|
||||||
sound: 0,
|
sound: 0.0,
|
||||||
cycle: 0,
|
cycle: 0,
|
||||||
keys: [false; 16],
|
keys: [false; 16],
|
||||||
flags: ControlFlags {
|
flags: ControlFlags {
|
||||||
debug: true,
|
debug: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
timer: Instant::now(),
|
||||||
breakpoints: vec![],
|
breakpoints: vec![],
|
||||||
disassembler: Disassemble::default(),
|
disassembler: Disassemble::default(),
|
||||||
}
|
}
|
||||||
@ -529,46 +605,40 @@ impl CPU {
|
|||||||
// | 8xyE | X = X << 1 |
|
// | 8xyE | X = X << 1 |
|
||||||
impl CPU {
|
impl CPU {
|
||||||
/// 8xy0: Loads the value of y into x
|
/// 8xy0: Loads the value of y into x
|
||||||
///
|
|
||||||
/// # Authenticity
|
|
||||||
/// The original chip-8 interpreter will clobber vF for any 8-series instruction
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn load(&mut self, x: Reg, y: Reg) {
|
fn load(&mut self, x: Reg, y: Reg) {
|
||||||
self.v[x] = self.v[y];
|
self.v[x] = self.v[y];
|
||||||
if self.flags.authentic {
|
|
||||||
self.v[0xf] = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX
|
/// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX
|
||||||
///
|
///
|
||||||
/// # Authenticity
|
/// # Quirk
|
||||||
/// The original chip-8 interpreter will clobber vF for any 8-series instruction
|
/// The original chip-8 interpreter will clobber vF for any 8-series instruction
|
||||||
#[inline]
|
#[inline]
|
||||||
fn or(&mut self, x: Reg, y: Reg) {
|
fn or(&mut self, x: Reg, y: Reg) {
|
||||||
self.v[x] |= self.v[y];
|
self.v[x] |= self.v[y];
|
||||||
if self.flags.authentic {
|
if self.flags.quirks.bin_ops {
|
||||||
self.v[0xf] = 0;
|
self.v[0xf] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX
|
/// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX
|
||||||
///
|
///
|
||||||
/// # Authenticity
|
/// # Quirk
|
||||||
/// The original chip-8 interpreter will clobber vF for any 8-series instruction
|
/// The original chip-8 interpreter will clobber vF for any 8-series instruction
|
||||||
#[inline]
|
#[inline]
|
||||||
fn and(&mut self, x: Reg, y: Reg) {
|
fn and(&mut self, x: Reg, y: Reg) {
|
||||||
self.v[x] &= self.v[y];
|
self.v[x] &= self.v[y];
|
||||||
if self.flags.authentic {
|
if self.flags.quirks.bin_ops {
|
||||||
self.v[0xf] = 0;
|
self.v[0xf] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX
|
/// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX
|
||||||
///
|
///
|
||||||
/// # Authenticity
|
/// # Quirk
|
||||||
/// The original chip-8 interpreter will clobber vF for any 8-series instruction
|
/// The original chip-8 interpreter will clobber vF for any 8-series instruction
|
||||||
#[inline]
|
#[inline]
|
||||||
fn xor(&mut self, x: Reg, y: Reg) {
|
fn xor(&mut self, x: Reg, y: Reg) {
|
||||||
self.v[x] ^= self.v[y];
|
self.v[x] ^= self.v[y];
|
||||||
if self.flags.authentic {
|
if self.flags.quirks.bin_ops {
|
||||||
self.v[0xf] = 0;
|
self.v[0xf] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -588,12 +658,11 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
/// 8xy6: Performs bitwise right shift of vX
|
/// 8xy6: Performs bitwise right shift of vX
|
||||||
///
|
///
|
||||||
/// # Authenticity
|
/// # Quirk
|
||||||
/// On the original chip-8 interpreter, this would perform the operation on vY
|
/// On the original chip-8 interpreter, this shifts vY and stores the result in vX
|
||||||
/// and store the result in vX. This behavior was left out, for now.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn shift_right(&mut self, x: Reg, y: Reg) {
|
fn shift_right(&mut self, x: Reg, y: Reg) {
|
||||||
let src: Reg = if self.flags.authentic {y} else {x};
|
let src: Reg = if self.flags.quirks.shift { y } else { x };
|
||||||
let shift_out = self.v[src] & 1;
|
let shift_out = self.v[src] & 1;
|
||||||
self.v[x] = self.v[src] >> 1;
|
self.v[x] = self.v[src] >> 1;
|
||||||
self.v[0xf] = shift_out;
|
self.v[0xf] = shift_out;
|
||||||
@ -607,12 +676,12 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
/// 8X_E: Performs bitwise left shift of vX
|
/// 8X_E: Performs bitwise left shift of vX
|
||||||
///
|
///
|
||||||
/// # Authenticity
|
/// # Quirk
|
||||||
/// On the original chip-8 interpreter, this would perform the operation on vY
|
/// On the original chip-8 interpreter, this would perform the operation on vY
|
||||||
/// and store the result in vX. This behavior was left out, for now.
|
/// and store the result in vX. This behavior was left out, for now.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn shift_left(&mut self, x: Reg, y: Reg) {
|
fn shift_left(&mut self, x: Reg, y: Reg) {
|
||||||
let src: Reg = if self.flags.authentic {y} else {x};
|
let src: Reg = if self.flags.quirks.shift { y } else { x };
|
||||||
let shift_out: u8 = self.v[src] >> 7;
|
let shift_out: u8 = self.v[src] >> 7;
|
||||||
self.v[x] = self.v[src] << 1;
|
self.v[x] = self.v[src] << 1;
|
||||||
self.v[0xf] = shift_out;
|
self.v[0xf] = shift_out;
|
||||||
@ -646,9 +715,17 @@ impl CPU {
|
|||||||
// | Baaa | Jump to &adr + v0
|
// | Baaa | Jump to &adr + v0
|
||||||
impl CPU {
|
impl CPU {
|
||||||
/// Badr: Jump to &adr + v0
|
/// Badr: Jump to &adr + v0
|
||||||
|
///
|
||||||
|
/// Quirk:
|
||||||
|
/// On the Super-Chip, this does stupid shit
|
||||||
#[inline]
|
#[inline]
|
||||||
fn jump_indexed(&mut self, a: Adr) {
|
fn jump_indexed(&mut self, a: Adr) {
|
||||||
self.pc = a.wrapping_add(self.v[0] as Adr);
|
let reg = if self.flags.quirks.stupid_jumps {
|
||||||
|
a as usize >> 8
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
self.pc = a.wrapping_add(self.v[reg] as Adr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,9 +741,14 @@ impl CPU {
|
|||||||
// | Dxyn | Draws n-byte sprite to the screen at coordinates (vX, vY)
|
// | Dxyn | Draws n-byte sprite to the screen at coordinates (vX, vY)
|
||||||
impl CPU {
|
impl CPU {
|
||||||
/// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
/// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
||||||
#[inline]
|
///
|
||||||
|
/// # Quirk
|
||||||
|
/// On the original chip-8 interpreter, this will wait for a VBI
|
||||||
fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) {
|
fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) {
|
||||||
let (x, y) = (self.v[x] as u16, self.v[y] as u16);
|
let (x, y) = (self.v[x] as u16 % 64, self.v[y] as u16 % 32);
|
||||||
|
if self.flags.quirks.draw_wait {
|
||||||
|
self.flags.vbi_wait = true;
|
||||||
|
}
|
||||||
self.v[0xf] = 0;
|
self.v[0xf] = 0;
|
||||||
for byte in 0..n as u16 {
|
for byte in 0..n as u16 {
|
||||||
if y + byte > 32 {
|
if y + byte > 32 {
|
||||||
@ -736,19 +818,15 @@ impl CPU {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
fn load_delay_timer(&mut self, x: Reg) {
|
fn load_delay_timer(&mut self, x: Reg) {
|
||||||
self.v[x] = self.delay;
|
self.v[x] = self.delay as u8;
|
||||||
}
|
}
|
||||||
/// Fx0A: Wait for key, then vX = K
|
/// Fx0A: Wait for key, then vX = K
|
||||||
#[inline]
|
#[inline]
|
||||||
fn wait_for_key(&mut self, x: Reg) {
|
fn wait_for_key(&mut self, x: Reg) {
|
||||||
let mut pressed = false;
|
if let Some(key) = self.flags.lastkey {
|
||||||
for bit in 0..16 {
|
self.v[x] = key as u8;
|
||||||
if self.keys[bit] {
|
self.flags.lastkey = None;
|
||||||
self.v[x] = bit as u8;
|
} else {
|
||||||
pressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !pressed {
|
|
||||||
self.pc = self.pc.wrapping_sub(2);
|
self.pc = self.pc.wrapping_sub(2);
|
||||||
self.flags.keypause = true;
|
self.flags.keypause = true;
|
||||||
}
|
}
|
||||||
@ -759,7 +837,7 @@ impl CPU {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
fn store_delay_timer(&mut self, x: Reg) {
|
fn store_delay_timer(&mut self, x: Reg) {
|
||||||
self.delay = self.v[x];
|
self.delay = self.v[x] as f64;
|
||||||
}
|
}
|
||||||
/// Fx18: Load vX into ST
|
/// Fx18: Load vX into ST
|
||||||
/// ```py
|
/// ```py
|
||||||
@ -767,7 +845,7 @@ impl CPU {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
fn store_sound_timer(&mut self, x: Reg) {
|
fn store_sound_timer(&mut self, x: Reg) {
|
||||||
self.sound = self.v[x];
|
self.sound = self.v[x] as f64;
|
||||||
}
|
}
|
||||||
/// Fx1e: Add vX to I,
|
/// Fx1e: Add vX to I,
|
||||||
/// ```py
|
/// ```py
|
||||||
@ -795,7 +873,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
/// Fx55: DMA Stor from I to registers 0..X
|
/// Fx55: DMA Stor from I to registers 0..X
|
||||||
///
|
///
|
||||||
/// # Authenticity
|
/// # Quirk
|
||||||
/// The original chip-8 interpreter uses I to directly index memory,
|
/// The original chip-8 interpreter uses I to directly index memory,
|
||||||
/// with the side effect of leaving I as I+X+1 after the transfer is done.
|
/// with the side effect of leaving I as I+X+1 after the transfer is done.
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -809,13 +887,13 @@ impl CPU {
|
|||||||
{
|
{
|
||||||
*value = self.v[reg]
|
*value = self.v[reg]
|
||||||
}
|
}
|
||||||
if self.flags.authentic {
|
if self.flags.quirks.dma_inc {
|
||||||
self.i += x as Adr + 1;
|
self.i += x as Adr + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Fx65: DMA Load from I to registers 0..X
|
/// Fx65: DMA Load from I to registers 0..X
|
||||||
///
|
///
|
||||||
/// # Authenticity
|
/// # Quirk
|
||||||
/// The original chip-8 interpreter uses I to directly index memory,
|
/// The original chip-8 interpreter uses I to directly index memory,
|
||||||
/// with the side effect of leaving I as I+X+1 after the transfer is done.
|
/// with the side effect of leaving I as I+X+1 after the transfer is done.
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -829,7 +907,7 @@ impl CPU {
|
|||||||
{
|
{
|
||||||
self.v[reg] = *value;
|
self.v[reg] = *value;
|
||||||
}
|
}
|
||||||
if self.flags.authentic {
|
if self.flags.quirks.dma_inc {
|
||||||
self.i += x as Adr + 1;
|
self.i += x as Adr + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
184
src/cpu/tests.rs
184
src/cpu/tests.rs
@ -14,6 +14,7 @@ fn setup_environment() -> (CPU, Bus) {
|
|||||||
flags: ControlFlags {
|
flags: ControlFlags {
|
||||||
debug: true,
|
debug: true,
|
||||||
pause: false,
|
pause: false,
|
||||||
|
monotonic: Some(8),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..CPU::default()
|
..CPU::default()
|
||||||
@ -29,6 +30,10 @@ fn setup_environment() -> (CPU, Bus) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_screen(bytes: &[u8]) {
|
||||||
|
bus! {Screen [0..0x100] = bytes}.print_screen().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
/// Unused instructions
|
/// Unused instructions
|
||||||
///
|
///
|
||||||
/// TODO: Exhaustively test unused instructions
|
/// TODO: Exhaustively test unused instructions
|
||||||
@ -251,7 +256,6 @@ mod math {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 8xy0: Loads the value of y into x
|
/// 8xy0: Loads the value of y into x
|
||||||
// TODO: Test with authentic flag set
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load() {
|
fn load() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
@ -275,10 +279,11 @@ mod math {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX
|
/// 8xy1: Performs bitwise or of vX and vY, and stores the result in vX
|
||||||
// TODO: Test with authentic flag set
|
// TODO: Test with bin_ops quirk flag set
|
||||||
#[test]
|
#[test]
|
||||||
fn or() {
|
fn or_inaccurate() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
|
cpu.flags.quirks.bin_ops = false;
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b) = (word as u8, (word >> 4) as u8);
|
let (a, b) = (word as u8, (word >> 4) as u8);
|
||||||
let expected_result = a | b;
|
let expected_result = a | b;
|
||||||
@ -297,10 +302,11 @@ mod math {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX
|
/// 8xy2: Performs bitwise and of vX and vY, and stores the result in vX
|
||||||
// TODO: Test with authentic flag set
|
// TODO: Test with bin_ops quirk flag set
|
||||||
#[test]
|
#[test]
|
||||||
fn and() {
|
fn and_inaccurate() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
|
cpu.flags.quirks.bin_ops = false;
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b) = (word as u8, (word >> 4) as u8);
|
let (a, b) = (word as u8, (word >> 4) as u8);
|
||||||
let expected_result = a & b;
|
let expected_result = a & b;
|
||||||
@ -319,10 +325,11 @@ mod math {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX
|
/// 8xy3: Performs bitwise xor of vX and vY, and stores the result in vX
|
||||||
// TODO: Test with authentic flag set
|
// TODO: Test with bin_ops quirk flag set
|
||||||
#[test]
|
#[test]
|
||||||
fn xor() {
|
fn xor_inaccurate() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
|
cpu.flags.quirks.bin_ops = false;
|
||||||
for word in 0..=0xffff {
|
for word in 0..=0xffff {
|
||||||
let (a, b) = (word as u8, (word >> 4) as u8);
|
let (a, b) = (word as u8, (word >> 4) as u8);
|
||||||
let expected_result = a ^ b;
|
let expected_result = a ^ b;
|
||||||
@ -521,41 +528,74 @@ mod io {
|
|||||||
|
|
||||||
mod display {
|
mod display {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
#[derive(Debug)]
|
||||||
struct ScreenTest {
|
struct ScreenTest {
|
||||||
program: &'static [u8],
|
program: &'static [u8],
|
||||||
screen: &'static [u8],
|
screen: &'static [u8],
|
||||||
steps: usize,
|
steps: usize,
|
||||||
rate: usize,
|
quirks: Quirks,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCREEN_TESTS: [ScreenTest; 4] = [
|
const SCREEN_TESTS: [ScreenTest; 4] = [
|
||||||
// Passing BC_test
|
// Passing BC_test
|
||||||
|
// # Quirks:
|
||||||
|
// - Requires
|
||||||
ScreenTest {
|
ScreenTest {
|
||||||
program: include_bytes!("../../chip-8/BC_test.ch8"),
|
program: include_bytes!("../../chip-8/BC_test.ch8"),
|
||||||
screen: include_bytes!("tests/BC_test.ch8_197.bin"),
|
screen: include_bytes!("tests/screens/BC_test.ch8/197.bin"),
|
||||||
steps: 197,
|
steps: 250,
|
||||||
rate: 8,
|
quirks: Quirks {
|
||||||
|
bin_ops: true,
|
||||||
|
shift: false,
|
||||||
|
draw_wait: true,
|
||||||
|
|
||||||
|
dma_inc: false,
|
||||||
|
stupid_jumps: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// The IBM Logo
|
// The IBM Logo
|
||||||
ScreenTest {
|
ScreenTest {
|
||||||
program: include_bytes!("../../chip-8/IBM Logo.ch8"),
|
program: include_bytes!("../../chip-8/IBM Logo.ch8"),
|
||||||
screen: include_bytes!("tests/IBM Logo.ch8_20.bin"),
|
screen: include_bytes!("tests/screens/IBM Logo.ch8/20.bin"),
|
||||||
steps: 20,
|
steps: 20,
|
||||||
rate: 8,
|
quirks: Quirks {
|
||||||
|
bin_ops: true,
|
||||||
|
shift: true,
|
||||||
|
draw_wait: false,
|
||||||
|
|
||||||
|
dma_inc: true,
|
||||||
|
stupid_jumps: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Rule 22 cellular automata
|
// Rule 22 cellular automata
|
||||||
|
// # Quirks
|
||||||
|
// - Requires draw_wait false, or it just takes AGES.
|
||||||
ScreenTest {
|
ScreenTest {
|
||||||
program: include_bytes!("../../chip-8/1dcell.ch8"),
|
program: include_bytes!("../../chip-8/1dcell.ch8"),
|
||||||
screen: include_bytes!("tests/1dcell.ch8_123342.bin"),
|
screen: include_bytes!("tests/screens/1dcell.ch8/123342.bin"),
|
||||||
steps: 123342,
|
steps: 123342,
|
||||||
rate: 8,
|
quirks: Quirks {
|
||||||
|
bin_ops: true,
|
||||||
|
shift: true,
|
||||||
|
draw_wait: false,
|
||||||
|
|
||||||
|
dma_inc: true,
|
||||||
|
stupid_jumps: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Rule 60 cellular automata
|
// Rule 60 cellular automata
|
||||||
ScreenTest {
|
ScreenTest {
|
||||||
program: include_bytes!("../../chip-8/1dcell.ch8"),
|
program: include_bytes!("../../chip-8/1dcell.ch8"),
|
||||||
screen: include_bytes!("tests/1dcell.ch8_2391162.bin"),
|
screen: include_bytes!("tests/screens/1dcell.ch8/2391162.bin"),
|
||||||
steps: 2391162,
|
steps: 2391162,
|
||||||
rate: 8,
|
quirks: Quirks {
|
||||||
|
bin_ops: true,
|
||||||
|
shift: true,
|
||||||
|
draw_wait: false,
|
||||||
|
|
||||||
|
dma_inc: true,
|
||||||
|
stupid_jumps: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -564,11 +604,16 @@ mod io {
|
|||||||
fn draw() {
|
fn draw() {
|
||||||
for test in SCREEN_TESTS {
|
for test in SCREEN_TESTS {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
|
cpu.flags.quirks = test.quirks;
|
||||||
// Load the test program
|
// Load the test program
|
||||||
bus = bus.load_region(Program, test.program);
|
bus = bus.load_region(Program, test.program);
|
||||||
// Run the test program for the specified number of steps
|
// Run the test program for the specified number of steps
|
||||||
cpu.multistep(&mut bus, test.steps, test.rate);
|
while cpu.cycle() < test.steps {
|
||||||
|
cpu.multistep(&mut bus, test.steps - cpu.cycle());
|
||||||
|
}
|
||||||
// Compare the screen to the reference screen buffer
|
// Compare the screen to the reference screen buffer
|
||||||
|
bus.print_screen().unwrap();
|
||||||
|
print_screen(test.screen);
|
||||||
assert_eq!(bus.get_region(Screen).unwrap(), test.screen);
|
assert_eq!(bus.get_region(Screen).unwrap(), test.screen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -605,7 +650,7 @@ mod io {
|
|||||||
for word in 0..=0xff {
|
for word in 0..=0xff {
|
||||||
for x in 0..=0xf {
|
for x in 0..=0xf {
|
||||||
// set the register under test to `word`
|
// set the register under test to `word`
|
||||||
cpu.delay = word;
|
cpu.delay = word as f64;
|
||||||
// do the thing
|
// do the thing
|
||||||
cpu.load_delay_timer(x);
|
cpu.load_delay_timer(x);
|
||||||
// validate the result
|
// validate the result
|
||||||
@ -625,7 +670,7 @@ mod io {
|
|||||||
// do the thing
|
// do the thing
|
||||||
cpu.store_delay_timer(x);
|
cpu.store_delay_timer(x);
|
||||||
// validate the result
|
// validate the result
|
||||||
assert_eq!(cpu.delay, word);
|
assert_eq!(cpu.delay, word as f64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -641,7 +686,7 @@ mod io {
|
|||||||
// do the thing
|
// do the thing
|
||||||
cpu.store_sound_timer(x);
|
cpu.store_sound_timer(x);
|
||||||
// validate the result
|
// validate the result
|
||||||
assert_eq!(cpu.sound, word);
|
assert_eq!(cpu.sound, word as f64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -744,20 +789,30 @@ mod io {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fx55: DMA Stor from I to registers 0..X
|
/// Fx55: DMA Stor from I to registers 0..X
|
||||||
// TODO: Test with authentic flag unset
|
// TODO: Test with dma_inc quirk set
|
||||||
// TODO: Test with authentic flag set
|
#[test]
|
||||||
//#[test]
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn dma_store() {
|
fn dma_store() {
|
||||||
todo!()
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
// Load values into registers
|
const DATA: &[u8] = b"ABCDEFGHIJKLMNOP";
|
||||||
// Perform DMA store
|
// Load some test data into memory
|
||||||
// Check that
|
let addr = 0x456;
|
||||||
|
cpu.v.as_mut_slice().write(DATA).unwrap();
|
||||||
|
for len in 0..16 {
|
||||||
|
// Perform DMA store
|
||||||
|
cpu.i = addr as u16;
|
||||||
|
cpu.store_dma(len, &mut bus);
|
||||||
|
// Check that bus grabbed the correct data
|
||||||
|
let mut bus = bus.get_mut(addr..addr + DATA.len()).unwrap();
|
||||||
|
assert_eq!(bus[0..=len], DATA[0..=len]);
|
||||||
|
assert_eq!(bus[len + 1..], [0; 16][len + 1..]);
|
||||||
|
// clear
|
||||||
|
bus.write(&[0; 16]).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fx65: DMA Load from I to registers 0..X
|
/// Fx65: DMA Load from I to registers 0..X
|
||||||
// TODO: Test with authentic flag unset
|
// TODO: Test with dma_inc quirk set
|
||||||
// TODO: Test with authentic flag set
|
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn dma_load() {
|
fn dma_load() {
|
||||||
@ -781,3 +836,72 @@ mod io {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// These are a series of interpreter tests using Timendus's incredible test suite
|
||||||
|
mod chip8_test_suite {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct SuiteTest {
|
||||||
|
program: &'static [u8],
|
||||||
|
screen: &'static [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_screentest(test: SuiteTest, mut cpu: CPU, mut bus: Bus) {
|
||||||
|
// Load the test program
|
||||||
|
bus = bus.load_region(Program, test.program);
|
||||||
|
// The test suite always initiates a keypause on test completion
|
||||||
|
while !cpu.flags.keypause {
|
||||||
|
cpu.multistep(&mut bus, 8);
|
||||||
|
}
|
||||||
|
// Compare the screen to the reference screen buffer
|
||||||
|
bus.print_screen().unwrap();
|
||||||
|
bus! {crate::bus::Region::Screen [0..256] = test.screen}
|
||||||
|
.print_screen()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(bus.get_region(Screen).unwrap(), test.screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn splash_screen() {
|
||||||
|
let (mut c, b) = setup_environment();
|
||||||
|
c.flags.quirks = true.into();
|
||||||
|
run_screentest(
|
||||||
|
SuiteTest {
|
||||||
|
program: include_bytes!("tests/chip8-test-suite/bin/chip8-test-suite.ch8"),
|
||||||
|
screen: include_bytes!("tests/screens/chip8-test-suite.ch8/splash.bin"),
|
||||||
|
},
|
||||||
|
c,
|
||||||
|
b,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flags_test() {
|
||||||
|
let (mut c, mut b) = setup_environment();
|
||||||
|
c.flags.quirks = true.into();
|
||||||
|
b.write(0x1ffu16, 3u8);
|
||||||
|
run_screentest(
|
||||||
|
SuiteTest {
|
||||||
|
program: include_bytes!("tests/chip8-test-suite/bin/chip8-test-suite.ch8"),
|
||||||
|
screen: include_bytes!("tests/screens/chip8-test-suite.ch8/flags.bin"),
|
||||||
|
},
|
||||||
|
c,
|
||||||
|
b,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quirks_test() {
|
||||||
|
let (mut c, mut b) = setup_environment();
|
||||||
|
c.flags.quirks = true.into();
|
||||||
|
b.write(0x1feu16, 0x0104u16);
|
||||||
|
run_screentest(
|
||||||
|
SuiteTest {
|
||||||
|
program: include_bytes!("tests/chip8-test-suite/bin/chip8-test-suite.ch8"),
|
||||||
|
screen: include_bytes!("tests/screens/chip8-test-suite.ch8/quirks.bin"),
|
||||||
|
},
|
||||||
|
c,
|
||||||
|
b,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user