cpu.rs: Make quirks individually configurable
This commit is contained in:
		@@ -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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										282
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										282
									
								
								src/cpu.rs
									
									
									
									
									
								
							@@ -15,12 +15,58 @@ type Reg = usize;
 | 
			
		||||
type Adr = u16;
 | 
			
		||||
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)]
 | 
			
		||||
pub struct ControlFlags {
 | 
			
		||||
    pub debug: bool,
 | 
			
		||||
    pub pause: bool,
 | 
			
		||||
    pub keypause: bool,
 | 
			
		||||
    pub authentic: bool,
 | 
			
		||||
    pub vbi_wait: bool,
 | 
			
		||||
    pub lastkey: Option<usize>,
 | 
			
		||||
    pub quirks: Quirks,
 | 
			
		||||
    pub monotonic: Option<usize>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ControlFlags {
 | 
			
		||||
@@ -37,8 +83,10 @@ pub struct Keys {
 | 
			
		||||
    keys: [bool; 16],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents the internal state of the CPU interpreter
 | 
			
		||||
#[derive(Clone, Debug, PartialEq)]
 | 
			
		||||
pub struct CPU {
 | 
			
		||||
    pub flags: ControlFlags,
 | 
			
		||||
    // memory map info
 | 
			
		||||
    screen: Adr,
 | 
			
		||||
    font: Adr,
 | 
			
		||||
@@ -47,12 +95,12 @@ pub struct CPU {
 | 
			
		||||
    sp: Adr,
 | 
			
		||||
    i: Adr,
 | 
			
		||||
    v: [u8; 16],
 | 
			
		||||
    delay: u8,
 | 
			
		||||
    sound: u8,
 | 
			
		||||
    delay: f64,
 | 
			
		||||
    sound: f64,
 | 
			
		||||
    // I/O
 | 
			
		||||
    pub keys: [bool; 16],
 | 
			
		||||
    pub flags: ControlFlags,
 | 
			
		||||
    keys: [bool; 16],
 | 
			
		||||
    // Execution data
 | 
			
		||||
    timer: Instant,
 | 
			
		||||
    cycle: usize,
 | 
			
		||||
    breakpoints: Vec<Adr>,
 | 
			
		||||
    disassembler: Disassemble,
 | 
			
		||||
@@ -60,36 +108,6 @@ pub struct CPU {
 | 
			
		||||
 | 
			
		||||
// public interface
 | 
			
		||||
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
 | 
			
		||||
    /// # Examples
 | 
			
		||||
    /// ```rust
 | 
			
		||||
@@ -111,14 +129,42 @@ impl CPU {
 | 
			
		||||
            font,
 | 
			
		||||
            pc,
 | 
			
		||||
            sp,
 | 
			
		||||
            i: 0,
 | 
			
		||||
            v: [0; 16],
 | 
			
		||||
            delay: 0,
 | 
			
		||||
            sound: 0,
 | 
			
		||||
            cycle: 0,
 | 
			
		||||
            keys: [false; 16],
 | 
			
		||||
            breakpoints,
 | 
			
		||||
            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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn cycle(&self) -> usize {
 | 
			
		||||
        self.cycle
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Soft resets the CPU, releasing keypause and reinitializing the program counter to 0x200
 | 
			
		||||
    pub fn soft_reset(&mut self) {
 | 
			
		||||
        self.pc = 0x200;
 | 
			
		||||
        self.flags.keypause = false;
 | 
			
		||||
        self.flags.vbi_wait = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set a breakpoint
 | 
			
		||||
@@ -162,38 +213,64 @@ impl CPU {
 | 
			
		||||
    pub fn singlestep(&mut self, bus: &mut Bus) -> &mut Self {
 | 
			
		||||
        self.flags.pause = false;
 | 
			
		||||
        self.tick(bus);
 | 
			
		||||
        self.flags.vbi_wait = false;
 | 
			
		||||
        self.flags.pause = true;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Unpauses the emulator for `steps` 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 {
 | 
			
		||||
            self.tick(bus);
 | 
			
		||||
            if rate != 0 && self.cycle % rate == rate - 1 {
 | 
			
		||||
                self.tick_timer();
 | 
			
		||||
            }
 | 
			
		||||
            self.vertical_blank();
 | 
			
		||||
        }
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Ticks the delay and sound timers
 | 
			
		||||
    pub fn tick_timer(&mut self) -> &mut Self {
 | 
			
		||||
    /// Signals the start of a vertical blank
 | 
			
		||||
    ///
 | 
			
		||||
    /// - Ticks the sound and delay timers
 | 
			
		||||
    /// - Disables framepause
 | 
			
		||||
    pub fn vertical_blank(&mut self) -> &mut Self {
 | 
			
		||||
        if self.flags.pause {
 | 
			
		||||
            return self;
 | 
			
		||||
        }
 | 
			
		||||
        self.delay = self.delay.saturating_sub(1);
 | 
			
		||||
        self.sound = self.sound.saturating_sub(1);
 | 
			
		||||
        // Use a monotonic counter when testing
 | 
			
		||||
        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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Runs a single instruction
 | 
			
		||||
    pub fn tick(&mut self, bus: &mut Bus) -> &mut Self {
 | 
			
		||||
        // 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;
 | 
			
		||||
        }
 | 
			
		||||
        let time = Instant::now();
 | 
			
		||||
        self.cycle += 1;
 | 
			
		||||
        // fetch opcode
 | 
			
		||||
        let opcode: u16 = bus.read(self.pc);
 | 
			
		||||
        let pc = self.pc;
 | 
			
		||||
@@ -318,9 +395,9 @@ impl CPU {
 | 
			
		||||
                0x65 => self.load_dma(x, bus),
 | 
			
		||||
                _ => 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:
 | 
			
		||||
        if self.flags.debug {
 | 
			
		||||
            std::println!(
 | 
			
		||||
@@ -331,7 +408,6 @@ impl CPU {
 | 
			
		||||
                elapsed.dimmed()
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        self.cycle += 1;
 | 
			
		||||
        // process breakpoints
 | 
			
		||||
        if self.breakpoints.contains(&self.pc) {
 | 
			
		||||
            self.flags.pause = true;
 | 
			
		||||
@@ -346,8 +422,7 @@ impl CPU {
 | 
			
		||||
            self.pc,
 | 
			
		||||
            self.sp,
 | 
			
		||||
            self.i,
 | 
			
		||||
            self
 | 
			
		||||
                .v
 | 
			
		||||
            self.v
 | 
			
		||||
                .into_iter()
 | 
			
		||||
                .enumerate()
 | 
			
		||||
                .map(|(i, gpr)| {
 | 
			
		||||
@@ -384,21 +459,22 @@ impl Default for CPU {
 | 
			
		||||
            sp: 0xefe,
 | 
			
		||||
            i: 0,
 | 
			
		||||
            v: [0; 16],
 | 
			
		||||
            delay: 0,
 | 
			
		||||
            sound: 0,
 | 
			
		||||
            delay: 0.0,
 | 
			
		||||
            sound: 0.0,
 | 
			
		||||
            cycle: 0,
 | 
			
		||||
            keys: [false; 16],
 | 
			
		||||
            flags: ControlFlags {
 | 
			
		||||
                debug: true,
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            },
 | 
			
		||||
            timer: Instant::now(),
 | 
			
		||||
            breakpoints: vec![],
 | 
			
		||||
            disassembler: Disassemble::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Below this point, comments may be duplicated per impl' block, 
 | 
			
		||||
// Below this point, comments may be duplicated per impl' block,
 | 
			
		||||
// since some opcodes handle multiple instructions.
 | 
			
		||||
 | 
			
		||||
// | 0aaa | Issues a "System call" (ML routine)
 | 
			
		||||
@@ -529,46 +605,40 @@ impl CPU {
 | 
			
		||||
// | 8xyE | X = X << 1                         |
 | 
			
		||||
impl CPU {
 | 
			
		||||
    /// 8xy0: Loads the value of y into x
 | 
			
		||||
    /// 
 | 
			
		||||
    /// # Authenticity
 | 
			
		||||
    /// The original chip-8 interpreter will clobber vF for any 8-series instruction
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn load(&mut self, x: Reg, y: Reg) {
 | 
			
		||||
        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
 | 
			
		||||
    /// 
 | 
			
		||||
    /// # Authenticity
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Quirk
 | 
			
		||||
    /// The original chip-8 interpreter will clobber vF for any 8-series instruction
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn or(&mut self, x: Reg, y: Reg) {
 | 
			
		||||
        self.v[x] |= self.v[y];
 | 
			
		||||
        if self.flags.authentic {
 | 
			
		||||
        if self.flags.quirks.bin_ops {
 | 
			
		||||
            self.v[0xf] = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// 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
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn and(&mut self, x: Reg, y: Reg) {
 | 
			
		||||
        self.v[x] &= self.v[y];
 | 
			
		||||
        if self.flags.authentic {
 | 
			
		||||
        if self.flags.quirks.bin_ops {
 | 
			
		||||
            self.v[0xf] = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// 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
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn xor(&mut self, x: Reg, y: Reg) {
 | 
			
		||||
        self.v[x] ^= self.v[y];
 | 
			
		||||
        if self.flags.authentic {
 | 
			
		||||
        if self.flags.quirks.bin_ops {
 | 
			
		||||
            self.v[0xf] = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -587,13 +657,12 @@ impl CPU {
 | 
			
		||||
        self.v[0xf] = (!carry).into();
 | 
			
		||||
    }
 | 
			
		||||
    /// 8xy6: Performs bitwise right shift of vX
 | 
			
		||||
    /// 
 | 
			
		||||
    /// # Authenticity
 | 
			
		||||
    /// 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.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Quirk
 | 
			
		||||
    /// On the original chip-8 interpreter, this shifts vY and stores the result in vX
 | 
			
		||||
    #[inline]
 | 
			
		||||
    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;
 | 
			
		||||
        self.v[x] = self.v[src] >> 1;
 | 
			
		||||
        self.v[0xf] = shift_out;
 | 
			
		||||
@@ -606,13 +675,13 @@ impl CPU {
 | 
			
		||||
        self.v[0xf] = (!carry).into();
 | 
			
		||||
    }
 | 
			
		||||
    /// 8X_E: Performs bitwise left shift of vX
 | 
			
		||||
    /// 
 | 
			
		||||
    /// # Authenticity
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Quirk
 | 
			
		||||
    /// 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.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    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;
 | 
			
		||||
        self.v[x] = self.v[src] << 1;
 | 
			
		||||
        self.v[0xf] = shift_out;
 | 
			
		||||
@@ -646,9 +715,17 @@ impl CPU {
 | 
			
		||||
// | Baaa | Jump to &adr + v0
 | 
			
		||||
impl CPU {
 | 
			
		||||
    /// Badr: Jump to &adr + v0
 | 
			
		||||
    ///
 | 
			
		||||
    /// Quirk:
 | 
			
		||||
    /// On the Super-Chip, this does stupid shit
 | 
			
		||||
    #[inline]
 | 
			
		||||
    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)
 | 
			
		||||
impl CPU {
 | 
			
		||||
    /// 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) {
 | 
			
		||||
        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;
 | 
			
		||||
        for byte in 0..n as u16 {
 | 
			
		||||
            if y + byte > 32 {
 | 
			
		||||
@@ -736,19 +818,15 @@ impl CPU {
 | 
			
		||||
    /// ```
 | 
			
		||||
    #[inline]
 | 
			
		||||
    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
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn wait_for_key(&mut self, x: Reg) {
 | 
			
		||||
        let mut pressed = false;
 | 
			
		||||
        for bit in 0..16 {
 | 
			
		||||
            if self.keys[bit] {
 | 
			
		||||
                self.v[x] = bit as u8;
 | 
			
		||||
                pressed = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if !pressed {
 | 
			
		||||
        if let Some(key) = self.flags.lastkey {
 | 
			
		||||
            self.v[x] = key as u8;
 | 
			
		||||
            self.flags.lastkey = None;
 | 
			
		||||
        } else {
 | 
			
		||||
            self.pc = self.pc.wrapping_sub(2);
 | 
			
		||||
            self.flags.keypause = true;
 | 
			
		||||
        }
 | 
			
		||||
@@ -759,7 +837,7 @@ impl CPU {
 | 
			
		||||
    /// ```
 | 
			
		||||
    #[inline]
 | 
			
		||||
    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
 | 
			
		||||
    /// ```py
 | 
			
		||||
@@ -767,7 +845,7 @@ impl CPU {
 | 
			
		||||
    /// ```
 | 
			
		||||
    #[inline]
 | 
			
		||||
    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,
 | 
			
		||||
    /// ```py
 | 
			
		||||
@@ -794,8 +872,8 @@ impl CPU {
 | 
			
		||||
        bus.write(self.i, x / 100 % 10);
 | 
			
		||||
    }
 | 
			
		||||
    /// Fx55: DMA Stor from I to registers 0..X
 | 
			
		||||
    /// 
 | 
			
		||||
    /// # Authenticity
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Quirk
 | 
			
		||||
    /// 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.
 | 
			
		||||
    #[inline]
 | 
			
		||||
@@ -809,13 +887,13 @@ impl CPU {
 | 
			
		||||
        {
 | 
			
		||||
            *value = self.v[reg]
 | 
			
		||||
        }
 | 
			
		||||
        if self.flags.authentic {
 | 
			
		||||
        if self.flags.quirks.dma_inc {
 | 
			
		||||
            self.i += x as Adr + 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// Fx65: DMA Load from I to registers 0..X
 | 
			
		||||
    /// 
 | 
			
		||||
    /// # Authenticity
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Quirk
 | 
			
		||||
    /// 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.
 | 
			
		||||
    #[inline]
 | 
			
		||||
@@ -829,7 +907,7 @@ impl CPU {
 | 
			
		||||
        {
 | 
			
		||||
            self.v[reg] = *value;
 | 
			
		||||
        }
 | 
			
		||||
        if self.flags.authentic {
 | 
			
		||||
        if self.flags.quirks.dma_inc {
 | 
			
		||||
            self.i += x as Adr + 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										186
									
								
								src/cpu/tests.rs
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								src/cpu/tests.rs
									
									
									
									
									
								
							@@ -14,6 +14,7 @@ fn setup_environment() -> (CPU, Bus) {
 | 
			
		||||
            flags: ControlFlags {
 | 
			
		||||
                debug: true,
 | 
			
		||||
                pause: false,
 | 
			
		||||
                monotonic: Some(8),
 | 
			
		||||
                ..Default::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
 | 
			
		||||
///
 | 
			
		||||
/// TODO: Exhaustively test unused instructions
 | 
			
		||||
@@ -72,7 +77,7 @@ mod sys {
 | 
			
		||||
        let sp_orig = cpu.sp;
 | 
			
		||||
        // Place the address on the stack
 | 
			
		||||
        bus.write(cpu.sp.wrapping_add(2), test_addr);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        cpu.ret(&mut bus);
 | 
			
		||||
 | 
			
		||||
        // Verify the current address is the address from the stack
 | 
			
		||||
@@ -251,7 +256,6 @@ mod math {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 8xy0: Loads the value of y into x
 | 
			
		||||
    // TODO: Test with authentic flag set
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn load() {
 | 
			
		||||
        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
 | 
			
		||||
    // TODO: Test with authentic flag set
 | 
			
		||||
    // TODO: Test with bin_ops quirk flag set
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn or() {
 | 
			
		||||
    fn or_inaccurate() {
 | 
			
		||||
        let (mut cpu, _) = setup_environment();
 | 
			
		||||
        cpu.flags.quirks.bin_ops = false;
 | 
			
		||||
        for word in 0..=0xffff {
 | 
			
		||||
            let (a, b) = (word as u8, (word >> 4) as u8);
 | 
			
		||||
            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
 | 
			
		||||
    // TODO: Test with authentic flag set
 | 
			
		||||
    // TODO: Test with bin_ops quirk flag set
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn and() {
 | 
			
		||||
    fn and_inaccurate() {
 | 
			
		||||
        let (mut cpu, _) = setup_environment();
 | 
			
		||||
        cpu.flags.quirks.bin_ops = false;
 | 
			
		||||
        for word in 0..=0xffff {
 | 
			
		||||
            let (a, b) = (word as u8, (word >> 4) as u8);
 | 
			
		||||
            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
 | 
			
		||||
    // TODO: Test with authentic flag set
 | 
			
		||||
    // TODO: Test with bin_ops quirk flag set
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn xor() {
 | 
			
		||||
    fn xor_inaccurate() {
 | 
			
		||||
        let (mut cpu, _) = setup_environment();
 | 
			
		||||
        cpu.flags.quirks.bin_ops = false;
 | 
			
		||||
        for word in 0..=0xffff {
 | 
			
		||||
            let (a, b) = (word as u8, (word >> 4) as u8);
 | 
			
		||||
            let expected_result = a ^ b;
 | 
			
		||||
@@ -521,41 +528,74 @@ mod io {
 | 
			
		||||
 | 
			
		||||
    mod display {
 | 
			
		||||
        use super::*;
 | 
			
		||||
        #[derive(Debug)]
 | 
			
		||||
        struct ScreenTest {
 | 
			
		||||
            program: &'static [u8],
 | 
			
		||||
            screen: &'static [u8],
 | 
			
		||||
            steps: usize,
 | 
			
		||||
            rate: usize,
 | 
			
		||||
            quirks: Quirks,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const SCREEN_TESTS: [ScreenTest; 4] = [
 | 
			
		||||
            // Passing BC_test
 | 
			
		||||
            // # Quirks:
 | 
			
		||||
            // - Requires
 | 
			
		||||
            ScreenTest {
 | 
			
		||||
                program: include_bytes!("../../chip-8/BC_test.ch8"),
 | 
			
		||||
                screen: include_bytes!("tests/BC_test.ch8_197.bin"),
 | 
			
		||||
                steps: 197,
 | 
			
		||||
                rate: 8,
 | 
			
		||||
                screen: include_bytes!("tests/screens/BC_test.ch8/197.bin"),
 | 
			
		||||
                steps: 250,
 | 
			
		||||
                quirks: Quirks {
 | 
			
		||||
                    bin_ops: true,
 | 
			
		||||
                    shift: false,
 | 
			
		||||
                    draw_wait: true,
 | 
			
		||||
 | 
			
		||||
                    dma_inc: false,
 | 
			
		||||
                    stupid_jumps: false,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            // The IBM Logo
 | 
			
		||||
            ScreenTest {
 | 
			
		||||
                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,
 | 
			
		||||
                rate: 8,
 | 
			
		||||
                quirks: Quirks {
 | 
			
		||||
                    bin_ops: true,
 | 
			
		||||
                    shift: true,
 | 
			
		||||
                    draw_wait: false,
 | 
			
		||||
 | 
			
		||||
                    dma_inc: true,
 | 
			
		||||
                    stupid_jumps: false,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            // Rule 22 cellular automata
 | 
			
		||||
            // # Quirks
 | 
			
		||||
            // - Requires draw_wait false, or it just takes AGES.
 | 
			
		||||
            ScreenTest {
 | 
			
		||||
                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,
 | 
			
		||||
                rate: 8,
 | 
			
		||||
                quirks: Quirks {
 | 
			
		||||
                    bin_ops: true,
 | 
			
		||||
                    shift: true,
 | 
			
		||||
                    draw_wait: false,
 | 
			
		||||
 | 
			
		||||
                    dma_inc: true,
 | 
			
		||||
                    stupid_jumps: false,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            // Rule 60 cellular automata
 | 
			
		||||
            ScreenTest {
 | 
			
		||||
                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,
 | 
			
		||||
                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() {
 | 
			
		||||
            for test in SCREEN_TESTS {
 | 
			
		||||
                let (mut cpu, mut bus) = setup_environment();
 | 
			
		||||
                cpu.flags.quirks = test.quirks;
 | 
			
		||||
                // Load the test program
 | 
			
		||||
                bus = bus.load_region(Program, test.program);
 | 
			
		||||
                // 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
 | 
			
		||||
                bus.print_screen().unwrap();
 | 
			
		||||
                print_screen(test.screen);
 | 
			
		||||
                assert_eq!(bus.get_region(Screen).unwrap(), test.screen);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -605,7 +650,7 @@ mod io {
 | 
			
		||||
        for word in 0..=0xff {
 | 
			
		||||
            for x in 0..=0xf {
 | 
			
		||||
                // set the register under test to `word`
 | 
			
		||||
                cpu.delay = word;
 | 
			
		||||
                cpu.delay = word as f64;
 | 
			
		||||
                // do the thing
 | 
			
		||||
                cpu.load_delay_timer(x);
 | 
			
		||||
                // validate the result
 | 
			
		||||
@@ -625,7 +670,7 @@ mod io {
 | 
			
		||||
                // do the thing
 | 
			
		||||
                cpu.store_delay_timer(x);
 | 
			
		||||
                // validate the result
 | 
			
		||||
                assert_eq!(cpu.delay, word);
 | 
			
		||||
                assert_eq!(cpu.delay, word as f64);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -641,7 +686,7 @@ mod io {
 | 
			
		||||
                // do the thing
 | 
			
		||||
                cpu.store_sound_timer(x);
 | 
			
		||||
                // 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
 | 
			
		||||
    // TODO: Test with authentic flag unset
 | 
			
		||||
    // TODO: Test with authentic flag set
 | 
			
		||||
    //#[test]
 | 
			
		||||
    // TODO: Test with dma_inc quirk set
 | 
			
		||||
    #[test]
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    fn dma_store() {
 | 
			
		||||
        todo!()
 | 
			
		||||
        // Load values into registers
 | 
			
		||||
        // Perform DMA store
 | 
			
		||||
        // Check that
 | 
			
		||||
        let (mut cpu, mut bus) = setup_environment();
 | 
			
		||||
        const DATA: &[u8] = b"ABCDEFGHIJKLMNOP";
 | 
			
		||||
        // Load some test data into memory
 | 
			
		||||
        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
 | 
			
		||||
    // TODO: Test with authentic flag unset
 | 
			
		||||
    // TODO: Test with authentic flag set
 | 
			
		||||
    // TODO: Test with dma_inc quirk set
 | 
			
		||||
    #[test]
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    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,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user