Chirp: Bus Schism: Split into Mem (internal) and Screen (external)
This commit is contained in:
parent
f4d7e514bc
commit
96b6038bbe
@ -1,7 +1,7 @@
|
|||||||
//! The emulator's state, including the screen data
|
//! The emulator's state, including the screen data
|
||||||
|
|
||||||
use super::{error, BACKGROUND, FOREGROUND};
|
use super::{error, BACKGROUND, FOREGROUND};
|
||||||
use chirp::{bus, Bus, Screen, CPU};
|
use chirp::{Grab, Screen, CPU};
|
||||||
use pixels::Pixels;
|
use pixels::Pixels;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use winit::event::VirtualKeyCode;
|
use winit::event::VirtualKeyCode;
|
||||||
@ -10,7 +10,7 @@ use winit_input_helper::WinitInputHelper;
|
|||||||
/// The state of the application
|
/// The state of the application
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Emulator {
|
pub struct Emulator {
|
||||||
screen: Bus,
|
screen: Screen,
|
||||||
cpu: CPU,
|
cpu: CPU,
|
||||||
pub ipf: usize,
|
pub ipf: usize,
|
||||||
pub rom: PathBuf,
|
pub rom: PathBuf,
|
||||||
@ -23,9 +23,7 @@ impl Emulator {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
/// Panics if the provided ROM does not exist
|
/// Panics if the provided ROM does not exist
|
||||||
pub fn new(ipf: usize, rom: impl AsRef<Path>) -> Self {
|
pub fn new(ipf: usize, rom: impl AsRef<Path>) -> Self {
|
||||||
let screen = bus! {
|
let screen = Screen::default();
|
||||||
Screen [0x000..0x100],
|
|
||||||
};
|
|
||||||
let mut cpu = CPU::default();
|
let mut cpu = CPU::default();
|
||||||
cpu.load_program(&rom).expect("Loaded file MUST exist.");
|
cpu.load_program(&rom).expect("Loaded file MUST exist.");
|
||||||
Self {
|
Self {
|
||||||
@ -43,7 +41,7 @@ impl Emulator {
|
|||||||
}
|
}
|
||||||
/// Rasterizes the screen into a [Pixels] buffer
|
/// Rasterizes the screen into a [Pixels] buffer
|
||||||
pub fn draw(&mut self, pixels: &mut Pixels) -> Result<(), error::Error> {
|
pub fn draw(&mut self, pixels: &mut Pixels) -> Result<(), error::Error> {
|
||||||
if let Some(screen) = self.screen.get_region(Screen) {
|
if let Some(screen) = self.screen.grab(..) {
|
||||||
let len_log2 = screen.len().ilog2() / 2;
|
let len_log2 = screen.len().ilog2() / 2;
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let (width, height) = (2u32.pow(len_log2 + 2), 2u32.pow(len_log2 + 1));
|
let (width, height) = (2u32.pow(len_log2 + 2), 2u32.pow(len_log2 + 1));
|
||||||
@ -102,7 +100,7 @@ impl Emulator {
|
|||||||
}
|
}
|
||||||
/// Prints the screen (using the highest resolution available printer) to stdout
|
/// Prints the screen (using the highest resolution available printer) to stdout
|
||||||
pub fn print_screen(&self) -> Result<(), error::Error> {
|
pub fn print_screen(&self) -> Result<(), error::Error> {
|
||||||
self.screen.print_screen()?;
|
self.screen.print_screen();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
/// Dumps the raw screen bytes to a file named `{rom}_{cycle}.bin`,
|
/// Dumps the raw screen bytes to a file named `{rom}_{cycle}.bin`,
|
||||||
@ -114,23 +112,9 @@ impl Emulator {
|
|||||||
self.cpu.cycle()
|
self.cpu.cycle()
|
||||||
));
|
));
|
||||||
path.set_extension("bin");
|
path.set_extension("bin");
|
||||||
if std::fs::write(
|
if std::fs::write(&path, self.screen.as_slice()).is_ok() {
|
||||||
&path,
|
|
||||||
self.screen
|
|
||||||
.get_region(Screen)
|
|
||||||
.expect("Region::Screen should exist"),
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
eprintln!("Saved to {}", &path.display());
|
eprintln!("Saved to {}", &path.display());
|
||||||
} else if std::fs::write(
|
} else if std::fs::write("screen_dump.bin", self.screen.as_slice()).is_ok() {
|
||||||
"screen_dump.bin",
|
|
||||||
self.screen
|
|
||||||
.get_region(Screen)
|
|
||||||
.expect("Region::Screen should exist"),
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
eprintln!("Saved to screen_dump.bin");
|
eprintln!("Saved to screen_dump.bin");
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Failed to dump screen to file.")
|
eprintln!("Failed to dump screen to file.")
|
||||||
@ -171,14 +155,14 @@ impl Emulator {
|
|||||||
/// Soft-resets the CPU, keeping the program in memory
|
/// Soft-resets the CPU, keeping the program in memory
|
||||||
pub fn soft_reset(&mut self) {
|
pub fn soft_reset(&mut self) {
|
||||||
self.cpu.reset();
|
self.cpu.reset();
|
||||||
self.screen.clear_region(Screen);
|
self.screen.clear();
|
||||||
eprintln!("Soft Reset");
|
eprintln!("Soft Reset");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new CPU with the current CPU's flags
|
/// Creates a new CPU with the current CPU's flags
|
||||||
pub fn hard_reset(&mut self) {
|
pub fn hard_reset(&mut self) {
|
||||||
self.cpu.reset();
|
self.cpu.reset();
|
||||||
self.screen.clear_region(Screen);
|
self.screen.clear();
|
||||||
// keep the flags
|
// keep the flags
|
||||||
let flags = self.cpu.flags.clone();
|
let flags = self.cpu.flags.clone();
|
||||||
// instantiate a completely new CPU, and reload the ROM from disk
|
// instantiate a completely new CPU, and reload the ROM from disk
|
||||||
|
@ -8,7 +8,6 @@ mod gui;
|
|||||||
|
|
||||||
use crate::emu::*;
|
use crate::emu::*;
|
||||||
use crate::gui::*;
|
use crate::gui::*;
|
||||||
use core::panic;
|
|
||||||
use pixels::{Pixels, SurfaceTexture};
|
use pixels::{Pixels, SurfaceTexture};
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
use winit::dpi::LogicalSize;
|
use winit::dpi::LogicalSize;
|
||||||
|
@ -12,7 +12,6 @@ use chirp::error::Error::BreakpointHit;
|
|||||||
use chirp::{error::Result, *};
|
use chirp::{error::Result, *};
|
||||||
use gumdrop::*;
|
use gumdrop::*;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use std::fs::read;
|
|
||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
@ -108,6 +107,11 @@ struct Arguments {
|
|||||||
pub frame_rate: u64,
|
pub frame_rate: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Chip8 {
|
||||||
|
pub cpu: CPU,
|
||||||
|
pub screen: Screen,
|
||||||
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct State {
|
struct State {
|
||||||
pub speed: usize,
|
pub speed: usize,
|
||||||
@ -127,16 +131,8 @@ impl State {
|
|||||||
rate: options.frame_rate,
|
rate: options.frame_rate,
|
||||||
perf: options.perf,
|
perf: options.perf,
|
||||||
ch8: Chip8 {
|
ch8: Chip8 {
|
||||||
bus: bus! {
|
|
||||||
// Load the charset into ROM
|
|
||||||
Charset [0x0050..0x00A0] = include_bytes!("../../mem/charset.bin"),
|
|
||||||
// Load the ROM file into RAM
|
|
||||||
Program [0x0200..0x1000] = &read(&options.file)?,
|
|
||||||
// Create a screen
|
|
||||||
Screen [0x1000..0x1100],
|
|
||||||
},
|
|
||||||
cpu: CPU::new(
|
cpu: CPU::new(
|
||||||
&options.file,
|
Some(&options.file),
|
||||||
0x50,
|
0x50,
|
||||||
0x200,
|
0x200,
|
||||||
Dis::default(),
|
Dis::default(),
|
||||||
@ -148,6 +144,7 @@ impl State {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)?,
|
)?,
|
||||||
|
screen: Screen::default(),
|
||||||
},
|
},
|
||||||
ui: UIBuilder::new(128, 64, &options.file).build()?,
|
ui: UIBuilder::new(128, 64, &options.file).build()?,
|
||||||
ft: Instant::now(),
|
ft: Instant::now(),
|
||||||
@ -158,7 +155,7 @@ impl State {
|
|||||||
state.ch8.cpu.flags.quirks.draw_wait ^= options.drawsync;
|
state.ch8.cpu.flags.quirks.draw_wait ^= options.drawsync;
|
||||||
state.ch8.cpu.flags.quirks.shift ^= options.shift;
|
state.ch8.cpu.flags.quirks.shift ^= options.shift;
|
||||||
state.ch8.cpu.flags.quirks.stupid_jumps ^= options.jumping;
|
state.ch8.cpu.flags.quirks.stupid_jumps ^= options.jumping;
|
||||||
state.ch8.bus.write(0x1feu16, options.data);
|
state.ch8.screen.write(0x1feu16, options.data);
|
||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
fn keys(&mut self) -> Result<bool> {
|
fn keys(&mut self) -> Result<bool> {
|
||||||
@ -173,7 +170,7 @@ impl State {
|
|||||||
match self.step {
|
match self.step {
|
||||||
Some(ticks) => {
|
Some(ticks) => {
|
||||||
let time = Instant::now();
|
let time = Instant::now();
|
||||||
self.ch8.cpu.multistep(&mut self.ch8.bus, ticks)?;
|
self.ch8.cpu.multistep(&mut self.ch8.screen, ticks)?;
|
||||||
if self.perf {
|
if self.perf {
|
||||||
let time = time.elapsed();
|
let time = time.elapsed();
|
||||||
let nspt = time.as_secs_f64() / ticks as f64;
|
let nspt = time.as_secs_f64() / ticks as f64;
|
||||||
@ -186,7 +183,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.ch8.cpu.multistep(&mut self.ch8.bus, rate)?;
|
self.ch8.cpu.multistep(&mut self.ch8.screen, rate)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,7 +192,7 @@ impl State {
|
|||||||
fn wait_for_next_frame(&mut self) {
|
fn wait_for_next_frame(&mut self) {
|
||||||
let rate = Duration::from_nanos(1_000_000_000 / self.rate + 1);
|
let rate = Duration::from_nanos(1_000_000_000 / self.rate + 1);
|
||||||
std::thread::sleep(rate.saturating_sub(self.ft.elapsed()));
|
std::thread::sleep(rate.saturating_sub(self.ft.elapsed()));
|
||||||
self.ft = self.ft + rate;
|
self.ft += rate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
//! Tests for chirp-minifb
|
//! Tests for chirp-minifb
|
||||||
|
#![allow(clippy::redundant_clone)]
|
||||||
|
|
||||||
use super::ui::*;
|
use super::ui::*;
|
||||||
|
use super::Chip8;
|
||||||
use chirp::*;
|
use chirp::*;
|
||||||
use std::{collections::hash_map::DefaultHasher, hash::Hash};
|
use std::{collections::hash_map::DefaultHasher, hash::Hash};
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ mod ui {
|
|||||||
fn new_chip8() -> Chip8 {
|
fn new_chip8() -> Chip8 {
|
||||||
Chip8 {
|
Chip8 {
|
||||||
cpu: CPU::default(),
|
cpu: CPU::default(),
|
||||||
bus: bus! {},
|
screen: Screen::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -4,18 +4,14 @@
|
|||||||
//! Platform-specific IO/UI code, and some debug functionality.
|
//! Platform-specific IO/UI code, and some debug functionality.
|
||||||
//! TODO: Destroy this all.
|
//! TODO: Destroy this all.
|
||||||
|
|
||||||
|
use super::Chip8;
|
||||||
|
use chirp::{error::Result, screen::Screen};
|
||||||
|
use minifb::*;
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chirp::{
|
|
||||||
cpu::bus::{Bus, Region},
|
|
||||||
error::Result,
|
|
||||||
Chip8,
|
|
||||||
};
|
|
||||||
use minifb::*;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct UIBuilder {
|
pub struct UIBuilder {
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
@ -105,27 +101,25 @@ impl FrameBuffer {
|
|||||||
format: Default::default(),
|
format: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn render(&mut self, window: &mut Window, bus: &Bus) -> Result<()> {
|
pub fn render(&mut self, window: &mut Window, screen: &Screen) -> Result<()> {
|
||||||
if let Some(screen) = bus.get_region(Region::Screen) {
|
// Resizing the buffer does not unmap memory.
|
||||||
// Resizing the buffer does not unmap memory.
|
// After the first use of high-res mode, this is pretty cheap
|
||||||
// After the first use of high-res mode, this is pretty cheap
|
(self.width, self.height) = match screen.len() {
|
||||||
(self.width, self.height) = match screen.len() {
|
256 => (64, 32),
|
||||||
256 => (64, 32),
|
1024 => (128, 64),
|
||||||
1024 => (128, 64),
|
_ => {
|
||||||
_ => {
|
unimplemented!("Screen must be 64*32 or 128*64");
|
||||||
unimplemented!("Screen must be 64*32 or 128*64");
|
}
|
||||||
}
|
};
|
||||||
};
|
self.buffer.resize(self.width * self.height, 0);
|
||||||
self.buffer.resize(self.width * self.height, 0);
|
for (idx, byte) in screen.as_slice().iter().enumerate() {
|
||||||
for (idx, byte) in screen.iter().enumerate() {
|
for bit in 0..8 {
|
||||||
for bit in 0..8 {
|
self.buffer[8 * idx + bit] = if byte & (1 << (7 - bit)) as u8 != 0 {
|
||||||
self.buffer[8 * idx + bit] = if byte & (1 << (7 - bit)) as u8 != 0 {
|
self.format.fg
|
||||||
self.format.fg
|
} else {
|
||||||
} else {
|
self.format.bg
|
||||||
self.format.bg
|
// .wrapping_add(0x001104 * (idx / self.width) as u32)
|
||||||
// .wrapping_add(0x001104 * (idx / self.width) as u32)
|
// .wrapping_add(0x141000 * (idx & 3) as u32)
|
||||||
// .wrapping_add(0x141000 * (idx & 3) as u32)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,7 +158,7 @@ impl UI {
|
|||||||
}
|
}
|
||||||
self.time = Instant::now();
|
self.time = Instant::now();
|
||||||
// update framebuffer
|
// update framebuffer
|
||||||
self.fb.render(&mut self.window, &ch8.bus)?;
|
self.fb.render(&mut self.window, &ch8.screen)?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +176,6 @@ impl UI {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|key| !self.window.get_keys().contains(key))
|
.filter(|key| !self.window.get_keys().contains(key))
|
||||||
};
|
};
|
||||||
use crate::ui::Region::*;
|
|
||||||
for key in get_keys_released() {
|
for key in get_keys_released() {
|
||||||
if let Some(key) = identify_key(key) {
|
if let Some(key) = identify_key(key) {
|
||||||
ch8.cpu.release(key)?;
|
ch8.cpu.release(key)?;
|
||||||
@ -193,7 +186,7 @@ impl UI {
|
|||||||
use Key::*;
|
use Key::*;
|
||||||
match key {
|
match key {
|
||||||
F1 | Comma => ch8.cpu.dump(),
|
F1 | Comma => ch8.cpu.dump(),
|
||||||
F2 | Period => ch8.bus.print_screen()?,
|
F2 | Period => ch8.screen.print_screen(),
|
||||||
F3 => {
|
F3 => {
|
||||||
debug_dump_screen(ch8, &self.rom).expect("Unable to write debug screen dump");
|
debug_dump_screen(ch8, &self.rom).expect("Unable to write debug screen dump");
|
||||||
}
|
}
|
||||||
@ -217,7 +210,7 @@ impl UI {
|
|||||||
}),
|
}),
|
||||||
F6 | Enter => {
|
F6 | Enter => {
|
||||||
eprintln!("Step");
|
eprintln!("Step");
|
||||||
ch8.cpu.singlestep(&mut ch8.bus)?;
|
ch8.cpu.singlestep(&mut ch8.screen)?;
|
||||||
}
|
}
|
||||||
F7 => {
|
F7 => {
|
||||||
eprintln!("Set breakpoint {:03x}.", ch8.cpu.pc());
|
eprintln!("Set breakpoint {:03x}.", ch8.cpu.pc());
|
||||||
@ -230,7 +223,7 @@ impl UI {
|
|||||||
F9 | Delete => {
|
F9 | Delete => {
|
||||||
eprintln!("Soft reset state.cpu {:03x}", ch8.cpu.pc());
|
eprintln!("Soft reset state.cpu {:03x}", ch8.cpu.pc());
|
||||||
ch8.cpu.soft_reset();
|
ch8.cpu.soft_reset();
|
||||||
ch8.bus.clear_region(Screen);
|
ch8.screen.clear();
|
||||||
}
|
}
|
||||||
Escape => return Ok(false),
|
Escape => return Ok(false),
|
||||||
key => {
|
key => {
|
||||||
@ -274,19 +267,9 @@ pub fn debug_dump_screen(ch8: &Chip8, rom: &Path) -> Result<()> {
|
|||||||
ch8.cpu.cycle()
|
ch8.cpu.cycle()
|
||||||
));
|
));
|
||||||
path.set_extension("bin");
|
path.set_extension("bin");
|
||||||
if let Ok(_) = std::fs::write(
|
if std::fs::write(&path, ch8.screen.as_slice()).is_ok() {
|
||||||
&path,
|
|
||||||
ch8.bus
|
|
||||||
.get_region(Region::Screen)
|
|
||||||
.expect("Region::Screen should exist"),
|
|
||||||
) {
|
|
||||||
eprintln!("Saved to {}", &path.display());
|
eprintln!("Saved to {}", &path.display());
|
||||||
} else if let Ok(_) = std::fs::write(
|
} else if std::fs::write("screen_dump.bin", ch8.screen.as_slice()).is_ok() {
|
||||||
"screen_dump.bin",
|
|
||||||
ch8.bus
|
|
||||||
.get_region(Region::Screen)
|
|
||||||
.expect("Region::Screen should exist"),
|
|
||||||
) {
|
|
||||||
eprintln!("Saved to screen_dump.bin");
|
eprintln!("Saved to screen_dump.bin");
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Failed to dump screen to file.")
|
eprintln!("Failed to dump screen to file.")
|
||||||
|
@ -4,7 +4,7 @@ use std::{env::args, fs::read};
|
|||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
for screen in args().skip(1).inspect(|screen| println!("{screen}")) {
|
for screen in args().skip(1).inspect(|screen| println!("{screen}")) {
|
||||||
let screen = read(screen)?;
|
let screen = read(screen)?;
|
||||||
bus! {Screen [0..screen.len()] = &screen}.print_screen()?;
|
Screen::from(screen).print_screen();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
40
src/cpu.rs
40
src/cpu.rs
@ -7,26 +7,27 @@
|
|||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
mod behavior;
|
mod behavior;
|
||||||
pub mod bus;
|
|
||||||
pub mod flags;
|
pub mod flags;
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod mem;
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
pub mod quirks;
|
pub mod quirks;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
bus::{Bus, Region::*},
|
|
||||||
flags::Flags,
|
flags::Flags,
|
||||||
instruction::{
|
instruction::{
|
||||||
disassembler::{Dis, Disassembler},
|
disassembler::{Dis, Disassembler},
|
||||||
Insn,
|
Insn,
|
||||||
},
|
},
|
||||||
|
mem::{Mem, Region::*},
|
||||||
mode::Mode,
|
mode::Mode,
|
||||||
quirks::Quirks,
|
quirks::Quirks,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
bus,
|
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
traits::auto_cast::{AutoCast, Grab},
|
screen::Screen,
|
||||||
|
traits::{AutoCast, Grab},
|
||||||
};
|
};
|
||||||
use imperative_rs::InstructionSet;
|
use imperative_rs::InstructionSet;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
@ -44,7 +45,7 @@ pub struct CPU {
|
|||||||
/// chip-8. Includes [Quirks], target IPF, etc.
|
/// chip-8. Includes [Quirks], target IPF, etc.
|
||||||
pub flags: Flags,
|
pub flags: Flags,
|
||||||
// memory map info
|
// memory map info
|
||||||
mem: Bus,
|
mem: Mem,
|
||||||
font: Adr,
|
font: Adr,
|
||||||
// memory
|
// memory
|
||||||
stack: Vec<Adr>,
|
stack: Vec<Adr>,
|
||||||
@ -90,7 +91,7 @@ impl CPU {
|
|||||||
flags: Flags,
|
flags: Flags,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
const CHARSET: &[u8] = include_bytes!("mem/charset.bin");
|
const CHARSET: &[u8] = include_bytes!("mem/charset.bin");
|
||||||
let mem = bus! {
|
let mem = mem! {
|
||||||
Charset [font as usize..font as usize + CHARSET.len()] = CHARSET,
|
Charset [font as usize..font as usize + CHARSET.len()] = CHARSET,
|
||||||
Program [pc as usize..0x1000],
|
Program [pc as usize..0x1000],
|
||||||
};
|
};
|
||||||
@ -133,7 +134,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Grabs a reference to the [CPU]'s memory
|
/// Grabs a reference to the [CPU]'s memory
|
||||||
pub fn introspect(&mut self) -> &Bus {
|
pub fn introspect(&mut self) -> &Mem {
|
||||||
&self.mem
|
&self.mem
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,7 +399,7 @@ impl CPU {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use chirp::*;
|
/// # use chirp::*;
|
||||||
/// let mut cpu = CPU::default();
|
/// let mut cpu = CPU::default();
|
||||||
/// let mut screen = bus!{
|
/// let mut screen = mem!{
|
||||||
/// Screen [0x000..0x100],
|
/// Screen [0x000..0x100],
|
||||||
/// };
|
/// };
|
||||||
/// cpu.load_program_bytes(&[0x00, 0xe0, 0x22, 0x02]);
|
/// cpu.load_program_bytes(&[0x00, 0xe0, 0x22, 0x02]);
|
||||||
@ -406,7 +407,7 @@ impl CPU {
|
|||||||
/// assert_eq!(0x202, cpu.pc());
|
/// assert_eq!(0x202, cpu.pc());
|
||||||
/// assert_eq!(1, cpu.cycle());
|
/// assert_eq!(1, cpu.cycle());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn singlestep(&mut self, bus: &mut Bus) -> Result<&mut Self> {
|
pub fn singlestep(&mut self, bus: &mut Screen) -> Result<&mut Self> {
|
||||||
self.flags.pause = false;
|
self.flags.pause = false;
|
||||||
self.tick(bus)?;
|
self.tick(bus)?;
|
||||||
self.flags.draw_wait = false;
|
self.flags.draw_wait = false;
|
||||||
@ -421,7 +422,7 @@ impl CPU {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use chirp::*;
|
/// # use chirp::*;
|
||||||
/// let mut cpu = CPU::default();
|
/// let mut cpu = CPU::default();
|
||||||
/// let mut screen = bus!{
|
/// let mut screen = mem!{
|
||||||
/// Screen [0x000..0x100],
|
/// Screen [0x000..0x100],
|
||||||
/// };
|
/// };
|
||||||
/// cpu.load_program_bytes(&[0x00, 0xe0, 0x22, 0x02]);
|
/// cpu.load_program_bytes(&[0x00, 0xe0, 0x22, 0x02]);
|
||||||
@ -430,7 +431,7 @@ impl CPU {
|
|||||||
/// assert_eq!(0x202, cpu.pc());
|
/// assert_eq!(0x202, cpu.pc());
|
||||||
/// assert_eq!(0x20, cpu.cycle());
|
/// assert_eq!(0x20, cpu.cycle());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn multistep(&mut self, screen: &mut Bus, steps: usize) -> Result<&mut Self> {
|
pub fn multistep(&mut self, screen: &mut Screen, steps: usize) -> Result<&mut Self> {
|
||||||
//let speed = 1.0 / steps as f64;
|
//let speed = 1.0 / steps as f64;
|
||||||
for _ in 0..steps {
|
for _ in 0..steps {
|
||||||
self.tick(screen)?;
|
self.tick(screen)?;
|
||||||
@ -447,22 +448,21 @@ impl CPU {
|
|||||||
/// This result contains information about the breakpoint, but can be safely ignored.
|
/// This result contains information about the breakpoint, but can be safely ignored.
|
||||||
///
|
///
|
||||||
/// Returns [Error::UnimplementedInstruction] if the instruction at `pc` is unimplemented.
|
/// Returns [Error::UnimplementedInstruction] if the instruction at `pc` is unimplemented.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use chirp::*;
|
/// # use chirp::*;
|
||||||
/// let mut cpu = CPU::default();
|
/// let mut cpu = CPU::default();
|
||||||
/// let mut bus = bus!{
|
/// let mut bus = mem!{
|
||||||
/// Program [0x0200..0x0f00] = &[
|
/// Screen [0x000..0x100],
|
||||||
/// 0x00, 0xe0, // cls
|
|
||||||
/// 0x22, 0x02, // jump 0x202 (pc)
|
|
||||||
/// ],
|
|
||||||
/// Screen [0x0f00..0x1000],
|
|
||||||
/// };
|
/// };
|
||||||
|
/// cpu.load_program_bytes(&[0x00, 0xe0, 0x22, 0x02]);
|
||||||
/// cpu.tick(&mut bus)
|
/// cpu.tick(&mut bus)
|
||||||
/// .expect("0x00e0 (cls) should be a valid opcode.");
|
/// .expect("0x00e0 (cls) should be a valid opcode.");
|
||||||
/// assert_eq!(0x202, cpu.pc());
|
/// assert_eq!(0x202, cpu.pc());
|
||||||
/// assert_eq!(1, cpu.cycle());
|
/// assert_eq!(1, cpu.cycle());
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
/// Returns [Error::UnimplementedInstruction] if the instruction is not implemented.
|
/// Returns [Error::UnimplementedInstruction] if the instruction is not implemented.
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use chirp::*;
|
/// # use chirp::*;
|
||||||
@ -470,7 +470,7 @@ impl CPU {
|
|||||||
/// let mut cpu = CPU::default();
|
/// let mut cpu = CPU::default();
|
||||||
/// # cpu.flags.debug = true; // enable live disassembly
|
/// # cpu.flags.debug = true; // enable live disassembly
|
||||||
/// # cpu.flags.monotonic = true; // enable monotonic/test timing
|
/// # cpu.flags.monotonic = true; // enable monotonic/test timing
|
||||||
/// let mut bus = bus!{
|
/// let mut bus = mem!{
|
||||||
/// Screen [0x0f00..0x1000],
|
/// Screen [0x0f00..0x1000],
|
||||||
/// };
|
/// };
|
||||||
/// cpu.load_program_bytes(&[
|
/// cpu.load_program_bytes(&[
|
||||||
@ -480,7 +480,7 @@ impl CPU {
|
|||||||
/// dbg!(cpu.tick(&mut bus))
|
/// dbg!(cpu.tick(&mut bus))
|
||||||
/// .expect_err("Should return Error::InvalidInstruction { 0xffff }");
|
/// .expect_err("Should return Error::InvalidInstruction { 0xffff }");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn tick(&mut self, screen: &mut Bus) -> Result<&mut Self> {
|
pub fn tick(&mut self, screen: &mut Screen) -> Result<&mut Self> {
|
||||||
// Do nothing if paused
|
// Do nothing if paused
|
||||||
if self.flags.is_paused() {
|
if self.flags.is_paused() {
|
||||||
// always tick in test mode
|
// always tick in test mode
|
||||||
@ -608,7 +608,7 @@ impl Default for CPU {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
CPU {
|
CPU {
|
||||||
stack: vec![],
|
stack: vec![],
|
||||||
mem: bus! {
|
mem: mem! {
|
||||||
Charset [0x0050..0x00a0] = include_bytes!("mem/charset.bin"),
|
Charset [0x0050..0x00a0] = include_bytes!("mem/charset.bin"),
|
||||||
Program [0x0200..0x1000],
|
Program [0x0200..0x1000],
|
||||||
},
|
},
|
||||||
|
@ -3,14 +3,15 @@
|
|||||||
|
|
||||||
//! Contains implementations for each Chip-8 [Insn]
|
//! Contains implementations for each Chip-8 [Insn]
|
||||||
|
|
||||||
use super::{bus::Region, *};
|
use super::*;
|
||||||
|
use crate::traits::Grab;
|
||||||
use rand::random;
|
use rand::random;
|
||||||
|
|
||||||
impl CPU {
|
impl CPU {
|
||||||
/// Executes a single [Insn]
|
/// Executes a single [Insn]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[inline(always)]
|
#[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 {
|
match instruction {
|
||||||
// Core Chip-8 instructions
|
// Core Chip-8 instructions
|
||||||
Insn::cls => self.clear_screen(screen),
|
Insn::cls => self.clear_screen(screen),
|
||||||
@ -73,8 +74,8 @@ impl CPU {
|
|||||||
/// |`00e0`| Clears the screen memory to 0
|
/// |`00e0`| Clears the screen memory to 0
|
||||||
/// Corresponds to [Insn::cls]
|
/// Corresponds to [Insn::cls]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(super) fn clear_screen(&mut self, bus: &mut Bus) {
|
pub(super) fn clear_screen(&mut self, screen: &mut Screen) {
|
||||||
bus.clear_region(Region::Screen);
|
screen.clear()
|
||||||
}
|
}
|
||||||
/// |`00ee`| Returns from subroutine
|
/// |`00ee`| Returns from subroutine
|
||||||
/// Corresponds to [Insn::ret]
|
/// Corresponds to [Insn::ret]
|
||||||
@ -99,7 +100,7 @@ impl CPU {
|
|||||||
///
|
///
|
||||||
/// Corresponds to [Insn::scd]
|
/// Corresponds to [Insn::scd]
|
||||||
#[inline(always)]
|
#[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 {
|
match self.flags.draw_mode {
|
||||||
true => {
|
true => {
|
||||||
// Get a line from the bus
|
// Get a line from the bus
|
||||||
@ -148,18 +149,18 @@ impl CPU {
|
|||||||
/// Initialize lores mode
|
/// Initialize lores mode
|
||||||
///
|
///
|
||||||
/// Corresponds to [Insn::lores]
|
/// 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;
|
self.flags.draw_mode = false;
|
||||||
screen.set_region(Region::Screen, 0..256);
|
screen.with_size(256);
|
||||||
self.clear_screen(screen);
|
self.clear_screen(screen);
|
||||||
}
|
}
|
||||||
/// # |`00ff`|
|
/// # |`00ff`|
|
||||||
/// Initialize hires mode
|
/// Initialize hires mode
|
||||||
///
|
///
|
||||||
/// Corresponds to [Insn::hires]
|
/// 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;
|
self.flags.draw_mode = true;
|
||||||
screen.set_region(Region::Screen, 0..1024);
|
screen.with_size(1024);
|
||||||
self.clear_screen(screen);
|
self.clear_screen(screen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -439,7 +440,7 @@ impl CPU {
|
|||||||
/// # Quirk
|
/// # Quirk
|
||||||
/// On the original chip-8 interpreter, this will wait for a VBI
|
/// On the original chip-8 interpreter, this will wait for a VBI
|
||||||
#[inline(always)]
|
#[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 {
|
if !self.flags.quirks.draw_wait {
|
||||||
self.flags.draw_wait = true;
|
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)
|
/// |`Dxyn`| Chip-8: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
||||||
#[inline(always)]
|
#[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);
|
self.draw_sprite(self.v[x] as u16 % 64, self.v[y] as u16 % 32, n, 64, 32, scr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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;
|
let w_bytes = w / 8;
|
||||||
self.v[0xf] = 0;
|
self.v[0xf] = 0;
|
||||||
if let Some(sprite) = self.mem.grab(self.i as usize..(self.i + n as u16) as usize) {
|
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 {
|
impl CPU {
|
||||||
/// |`Dxyn`| Super-Chip extension high-resolution graphics mode
|
/// |`Dxyn`| Super-Chip extension high-resolution graphics mode
|
||||||
#[inline(always)]
|
#[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 {
|
if !self.flags.quirks.draw_wait {
|
||||||
self.flags.draw_wait = true;
|
self.flags.draw_wait = true;
|
||||||
}
|
}
|
||||||
@ -508,7 +517,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
/// Draws a 16x16 Super Chip sprite
|
/// Draws a 16x16 Super Chip sprite
|
||||||
#[inline(always)]
|
#[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;
|
self.v[0xf] = 0;
|
||||||
let w_bytes = w / 8;
|
let w_bytes = w / 8;
|
||||||
if let Some(sprite) = self.mem.grab(self.i as usize..(self.i + 32) as usize) {
|
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
|
// (c) 2023 John A. Breaux
|
||||||
// This code is licensed under MIT license (see LICENSE for details)
|
// 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::{
|
use std::{
|
||||||
fmt::{Debug, Display, Formatter},
|
fmt::{Debug, Display, Formatter},
|
||||||
ops::Range,
|
ops::Range,
|
||||||
slice::SliceIndex,
|
slice::SliceIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Creates a new bus, growing the backing memory as needed
|
/// Creates a new [Mem], growing as needed
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use chirp::*;
|
/// # use chirp::*;
|
||||||
/// let mut bus = bus! {
|
/// let mut mem = mem! {
|
||||||
/// Charset [0x0000..0x0800] = b"ABCDEF",
|
/// Charset [0x0000..0x0800] = b"ABCDEF",
|
||||||
/// Program [0x0800..0xf000] = include_bytes!("bus.rs"),
|
/// Program [0x0800..0xf000] = include_bytes!("mem.rs"),
|
||||||
/// };
|
/// };
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! bus {
|
macro_rules! mem {
|
||||||
($($name:path $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => {
|
($($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
|
// Traits Read and Write are here purely to make implementing other things more bearable
|
||||||
impl Grab<u8> for Bus {
|
impl Grab for Mem {
|
||||||
/// Gets a slice of [Bus] memory
|
/// Gets a slice of [Mem] memory
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let bus = Bus::new()
|
/// let mem = Mem::new()
|
||||||
/// .add_region_owned(Program, 0..10);
|
/// .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(())
|
///# Ok(())
|
||||||
///# }
|
///# }
|
||||||
/// ```
|
/// ```
|
||||||
@ -51,14 +49,14 @@ impl Grab<u8> for Bus {
|
|||||||
self.memory.get(index)
|
self.memory.get(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a mutable slice of [Bus] memory
|
/// Gets a mutable slice of [Mem] memory
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let mut bus = Bus::new()
|
/// let mut mem = Mem::new()
|
||||||
/// .add_region_owned(Program, 0..10);
|
/// .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(())
|
///# Ok(())
|
||||||
///# }
|
///# }
|
||||||
/// ```
|
/// ```
|
||||||
@ -80,8 +78,6 @@ pub enum Region {
|
|||||||
Charset,
|
Charset,
|
||||||
/// Program memory
|
/// Program memory
|
||||||
Program,
|
Program,
|
||||||
/// Screen buffer
|
|
||||||
Screen,
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Total number of named regions
|
/// Total number of named regions
|
||||||
Count,
|
Count,
|
||||||
@ -95,7 +91,6 @@ impl Display for Region {
|
|||||||
match self {
|
match self {
|
||||||
Region::Charset => "Charset",
|
Region::Charset => "Charset",
|
||||||
Region::Program => "Program",
|
Region::Program => "Program",
|
||||||
Region::Screen => "Screen",
|
|
||||||
_ => "",
|
_ => "",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -105,35 +100,35 @@ impl Display for Region {
|
|||||||
/// Stores memory in a series of named regions with ranges
|
/// Stores memory in a series of named regions with ranges
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Bus {
|
pub struct Mem {
|
||||||
memory: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
region: [Option<Range<usize>>; Region::Count as usize],
|
region: [Option<Range<usize>>; Region::Count as usize],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bus {
|
impl Mem {
|
||||||
// TODO: make bus::new() give a properly set up bus with a default memory map
|
// TODO: make mem::new() give a properly set up mem with a default memory map
|
||||||
/// Constructs a new bus
|
/// Constructs a new mem
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let bus = Bus::new();
|
/// let mem = Mem::new();
|
||||||
/// assert!(bus.is_empty());
|
/// assert!(mem.is_empty());
|
||||||
///# Ok(())
|
///# Ok(())
|
||||||
///# }
|
///# }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new() -> Self {
|
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
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let bus = Bus::new()
|
/// let mem = Mem::new()
|
||||||
/// .add_region_owned(Program, 0..1234);
|
/// .add_region_owned(Program, 0..1234);
|
||||||
/// assert_eq!(1234, bus.len());
|
/// assert_eq!(1234, mem.len());
|
||||||
///# Ok(())
|
///# Ok(())
|
||||||
///# }
|
///# }
|
||||||
/// ```
|
/// ```
|
||||||
@ -146,47 +141,48 @@ impl Bus {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let bus = Bus::new();
|
/// let mem = Mem::new();
|
||||||
/// assert!(bus.is_empty());
|
/// assert!(mem.is_empty());
|
||||||
///# Ok(())
|
///# Ok(())
|
||||||
///# }
|
///# }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.memory.is_empty()
|
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
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let mut bus = Bus::new();
|
/// let mut mem = Mem::new();
|
||||||
/// bus.with_size(1234);
|
/// mem.with_size(1234);
|
||||||
/// assert_eq!(1234, bus.len());
|
/// assert_eq!(1234, mem.len());
|
||||||
/// bus.with_size(0);
|
/// mem.with_size(0);
|
||||||
/// assert_eq!(1234, bus.len());
|
/// assert_eq!(1234, mem.len());
|
||||||
///# Ok(())
|
///# Ok(())
|
||||||
///# }
|
///# }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn with_size(&mut self, size: usize) {
|
fn with_size(&mut self, size: usize) {
|
||||||
if self.len() < size {
|
if self.len() < size {
|
||||||
self.memory.resize(size, 0);
|
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 {
|
pub fn add_region_owned(mut self, name: Region, range: Range<usize>) -> Self {
|
||||||
self.add_region(name, range);
|
self.add_region(name, range);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a new named range ([Region]) to a [Bus]
|
/// Adds a new named range ([Region]) to a [Mem]
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let mut bus = Bus::new();
|
/// let mut mem = Mem::new();
|
||||||
/// bus.add_region(Program, 0..1234);
|
/// mem.add_region(Program, 0..1234);
|
||||||
/// assert_eq!(1234, bus.len());
|
/// assert_eq!(1234, mem.len());
|
||||||
///# Ok(())
|
///# Ok(())
|
||||||
///# }
|
///# }
|
||||||
/// ```
|
/// ```
|
||||||
@ -203,9 +199,9 @@ impl Bus {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let mut bus = Bus::new().add_region_owned(Program, 0..1234);
|
/// let mut mem = Mem::new().add_region_owned(Program, 0..1234);
|
||||||
/// bus.set_region(Program, 1234..2345);
|
/// mem.set_region(Program, 1234..2345);
|
||||||
/// assert_eq!(2345, bus.len());
|
/// assert_eq!(2345, mem.len());
|
||||||
///# Ok(())
|
///# Ok(())
|
||||||
///# }
|
///# }
|
||||||
/// ```
|
/// ```
|
||||||
@ -217,7 +213,7 @@ impl Bus {
|
|||||||
self
|
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 {
|
pub fn load_region_owned(mut self, name: Region, data: &[u8]) -> Self {
|
||||||
self.load_region(name, data).ok();
|
self.load_region(name, data).ok();
|
||||||
self
|
self
|
||||||
@ -228,7 +224,7 @@ impl Bus {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let bus = Bus::new()
|
/// let mem = Mem::new()
|
||||||
/// .add_region_owned(Program, 0..1234)
|
/// .add_region_owned(Program, 0..1234)
|
||||||
/// .load_region(Program, b"Hello, world!")?;
|
/// .load_region(Program, b"Hello, world!")?;
|
||||||
///# // TODO: Test if region actually contains "Hello, world!"
|
///# // TODO: Test if region actually contains "Hello, world!"
|
||||||
@ -248,7 +244,7 @@ impl Bus {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let bus = Bus::new()
|
/// let mem = Mem::new()
|
||||||
/// .add_region_owned(Program, 0..1234)
|
/// .add_region_owned(Program, 0..1234)
|
||||||
/// .clear_region(Program);
|
/// .clear_region(Program);
|
||||||
///# // TODO: test if region actually clear
|
///# // TODO: test if region actually clear
|
||||||
@ -259,7 +255,7 @@ impl Bus {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let bus = Bus::new()
|
/// let mem = Mem::new()
|
||||||
/// .add_region_owned(Program, 0..1234)
|
/// .add_region_owned(Program, 0..1234)
|
||||||
/// .clear_region(Screen);
|
/// .clear_region(Screen);
|
||||||
///# // TODO: test if region actually clear
|
///# // TODO: test if region actually clear
|
||||||
@ -278,9 +274,9 @@ impl Bus {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let bus = Bus::new()
|
/// let mem = Mem::new()
|
||||||
/// .add_region_owned(Program, 0..10);
|
/// .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(())
|
///# Ok(())
|
||||||
///# }
|
///# }
|
||||||
/// ```
|
/// ```
|
||||||
@ -295,9 +291,9 @@ impl Bus {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
///# use chirp::*;
|
///# use chirp::*;
|
||||||
///# fn main() -> Result<()> {
|
///# fn main() -> Result<()> {
|
||||||
/// let mut bus = Bus::new()
|
/// let mut mem = Mem::new()
|
||||||
/// .add_region_owned(Program, 0..10);
|
/// .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(())
|
///# Ok(())
|
||||||
///# }
|
///# }
|
||||||
/// ```
|
/// ```
|
||||||
@ -306,79 +302,10 @@ impl Bus {
|
|||||||
debug_assert!(self.region.get(name as usize).is_some());
|
debug_assert!(self.region.get(name as usize).is_some());
|
||||||
self.grab_mut(self.region.get(name as usize)?.clone()?)
|
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")]
|
#[cfg(target_feature = "rhexdump")]
|
||||||
impl Display for Bus {
|
impl Display for Mem {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
use rhexdump::Rhexdump;
|
use rhexdump::Rhexdump;
|
||||||
let mut rhx = Rhexdump::default();
|
let mut rhx = Rhexdump::default();
|
@ -13,15 +13,12 @@
|
|||||||
//! Some of these tests run >16M times, which is very silly
|
//! Some of these tests run >16M times, which is very silly
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::Screen;
|
||||||
bus,
|
|
||||||
cpu::bus::{Bus, Region::*},
|
|
||||||
};
|
|
||||||
use rand::random;
|
use rand::random;
|
||||||
|
|
||||||
mod decode;
|
mod decode;
|
||||||
|
|
||||||
fn setup_environment() -> (CPU, Bus) {
|
fn setup_environment() -> (CPU, Screen) {
|
||||||
let mut ch8 = (
|
let mut ch8 = (
|
||||||
CPU {
|
CPU {
|
||||||
flags: Flags {
|
flags: Flags {
|
||||||
@ -31,10 +28,7 @@ fn setup_environment() -> (CPU, Bus) {
|
|||||||
},
|
},
|
||||||
..CPU::default()
|
..CPU::default()
|
||||||
},
|
},
|
||||||
bus! {
|
Screen::default(),
|
||||||
// Create a screen
|
|
||||||
Screen [0x000..0x100] = include_bytes!("../../chip8Archive/roms/1dcell.ch8"),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
ch8.0
|
ch8.0
|
||||||
.load_program_bytes(include_bytes!("tests/roms/jumptest.ch8"))
|
.load_program_bytes(include_bytes!("tests/roms/jumptest.ch8"))
|
||||||
@ -43,9 +37,7 @@ fn setup_environment() -> (CPU, Bus) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn print_screen(bytes: &[u8]) {
|
fn print_screen(bytes: &[u8]) {
|
||||||
bus! {Screen [0..0x100] = bytes}
|
Screen::from(bytes).print_screen()
|
||||||
.print_screen()
|
|
||||||
.expect("Printing screen should not fail if Screen exists.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unused instructions
|
/// Unused instructions
|
||||||
@ -57,9 +49,9 @@ mod unimplemented {
|
|||||||
$pub:vis test $name:ident { $($insn:literal),+$(,)? }
|
$pub:vis test $name:ident { $($insn:literal),+$(,)? }
|
||||||
);+ $(;)?) => {
|
);+ $(;)?) => {
|
||||||
$( $(#[$attr])* #[test] $pub fn $name () {$(
|
$( $(#[$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.mem.write(0x200u16, $insn as u16);
|
||||||
cpu.tick(&mut bus)
|
cpu.tick(&mut screen)
|
||||||
.expect_err(stringify!($insn is not an instruction));
|
.expect_err(stringify!($insn is not an instruction));
|
||||||
)*} )+
|
)*} )+
|
||||||
};
|
};
|
||||||
@ -95,10 +87,11 @@ mod sys {
|
|||||||
/// 00e0: Clears the screen memory to 0
|
/// 00e0: Clears the screen memory to 0
|
||||||
#[test]
|
#[test]
|
||||||
fn clear_screen() {
|
fn clear_screen() {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut screen) = setup_environment();
|
||||||
cpu.clear_screen(&mut bus);
|
cpu.clear_screen(&mut screen);
|
||||||
bus.get_region(Screen)
|
screen
|
||||||
.expect("Expected screen, got None")
|
.grab(..)
|
||||||
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|byte| assert_eq!(*byte, 0));
|
.for_each(|byte| assert_eq!(*byte, 0));
|
||||||
}
|
}
|
||||||
@ -751,7 +744,7 @@ mod io {
|
|||||||
#[test]
|
#[test]
|
||||||
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 screen) = setup_environment();
|
||||||
cpu.flags.quirks = test.quirks;
|
cpu.flags.quirks = test.quirks;
|
||||||
// Debug mode is 5x slower
|
// Debug mode is 5x slower
|
||||||
cpu.flags.debug = false;
|
cpu.flags.debug = false;
|
||||||
@ -759,18 +752,13 @@ mod io {
|
|||||||
cpu.mem.load_region(Program, test.program).unwrap();
|
cpu.mem.load_region(Program, test.program).unwrap();
|
||||||
// Run the test program for the specified number of steps
|
// Run the test program for the specified number of steps
|
||||||
while cpu.cycle() < test.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");
|
.expect("Draw tests should not contain undefined instructions");
|
||||||
}
|
}
|
||||||
// Compare the screen to the reference screen buffer
|
// Compare the screen to the reference screen buffer
|
||||||
bus.print_screen()
|
screen.print_screen();
|
||||||
.expect("Printing screen should not fail if screen exists");
|
|
||||||
print_screen(test.screen);
|
print_screen(test.screen);
|
||||||
assert_eq!(
|
assert_eq!(screen.grab(..).unwrap(), test.screen);
|
||||||
bus.get_region(Screen)
|
|
||||||
.expect("Getting screen should not fail if screen exists"),
|
|
||||||
test.screen
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1028,15 +1016,15 @@ mod io {
|
|||||||
// Perform DMA store
|
// Perform DMA store
|
||||||
cpu.i = addr as u16;
|
cpu.i = addr as u16;
|
||||||
cpu.store_dma(len);
|
cpu.store_dma(len);
|
||||||
// Check that bus grabbed the correct data
|
// Check that screen grabbed the correct data
|
||||||
let bus = cpu
|
let screen = cpu
|
||||||
.mem
|
.mem
|
||||||
.grab_mut(addr..addr + DATA.len())
|
.grab_mut(addr..addr + DATA.len())
|
||||||
.expect("Getting a mutable slice at addr 0x0456 should not fail");
|
.expect("Getting a mutable slice at addr 0x0456 should not fail");
|
||||||
assert_eq!(bus[0..=len], DATA[0..=len]);
|
assert_eq!(screen[0..=len], DATA[0..=len]);
|
||||||
assert_eq!(bus[len + 1..], [0; 16][len + 1..]);
|
assert_eq!(screen[len + 1..], [0; 16][len + 1..]);
|
||||||
// clear
|
// clear
|
||||||
bus.fill(0);
|
screen.fill(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1074,11 +1062,11 @@ mod behavior {
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
#[test]
|
#[test]
|
||||||
fn delay() {
|
fn delay() {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut screen) = setup_environment();
|
||||||
cpu.flags.monotonic = false;
|
cpu.flags.monotonic = false;
|
||||||
cpu.delay = 10;
|
cpu.delay = 10;
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
cpu.multistep(&mut bus, 8)
|
cpu.multistep(&mut screen, 8)
|
||||||
.expect("Running valid instructions should always succeed");
|
.expect("Running valid instructions should always succeed");
|
||||||
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
|
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
|
||||||
}
|
}
|
||||||
@ -1087,11 +1075,11 @@ mod behavior {
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn sound() {
|
fn sound() {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut screen) = setup_environment();
|
||||||
cpu.flags.monotonic = false; // disable monotonic timing
|
cpu.flags.monotonic = false; // disable monotonic timing
|
||||||
cpu.sound = 10;
|
cpu.sound = 10;
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
cpu.multistep(&mut bus, 8)
|
cpu.multistep(&mut screen, 8)
|
||||||
.expect("Running valid instructions should always succeed");
|
.expect("Running valid instructions should always succeed");
|
||||||
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
|
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
|
||||||
}
|
}
|
||||||
@ -1100,11 +1088,11 @@ mod behavior {
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn vbi_wait() {
|
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.monotonic = false; // disable monotonic timing
|
||||||
cpu.flags.draw_wait = true;
|
cpu.flags.draw_wait = true;
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
cpu.multistep(&mut bus, 8)
|
cpu.multistep(&mut screen, 8)
|
||||||
.expect("Running valid instructions should always succeed");
|
.expect("Running valid instructions should always succeed");
|
||||||
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
|
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
|
||||||
}
|
}
|
||||||
@ -1118,9 +1106,9 @@ mod behavior {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(feature = "unstable", no_coverage)]
|
#[cfg_attr(feature = "unstable", no_coverage)]
|
||||||
fn hit_break() {
|
fn hit_break() {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut screen) = setup_environment();
|
||||||
cpu.set_break(0x202);
|
cpu.set_break(0x202);
|
||||||
match cpu.multistep(&mut bus, 10) {
|
match cpu.multistep(&mut screen, 10) {
|
||||||
Err(crate::error::Error::BreakpointHit { addr, next }) => {
|
Err(crate::error::Error::BreakpointHit { addr, next }) => {
|
||||||
assert_eq!(0x202, addr); // current address is 202
|
assert_eq!(0x202, addr); // current address is 202
|
||||||
assert_eq!(0x1204, next); // next insn is `jmp 204`
|
assert_eq!(0x1204, next); // next insn is `jmp 204`
|
||||||
@ -1133,9 +1121,9 @@ mod behavior {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(feature = "unstable", no_coverage)]
|
#[cfg_attr(feature = "unstable", no_coverage)]
|
||||||
fn hit_break_singlestep() {
|
fn hit_break_singlestep() {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut screen) = setup_environment();
|
||||||
cpu.set_break(0x202);
|
cpu.set_break(0x202);
|
||||||
match cpu.singlestep(&mut bus) {
|
match cpu.singlestep(&mut screen) {
|
||||||
Err(crate::error::Error::BreakpointHit { addr, next }) => {
|
Err(crate::error::Error::BreakpointHit { addr, next }) => {
|
||||||
assert_eq!(0x202, addr); // current address is 202
|
assert_eq!(0x202, addr); // current address is 202
|
||||||
assert_eq!(0x1204, next); // next insn is `jmp 204`
|
assert_eq!(0x1204, next); // next insn is `jmp 204`
|
||||||
@ -1150,10 +1138,10 @@ mod behavior {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(feature = "unstable", no_coverage)]
|
#[cfg_attr(feature = "unstable", no_coverage)]
|
||||||
fn invalid_pc() {
|
fn invalid_pc() {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut screen) = setup_environment();
|
||||||
// The bus extends from 0x0..0x1000
|
// The screen extends from 0x0..0x1000
|
||||||
cpu.pc = 0x1001;
|
cpu.pc = 0x1001;
|
||||||
match cpu.tick(&mut bus) {
|
match cpu.tick(&mut screen) {
|
||||||
Err(Error::InvalidAddressRange { range }) => {
|
Err(Error::InvalidAddressRange { range }) => {
|
||||||
eprintln!("InvalidAddressRange {{ {range:04x?} }}")
|
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
|
/// runs one arbitrary operation on a brand new CPU
|
||||||
/// returns the CPU for inspection
|
/// returns the CPU for inspection
|
||||||
fn run_single_op(op: &[u8]) -> CPU {
|
fn run_single_op(op: &[u8]) -> CPU {
|
||||||
let (mut cpu, mut bus) = (
|
let (mut cpu, mut screen) = (
|
||||||
CPU::default(),
|
CPU::default(),
|
||||||
bus! {
|
Screen::default(),
|
||||||
Screen[0x0..0x1000],
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
cpu.mem
|
cpu.mem
|
||||||
.load_region(Program, op).unwrap();
|
.load_region(Program, op).unwrap();
|
||||||
cpu.v = *INDX;
|
cpu.v = *INDX;
|
||||||
cpu.flags.quirks = Quirks::from(false);
|
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
|
cpu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
pub mod any_range;
|
pub mod any_range;
|
||||||
use any_range::AnyRange;
|
use any_range::AnyRange;
|
||||||
|
|
||||||
use crate::cpu::bus::Region;
|
use crate::cpu::mem::Region;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Result type, equivalent to [std::result::Result]<T, [enum@Error]>
|
/// Result type, equivalent to [std::result::Result]<T, [enum@Error]>
|
||||||
|
@ -10,11 +10,11 @@
|
|||||||
|
|
||||||
pub mod cpu;
|
pub mod cpu;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod screen;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
||||||
// Common imports for Chirp
|
// Common imports for Chirp
|
||||||
pub use cpu::{
|
pub use cpu::{
|
||||||
bus::{Bus, Region::*},
|
|
||||||
flags::Flags,
|
flags::Flags,
|
||||||
instruction::disassembler::{Dis, Disassembler},
|
instruction::disassembler::{Dis, Disassembler},
|
||||||
mode::Mode,
|
mode::Mode,
|
||||||
@ -22,8 +22,6 @@ pub use cpu::{
|
|||||||
CPU,
|
CPU,
|
||||||
};
|
};
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use traits::auto_cast::{AutoCast, Grab};
|
|
||||||
|
|
||||||
/// Holds the state of a Chip-8
|
/// Holds the state of a Chip-8
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct Chip8 {
|
pub struct Chip8 {
|
||||||
@ -32,3 +30,5 @@ pub struct Chip8 {
|
|||||||
/// Contains the memory of a chip-8
|
/// Contains the memory of a chip-8
|
||||||
pub bus: cpu::bus::Bus,
|
pub bus: cpu::bus::Bus,
|
||||||
}
|
}
|
||||||
|
pub use screen::Screen;
|
||||||
|
pub use traits::{AutoCast, FallibleAutoCast, Grab};
|
||||||
|
132
src/screen.rs
Normal file
132
src/screen.rs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
//! Contains the raw screen bytes, and an iterator over individual bits of those bytes.
|
||||||
|
|
||||||
|
use crate::traits::Grab;
|
||||||
|
|
||||||
|
/// Stores the screen bytes
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Screen {
|
||||||
|
/// The screen bytes
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]>> From<T> for Screen {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Screen {
|
||||||
|
bytes: value.as_ref().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Screen {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(256)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grab for Screen {
|
||||||
|
fn grab<I>(&self, index: I) -> Option<&<I as std::slice::SliceIndex<[u8]>>::Output>
|
||||||
|
where
|
||||||
|
I: std::slice::SliceIndex<[u8]>,
|
||||||
|
{
|
||||||
|
self.bytes.get(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grab_mut<I>(&mut self, index: I) -> Option<&mut <I as std::slice::SliceIndex<[u8]>>::Output>
|
||||||
|
where
|
||||||
|
I: std::slice::SliceIndex<[u8]>,
|
||||||
|
{
|
||||||
|
self.bytes.get_mut(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Screen {
|
||||||
|
/// Creates a new [Screen]
|
||||||
|
pub fn new(size: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
bytes: vec![0; size],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the screen has 0 elements
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.bytes.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the length of the [Screen]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.bytes.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a slice of the whole [Screen]
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
self.bytes.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the [Screen] to 0
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.bytes.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Grows the [Screen] memory to at least size bytes, but does not truncate
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::screen::Screen;
|
||||||
|
/// let mut screen = Screen::new(256);
|
||||||
|
/// assert_eq!(1234, screen.len());
|
||||||
|
/// screen.with_size(0);
|
||||||
|
/// assert_eq!(1234, screen.len());
|
||||||
|
/// ```
|
||||||
|
pub fn with_size(&mut self, size: usize) {
|
||||||
|
if self.len() < size {
|
||||||
|
self.bytes.resize(size, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints the [Screen] using either [drawille] or Box Drawing Characters
|
||||||
|
/// # Examples
|
||||||
|
/// [Screen::print_screen] will print the screen
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::screen::Screen;
|
||||||
|
/// let screen = Screen::default();
|
||||||
|
/// screen.print_screen();
|
||||||
|
/// ```
|
||||||
|
pub fn print_screen(&self) {
|
||||||
|
let len_log2 = self.bytes.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;
|
||||||
|
self.bytes
|
||||||
|
.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 self.bytes.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!("|");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,4 +3,5 @@
|
|||||||
|
|
||||||
//! Traits useful for Chirp
|
//! Traits useful for Chirp
|
||||||
|
|
||||||
pub mod auto_cast;
|
mod auto_cast;
|
||||||
|
pub use auto_cast::{AutoCast, FallibleAutoCast, Grab};
|
||||||
|
@ -3,26 +3,26 @@
|
|||||||
|
|
||||||
//! Traits for automatically serializing and deserializing Rust primitive types.
|
//! Traits for automatically serializing and deserializing Rust primitive types.
|
||||||
//!
|
//!
|
||||||
//! Users of this module should impl [Get]`<u8>` for their type, which notably returns `&[u8]` and `&mut [u8]`
|
//! Users of this module should impl [Grab]`<u8>` for their type, which notably returns `&[u8]` and `&mut [u8]`
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use core::mem::size_of;
|
use core::mem::size_of;
|
||||||
use std::{fmt::Debug, slice::SliceIndex};
|
use std::{fmt::Debug, slice::SliceIndex};
|
||||||
|
|
||||||
/// Gets a `&[T]` at [SliceIndex] `I`.
|
/// Get Raw Bytes at [SliceIndex] `I`.
|
||||||
///
|
///
|
||||||
/// This is similar to the [SliceIndex] method `.get(...)`, however implementing this trait
|
/// This is similar to the [SliceIndex] method `.get(...)`, however implementing this
|
||||||
/// for [u8] will auto-impl [ReadWrite]<([i8], [u8], [i16], [u16] ... [i128], [u128])>
|
/// trait will auto-impl [AutoCast]<([i8], [u8], [i16], [u16] ... [i128], [u128])>
|
||||||
pub trait Grab<T> {
|
pub trait Grab {
|
||||||
/// Gets the slice of Self at [SliceIndex] I
|
/// Gets the slice of Self at [SliceIndex] I
|
||||||
fn grab<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output>
|
fn grab<I>(&self, index: I) -> Option<&<I as SliceIndex<[u8]>>::Output>
|
||||||
where
|
where
|
||||||
I: SliceIndex<[T]>;
|
I: SliceIndex<[u8]>;
|
||||||
|
|
||||||
/// Gets a mutable slice of Self at [SliceIndex] I
|
/// Gets a mutable slice of Self at [SliceIndex] I
|
||||||
fn grab_mut<I>(&mut self, index: I) -> Option<&mut <I as SliceIndex<[T]>>::Output>
|
fn grab_mut<I>(&mut self, index: I) -> Option<&mut <I as SliceIndex<[u8]>>::Output>
|
||||||
where
|
where
|
||||||
I: SliceIndex<[T]>;
|
I: SliceIndex<[u8]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read or Write a T at address `addr`
|
/// Read or Write a T at address `addr`
|
||||||
@ -36,6 +36,10 @@ pub trait AutoCast<T>: FallibleAutoCast<T> {
|
|||||||
self.read_fallible(addr).unwrap_or_else(|e| panic!("{e:?}"))
|
self.read_fallible(addr).unwrap_or_else(|e| panic!("{e:?}"))
|
||||||
}
|
}
|
||||||
/// Write a T to address `addr`
|
/// Write a T to address `addr`
|
||||||
|
///
|
||||||
|
/// # Will Panic
|
||||||
|
///
|
||||||
|
/// This will panic on error. For a non-panicking implementation, do it yourself.
|
||||||
fn write(&mut self, addr: impl Into<usize>, data: T) {
|
fn write(&mut self, addr: impl Into<usize>, data: T) {
|
||||||
self.write_fallible(addr, data)
|
self.write_fallible(addr, data)
|
||||||
.unwrap_or_else(|e| panic!("{e:?}"));
|
.unwrap_or_else(|e| panic!("{e:?}"));
|
||||||
@ -43,7 +47,7 @@ pub trait AutoCast<T>: FallibleAutoCast<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read a T from address `addr`, and return the value as a [Result]
|
/// Read a T from address `addr`, and return the value as a [Result]
|
||||||
pub trait FallibleAutoCast<T>: Grab<u8> {
|
pub trait FallibleAutoCast<T>: Grab {
|
||||||
/// The [Err] type
|
/// The [Err] type
|
||||||
type Error: Debug;
|
type Error: Debug;
|
||||||
/// Read a T from address `addr`, returning the value as a [Result]
|
/// Read a T from address `addr`, returning the value as a [Result]
|
||||||
@ -59,8 +63,8 @@ pub trait FallibleAutoCast<T>: Grab<u8> {
|
|||||||
/// - `Self::to_be_bytes`
|
/// - `Self::to_be_bytes`
|
||||||
macro_rules! impl_rw {($($t:ty) ,* $(,)?) =>{
|
macro_rules! impl_rw {($($t:ty) ,* $(,)?) =>{
|
||||||
$(
|
$(
|
||||||
#[doc = concat!("Read or Write [", stringify!($t), "] at address `addr`")]
|
#[doc = concat!("Read or Write [", stringify!($t), "] at address `addr`, *discarding errors*.\n\nThis will never panic.")]
|
||||||
impl<T: Grab<u8> + FallibleAutoCast<$t>> AutoCast<$t> for T {
|
impl<T: Grab + FallibleAutoCast<$t>> AutoCast<$t> for T {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read(&self, addr: impl Into<usize>) -> $t {
|
fn read(&self, addr: impl Into<usize>) -> $t {
|
||||||
self.read_fallible(addr).ok().unwrap_or_default()
|
self.read_fallible(addr).ok().unwrap_or_default()
|
||||||
@ -70,7 +74,7 @@ macro_rules! impl_rw {($($t:ty) ,* $(,)?) =>{
|
|||||||
self.write_fallible(addr, data).ok();
|
self.write_fallible(addr, data).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Grab<u8>> FallibleAutoCast<$t> for T {
|
impl<T: Grab> FallibleAutoCast<$t> for T {
|
||||||
type Error = $crate::error::Error;
|
type Error = $crate::error::Error;
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_fallible(&self, addr: impl Into<usize>) -> $crate::error::Result<$t> {
|
fn read_fallible(&self, addr: impl Into<usize>) -> $crate::error::Result<$t> {
|
||||||
|
@ -6,20 +6,14 @@
|
|||||||
|
|
||||||
pub use chirp::*;
|
pub use chirp::*;
|
||||||
|
|
||||||
fn setup_environment() -> (CPU, Bus) {
|
fn setup_environment() -> (CPU, Screen) {
|
||||||
let mut cpu = CPU::default();
|
let mut cpu = CPU::default();
|
||||||
cpu.flags = Flags {
|
cpu.flags = Flags {
|
||||||
debug: true,
|
debug: true,
|
||||||
pause: false,
|
pause: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
(
|
(cpu, Screen::default())
|
||||||
cpu,
|
|
||||||
bus! {
|
|
||||||
// Create a screen, and fill it with garbage
|
|
||||||
Screen [0x000..0x100] = b"jsuadhgufywegrwsdyfogbbg4owgbrt",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SuiteTest {
|
struct SuiteTest {
|
||||||
@ -28,23 +22,21 @@ struct SuiteTest {
|
|||||||
screen: &'static [u8],
|
screen: &'static [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_screentest(test: SuiteTest, mut cpu: CPU, mut bus: Bus) {
|
fn run_screentest(test: SuiteTest, mut cpu: CPU, mut screen: Screen) {
|
||||||
// Set the test to run
|
// Set the test to run
|
||||||
cpu.poke(0x1ffu16, test.test);
|
cpu.poke(0x1ffu16, test.test);
|
||||||
cpu.load_program_bytes(test.data).unwrap();
|
cpu.load_program_bytes(test.data).unwrap();
|
||||||
// The test suite always initiates a keypause on test completion
|
// The test suite always initiates a keypause on test completion
|
||||||
while !(cpu.flags.is_paused()) {
|
while !(cpu.flags.is_paused()) {
|
||||||
cpu.multistep(&mut bus, 10).unwrap();
|
cpu.multistep(&mut screen, 10).unwrap();
|
||||||
if cpu.cycle() > 1000000 {
|
if cpu.cycle() > 1000000 {
|
||||||
panic!("test {} took too long", test.test)
|
panic!("test {} took too long", test.test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Compare the screen to the reference screen buffer
|
// Compare the screen to the reference screen buffer
|
||||||
bus.print_screen().unwrap();
|
screen.print_screen();
|
||||||
bus! {crate::cpu::bus::Region::Screen [0..256] = test.screen}
|
Screen::from(test.screen).print_screen();
|
||||||
.print_screen()
|
assert_eq!(screen.grab(..).unwrap(), test.screen);
|
||||||
.unwrap();
|
|
||||||
assert_eq!(bus.get_region(Screen).unwrap(), test.screen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,52 +1,45 @@
|
|||||||
//! Testing methods on Chirp's public API
|
//! Testing methods on Chirp's public API
|
||||||
|
use chirp::cpu::mem::Region::*;
|
||||||
use chirp::*;
|
use chirp::*;
|
||||||
use std::{collections::hash_map::DefaultHasher, hash::Hash};
|
use std::{collections::hash_map::DefaultHasher, hash::Hash};
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[allow(clippy::redundant_clone)]
|
|
||||||
fn chip8() {
|
|
||||||
let ch8 = Chip8::default(); // Default
|
|
||||||
let ch82 = ch8.clone(); // Clone
|
|
||||||
assert_eq!(ch8, ch82); // PartialEq
|
|
||||||
println!("{ch8:?}"); // Debug
|
|
||||||
}
|
|
||||||
|
|
||||||
mod bus {
|
mod bus {
|
||||||
use super::*;
|
use super::*;
|
||||||
mod region {
|
mod region {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[test]
|
#[test]
|
||||||
fn copy() {
|
fn copy() {
|
||||||
let r1 = Screen;
|
let r1 = Charset;
|
||||||
let r2 = r1;
|
let r2 = r1;
|
||||||
assert_eq!(r1, r2);
|
assert_eq!(r1, r2);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(clippy::clone_on_copy)]
|
#[allow(clippy::clone_on_copy)]
|
||||||
fn clone() {
|
fn clone() {
|
||||||
let r1 = Screen;
|
let r1 = Charset;
|
||||||
let r2 = r1.clone();
|
let r2 = r1.clone();
|
||||||
assert_eq!(r1, r2);
|
assert_eq!(r1, r2);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn display() {
|
fn display() {
|
||||||
println!("{Charset}{Program}{Screen}{Count}");
|
println!("{Charset}{Program}{Count}");
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn debug() {
|
fn debug() {
|
||||||
println!("{Charset:?}{Program:?}{Screen:?}{Count:?}");
|
println!("{Charset:?}{Program:?}{Count:?}");
|
||||||
}
|
}
|
||||||
// lmao the things you do for test coverage
|
// lmao the things you do for test coverage
|
||||||
#[test]
|
#[test]
|
||||||
fn eq() {
|
fn eq() {
|
||||||
assert_eq!(Screen, Screen);
|
assert_eq!(Charset, Charset);
|
||||||
assert_ne!(Charset, Program);
|
assert_ne!(Charset, Program);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn ord() {
|
fn ord() {
|
||||||
assert_eq!(Screen, Charset.max(Program).max(Screen));
|
assert_eq!(Program, Charset.max(Program));
|
||||||
assert!(Charset < Program && Program < Screen);
|
assert!(Charset < Program);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn hash() {
|
fn hash() {
|
||||||
@ -59,7 +52,7 @@ mod bus {
|
|||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn bus_missing_region() {
|
fn bus_missing_region() {
|
||||||
// Print the screen of a bus with no screen
|
// Print the screen of a bus with no screen
|
||||||
bus! {}.print_screen().unwrap()
|
mem! {}.get_region(Charset).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +201,7 @@ mod dis {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn error() {
|
fn error() {
|
||||||
let error = chirp::error::Error::MissingRegion { region: Screen };
|
let error = chirp::error::Error::InvalidAddressRange { range: (..).into() };
|
||||||
// Print it with Display and Debug
|
// Print it with Display and Debug
|
||||||
println!("{error} {error:?}");
|
println!("{error} {error:?}");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user