bus: Major refactor: auto-impl implicit casting for all numerics
This commit is contained in:
parent
7d25a9f5f1
commit
95d4751cdd
@ -11,7 +11,7 @@ use std::{
|
||||
};
|
||||
|
||||
use chirp::{
|
||||
bus::{Bus, Region},
|
||||
cpu::bus::{Bus, Region},
|
||||
error::Result,
|
||||
Chip8,
|
||||
};
|
||||
|
23
src/cpu.rs
23
src/cpu.rs
@ -6,22 +6,22 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub mod bus;
|
||||
pub mod disassembler;
|
||||
pub mod flags;
|
||||
pub mod init;
|
||||
pub mod instruction;
|
||||
pub mod mode;
|
||||
pub mod quirks;
|
||||
|
||||
use self::{
|
||||
bus::{Bus, Get, ReadWrite, Region},
|
||||
disassembler::{Dis, Disassembler, Insn},
|
||||
flags::Flags,
|
||||
mode::Mode,
|
||||
quirks::Quirks,
|
||||
};
|
||||
use crate::{
|
||||
bus::{Bus, Read, Region, Write},
|
||||
error::{Error, Result},
|
||||
};
|
||||
use crate::error::{Error, Result};
|
||||
use imperative_rs::InstructionSet;
|
||||
use owo_colors::OwoColorize;
|
||||
use rand::random;
|
||||
@ -86,7 +86,7 @@ impl CPU {
|
||||
/// 0xefe, // top of stack
|
||||
/// Dis::default(),
|
||||
/// vec![], // Breakpoints
|
||||
/// ControlFlags::default()
|
||||
/// Flags::default()
|
||||
/// );
|
||||
/// dbg!(cpu);
|
||||
/// ```
|
||||
@ -143,8 +143,8 @@ impl CPU {
|
||||
/// Releases a key, and reports whether the key's state changed.
|
||||
/// If key is outside range `0..=0xF`, returns [Error::InvalidKey].
|
||||
///
|
||||
/// If [ControlFlags::keypause] was enabled, it is disabled,
|
||||
/// and the [ControlFlags::lastkey] is recorded.
|
||||
/// If [Flags::keypause] was enabled, it is disabled,
|
||||
/// and the [Flags::lastkey] is recorded.
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use chirp::*;
|
||||
@ -280,7 +280,7 @@ impl CPU {
|
||||
/// 0xefe,
|
||||
/// Dis::default(),
|
||||
/// vec![],
|
||||
/// ControlFlags::default()
|
||||
/// Flags::default()
|
||||
/// );
|
||||
/// cpu.flags.keypause = true;
|
||||
/// cpu.flags.draw_wait = true;
|
||||
@ -487,14 +487,13 @@ impl CPU {
|
||||
.try_into()
|
||||
.expect("`slice` should be exactly 2 bytes.")
|
||||
} else {
|
||||
return Err(Error::InvalidBusRange {
|
||||
return Err(Error::InvalidAddressRange {
|
||||
range: self.pc as usize..self.pc as usize + 2,
|
||||
});
|
||||
};
|
||||
|
||||
// Print opcode disassembly:
|
||||
if self.flags.debug {
|
||||
println!("{:?}", self.timers.insn.elapsed().bright_black());
|
||||
self.timers.insn = Instant::now();
|
||||
std::print!(
|
||||
"{:3} {:03x}: {:<36}",
|
||||
@ -514,6 +513,10 @@ impl CPU {
|
||||
});
|
||||
}
|
||||
|
||||
if self.flags.debug {
|
||||
println!("{:?}", self.timers.insn.elapsed().bright_black());
|
||||
}
|
||||
|
||||
// process breakpoints
|
||||
if !self.breakpoints.is_empty() && self.breakpoints.contains(&self.pc) {
|
||||
self.flags.pause = true;
|
||||
|
@ -24,27 +24,58 @@ use std::{
|
||||
#[macro_export]
|
||||
macro_rules! bus {
|
||||
($($name:path $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => {
|
||||
$crate::bus::Bus::default()
|
||||
$crate::cpu::bus::Bus::default()
|
||||
$(
|
||||
.add_region($name, $range)
|
||||
.add_region_owned($name, $range)
|
||||
$(
|
||||
.load_region($name, $data)
|
||||
.load_region_owned($name, $data)
|
||||
)?
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Traits Read and Write are here purely to make implementing other things more bearable
|
||||
/// Read a T from address `addr`
|
||||
pub trait Read<T> {
|
||||
/// Read a T from address `addr`
|
||||
fn read(&self, addr: impl Into<usize>) -> T;
|
||||
}
|
||||
pub mod read;
|
||||
pub use read::{Get, ReadWrite};
|
||||
|
||||
/// Write "some data" to the Bus
|
||||
pub trait Write<T> {
|
||||
/// Write a T to address `addr`
|
||||
fn write(&mut self, addr: impl Into<usize>, data: T);
|
||||
// Traits Read and Write are here purely to make implementing other things more bearable
|
||||
impl Get<u8> for Bus {
|
||||
/// Gets a slice of [Bus] memory
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// .add_region_owned(Program, 0..10);
|
||||
/// assert!([0;10].as_slice() == bus.get(0..10).unwrap());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[u8]>>::Output>
|
||||
where
|
||||
I: SliceIndex<[u8]>,
|
||||
{
|
||||
self.memory.get(index)
|
||||
}
|
||||
|
||||
/// Gets a mutable slice of [Bus] memory
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let mut bus = Bus::new()
|
||||
/// .add_region_owned(Program, 0..10);
|
||||
/// assert!([0;10].as_slice() == bus.get_mut(0..10).unwrap());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
fn get_mut<I>(&mut self, index: I) -> Option<&mut <I as SliceIndex<[u8]>>::Output>
|
||||
where
|
||||
I: SliceIndex<[u8]>,
|
||||
{
|
||||
self.memory.get_mut(index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a named region in memory
|
||||
@ -70,10 +101,10 @@ impl Display for Region {
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Region::Charset => "charset",
|
||||
Region::Program => "program",
|
||||
Region::Screen => "screen",
|
||||
Region::Stack => "stack",
|
||||
Region::Charset => "Charset",
|
||||
Region::Program => "Program",
|
||||
Region::Screen => "Screen",
|
||||
Region::Stack => "Stack",
|
||||
_ => "",
|
||||
}
|
||||
)
|
||||
@ -109,7 +140,7 @@ impl Bus {
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// .add_region(Program, 0..1234);
|
||||
/// .add_region_owned(Program, 0..1234);
|
||||
/// assert_eq!(1234, bus.len());
|
||||
///# Ok(())
|
||||
///# }
|
||||
@ -149,30 +180,40 @@ impl Bus {
|
||||
self.memory.resize(size, 0);
|
||||
}
|
||||
}
|
||||
/// Adds a new named range (Region) to the bus
|
||||
|
||||
/// Adds a new names range ([Region]) to an owned [Bus]
|
||||
pub fn add_region_owned(mut self, name: Region, range: Range<usize>) -> Self {
|
||||
self.add_region(name, range);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a new named range ([Region]) to a [Bus]
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new().add_region(Program, 0..1234);
|
||||
/// let mut bus = Bus::new();
|
||||
/// bus.add_region(Program, 0..1234);
|
||||
/// assert_eq!(1234, bus.len());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
pub fn add_region(mut self, name: Region, range: Range<usize>) -> Self {
|
||||
pub fn add_region(&mut self, name: Region, range: Range<usize>) -> &mut Self {
|
||||
self.with_size(range.end);
|
||||
if let Some(region) = self.region.get_mut(name as usize) {
|
||||
*region = Some(range);
|
||||
}
|
||||
self
|
||||
}
|
||||
/// Updates an existing named range (Region)
|
||||
|
||||
/// Updates an existing [Region]
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new().add_region(Program, 0..1234);
|
||||
/// assert_eq!(1234, bus.len());
|
||||
/// let mut bus = Bus::new().add_region_owned(Program, 0..1234);
|
||||
/// bus.set_region(Program, 1234..2345);
|
||||
/// assert_eq!(2345, bus.len());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
@ -183,32 +224,40 @@ impl Bus {
|
||||
}
|
||||
self
|
||||
}
|
||||
/// Loads data into a named region
|
||||
|
||||
/// Loads data into a [Region] on an *owned* [Bus], for use during initialization
|
||||
pub fn load_region_owned(mut self, name: Region, data: &[u8]) -> Self {
|
||||
self.load_region(name, data).ok();
|
||||
self
|
||||
}
|
||||
|
||||
/// Loads data into a named [Region]
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// .add_region(Program, 0..1234)
|
||||
/// .load_region(Program, b"Hello, world!");
|
||||
/// .add_region_owned(Program, 0..1234)
|
||||
/// .load_region(Program, b"Hello, world!")?;
|
||||
///# // TODO: Test if region actually contains "Hello, world!"
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
pub fn load_region(mut self, name: Region, data: &[u8]) -> Self {
|
||||
pub fn load_region(&mut self, name: Region, data: &[u8]) -> Result<&mut Self> {
|
||||
use std::io::Write;
|
||||
if let Some(mut region) = self.get_region_mut(name) {
|
||||
region.write(data).ok(); // TODO: THIS SUCKS
|
||||
assert_eq!(region.write(data)?, data.len());
|
||||
}
|
||||
self
|
||||
Ok(self)
|
||||
}
|
||||
/// Fills a named region with zeroes
|
||||
|
||||
/// Fills a [Region] with zeroes
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// .add_region(Program, 0..1234)
|
||||
/// .add_region_owned(Program, 0..1234)
|
||||
/// .clear_region(Program);
|
||||
///# // TODO: test if region actually clear
|
||||
///# Ok(())
|
||||
@ -219,7 +268,7 @@ impl Bus {
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// .add_region(Program, 0..1234)
|
||||
/// .add_region_owned(Program, 0..1234)
|
||||
/// .clear_region(Screen);
|
||||
///# // TODO: test if region actually clear
|
||||
///# Ok(())
|
||||
@ -232,54 +281,20 @@ impl Bus {
|
||||
self
|
||||
}
|
||||
|
||||
/// Gets a slice of bus memory
|
||||
/// Gets a slice of a named [Region] of memory
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// .add_region(Program, 0..10);
|
||||
/// assert!([0;10].as_slice() == bus.get(0..10).unwrap());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[u8]>>::Output>
|
||||
where
|
||||
I: SliceIndex<[u8]>,
|
||||
{
|
||||
self.memory.get(index)
|
||||
}
|
||||
|
||||
/// Gets a mutable slice of bus memory
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let mut bus = Bus::new()
|
||||
/// .add_region(Program, 0..10);
|
||||
/// assert!([0;10].as_slice() == bus.get_mut(0..10).unwrap());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
pub fn get_mut<I>(&mut self, index: I) -> Option<&mut <I as SliceIndex<[u8]>>::Output>
|
||||
where
|
||||
I: SliceIndex<[u8]>,
|
||||
{
|
||||
self.memory.get_mut(index)
|
||||
}
|
||||
|
||||
/// Gets a slice of a named region of memory
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// .add_region(Program, 0..10);
|
||||
/// .add_region_owned(Program, 0..10);
|
||||
/// assert!([0;10].as_slice() == bus.get_region(Program).unwrap());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn get_region(&self, name: Region) -> Option<&[u8]> {
|
||||
debug_assert!(self.region.get(name as usize).is_some());
|
||||
self.get(self.region.get(name as usize)?.clone()?)
|
||||
}
|
||||
|
||||
@ -289,12 +304,14 @@ impl Bus {
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let mut bus = Bus::new()
|
||||
/// .add_region(Program, 0..10);
|
||||
/// .add_region_owned(Program, 0..10);
|
||||
/// assert!([0;10].as_slice() == bus.get_region_mut(Program).unwrap());
|
||||
///# Ok(())
|
||||
///# }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn get_region_mut(&mut self, name: Region) -> Option<&mut [u8]> {
|
||||
debug_assert!(self.region.get(name as usize).is_some());
|
||||
self.get_mut(self.region.get(name as usize)?.clone()?)
|
||||
}
|
||||
|
||||
@ -306,7 +323,7 @@ impl Bus {
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let bus = Bus::new()
|
||||
/// .add_region(Screen, 0x000..0x100);
|
||||
/// .add_region_owned(Screen, 0x000..0x100);
|
||||
/// bus.print_screen()?;
|
||||
///# Ok(())
|
||||
///# }
|
||||
@ -316,7 +333,7 @@ impl Bus {
|
||||
///# use chirp::*;
|
||||
///# fn main() -> Result<()> {
|
||||
/// let mut bus = Bus::new()
|
||||
/// .add_region(Program, 0..10);
|
||||
/// .add_region_owned(Program, 0..10);
|
||||
/// bus.print_screen()?;
|
||||
///# Ok(())
|
||||
///# }
|
||||
@ -326,21 +343,18 @@ impl Bus {
|
||||
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));
|
||||
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 mut canvas = Canvas::new(dbg!(width * 8), dbg!(height));
|
||||
let width = width * 8;
|
||||
screen
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(bytei, byte)| {
|
||||
(0..8)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(move |(biti, bit)| {
|
||||
(0..8).enumerate().filter_map(move |(biti, bit)| {
|
||||
if (byte << bit) & 0x80 != 0 {
|
||||
Some(bytei * 8 + biti)
|
||||
} else {
|
||||
@ -371,112 +385,6 @@ impl Bus {
|
||||
}
|
||||
}
|
||||
|
||||
impl Read<u8> for Bus {
|
||||
/// Read a u8 from address `addr`
|
||||
fn read(&self, addr: impl Into<usize>) -> u8 {
|
||||
let addr: usize = addr.into();
|
||||
*self.memory.get(addr).unwrap_or(&0xc5)
|
||||
}
|
||||
}
|
||||
|
||||
impl Read<u16> for Bus {
|
||||
/// Read a u16 from address `addr`
|
||||
fn read(&self, addr: impl Into<usize>) -> u16 {
|
||||
let addr: usize = addr.into();
|
||||
if let Some(bytes) = self.memory.get(addr..addr + 2) {
|
||||
u16::from_be_bytes(bytes.try_into().expect("Should get 2 bytes"))
|
||||
} else {
|
||||
0xc5c5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read<u32> for Bus {
|
||||
/// Read a u16 from address `addr`
|
||||
fn read(&self, addr: impl Into<usize>) -> u32 {
|
||||
let addr: usize = addr.into();
|
||||
if let Some(bytes) = self.memory.get(addr..addr + 4) {
|
||||
u32::from_be_bytes(bytes.try_into().expect("Should get 4 bytes"))
|
||||
} else {
|
||||
0xc5c5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read<u64> for Bus {
|
||||
/// Read a u16 from address `addr`
|
||||
fn read(&self, addr: impl Into<usize>) -> u64 {
|
||||
let addr: usize = addr.into();
|
||||
if let Some(bytes) = self.memory.get(addr..addr + 8) {
|
||||
u64::from_be_bytes(bytes.try_into().expect("Should get 8 bytes"))
|
||||
} else {
|
||||
0xc5c5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read<u128> for Bus {
|
||||
/// Read a u16 from address `addr`
|
||||
fn read(&self, addr: impl Into<usize>) -> u128 {
|
||||
let addr: usize = addr.into();
|
||||
if let Some(bytes) = self.memory.get(addr..addr + 16) {
|
||||
u128::from_be_bytes(bytes.try_into().expect("Should get 16 bytes"))
|
||||
} else {
|
||||
0xc5c5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write<u8> for Bus {
|
||||
/// Write a u8 to address `addr`
|
||||
fn write(&mut self, addr: impl Into<usize>, data: u8) {
|
||||
let addr: usize = addr.into();
|
||||
if let Some(byte) = self.get_mut(addr) {
|
||||
*byte = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write<u16> for Bus {
|
||||
/// Write a u16 to address `addr`
|
||||
fn write(&mut self, addr: impl Into<usize>, data: u16) {
|
||||
let addr: usize = addr.into();
|
||||
if let Some(slice) = self.get_mut(addr..addr + 2) {
|
||||
data.to_be_bytes().as_mut().swap_with_slice(slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write<u32> for Bus {
|
||||
/// Write a u16 to address `addr`
|
||||
fn write(&mut self, addr: impl Into<usize>, data: u32) {
|
||||
let addr: usize = addr.into();
|
||||
if let Some(slice) = self.get_mut(addr..addr + 4) {
|
||||
data.to_be_bytes().as_mut().swap_with_slice(slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write<u64> for Bus {
|
||||
/// Write a u16 to address `addr`
|
||||
fn write(&mut self, addr: impl Into<usize>, data: u64) {
|
||||
let addr: usize = addr.into();
|
||||
if let Some(slice) = self.get_mut(addr..addr + 8) {
|
||||
data.to_be_bytes().as_mut().swap_with_slice(slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write<u128> for Bus {
|
||||
/// Write a u16 to address `addr`
|
||||
fn write(&mut self, addr: impl Into<usize>, data: u128) {
|
||||
let addr: usize = addr.into();
|
||||
if let Some(slice) = self.get_mut(addr..addr + 16) {
|
||||
data.to_be_bytes().as_mut().swap_with_slice(slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_feature = "rhexdump")]
|
||||
impl Display for Bus {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
100
src/cpu/bus/read.rs
Normal file
100
src/cpu/bus/read.rs
Normal file
@ -0,0 +1,100 @@
|
||||
// (c) 2023 John A. Breaux
|
||||
// This code is licensed under MIT license (see LICENSE.txt for details)
|
||||
|
||||
//! Trait for getting a generic integer for a structure.
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use core::mem::size_of;
|
||||
use std::{fmt::Debug, slice::SliceIndex};
|
||||
|
||||
/// Gets a `&[T]` at [SliceIndex] `I`.
|
||||
///
|
||||
/// This is similar to the [SliceIndex] method `.get(...)`, however implementing this trait
|
||||
/// for [u8] will auto-impl [Read]<(i8, u8, i16, u16 ... i128, u128)>
|
||||
pub trait Get<T> {
|
||||
/// Gets the slice of Self at [SliceIndex] I
|
||||
fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output>
|
||||
where
|
||||
I: SliceIndex<[T]>;
|
||||
|
||||
/// Gets a mutable slice of Self at [SliceIndex] I
|
||||
fn get_mut<I>(&mut self, index: I) -> Option<&mut <I as SliceIndex<[T]>>::Output>
|
||||
where
|
||||
I: SliceIndex<[T]>;
|
||||
}
|
||||
|
||||
/// Read a T from address `addr`
|
||||
pub trait ReadWrite<T>: FallibleReadWrite<T> {
|
||||
/// Reads a T from address `addr`
|
||||
///
|
||||
/// # May Panic
|
||||
///
|
||||
/// If the type is not [Default], this will panic on error.
|
||||
fn read(&self, addr: impl Into<usize>) -> T {
|
||||
self.read_fallible(addr).unwrap_or_else(|e| panic!("{e:?}"))
|
||||
}
|
||||
/// Write a T to address `addr`
|
||||
fn write(&mut self, addr: impl Into<usize>, data: T) {
|
||||
self.write_fallible(addr, data)
|
||||
.unwrap_or_else(|e| panic!("{e:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a T from address `addr`, and return the value as a [Result]
|
||||
pub trait FallibleReadWrite<T>: Get<u8> {
|
||||
/// The [Err] type returned by [read_fallible]
|
||||
type Error: Debug;
|
||||
/// Read a T from address `addr`, returning the value as a [Result]
|
||||
fn read_fallible(&self, addr: impl Into<usize>) -> Result<T, Self::Error>;
|
||||
/// Write a T to address `addr`, returning the value as a [Result]
|
||||
fn write_fallible(&mut self, addr: impl Into<usize>, data: T) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// Implements Read and Write for the provided types
|
||||
///
|
||||
/// Relies on inherent methods of Rust numeric types:
|
||||
/// - `Self::from_be_bytes`
|
||||
/// - `Self::to_be_bytes`
|
||||
macro_rules! impl_rw {
|
||||
($($t:ty) ,* $(,)?) => {
|
||||
$(
|
||||
impl<T: Get<u8> + FallibleReadWrite<$t>> ReadWrite<$t> for T {
|
||||
#[inline(always)]
|
||||
fn read(&self, addr: impl Into<usize>) -> $t {
|
||||
self.read_fallible(addr).ok().unwrap_or_default()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write(&mut self, addr: impl Into<usize>, data: $t) {
|
||||
self.write_fallible(addr, data).ok();
|
||||
}
|
||||
}
|
||||
impl<T: Get<u8>> FallibleReadWrite<$t> for T {
|
||||
type Error = $crate::error::Error;
|
||||
#[inline(always)]
|
||||
fn read_fallible(&self, addr: impl Into<usize>) -> $crate::error::Result<$t> {
|
||||
let addr: usize = addr.into();
|
||||
let range = addr..addr + core::mem::size_of::<$t>();
|
||||
if let Some(bytes) = self.get(range.clone()) {
|
||||
// Chip-8 is a big-endian system
|
||||
Ok(<$t>::from_be_bytes(bytes.try_into()?))
|
||||
} else {
|
||||
Err($crate::error::Error::InvalidAddressRange{range})
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write_fallible(&mut self, addr: impl Into<usize>, data: $t) -> std::result::Result<(), Self::Error> {
|
||||
let addr: usize = addr.into();
|
||||
if let Some(slice) = self.get_mut(addr..addr + core::mem::size_of::<$t>()) {
|
||||
// Chip-8 is a big-endian system
|
||||
data.to_be_bytes().as_mut().swap_with_slice(slice);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_rw!(i8, i16, i32, i64, i128);
|
||||
impl_rw!(u8, u16, u32, u64, u128);
|
||||
impl_rw!(f32, f64);
|
@ -74,7 +74,7 @@ impl CPU {
|
||||
}
|
||||
/// |`00ee`| Returns from subroutine
|
||||
#[inline(always)]
|
||||
pub(super) fn ret(&mut self, bus: &impl Read<u16>) {
|
||||
pub(super) fn ret(&mut self, bus: &impl ReadWrite<u16>) {
|
||||
self.sp = self.sp.wrapping_add(2);
|
||||
self.pc = bus.read(self.sp);
|
||||
}
|
||||
@ -97,7 +97,7 @@ impl CPU {
|
||||
impl CPU {
|
||||
/// |`2aaa`| Pushes pc onto the stack, then jumps to a
|
||||
#[inline(always)]
|
||||
pub(super) fn call(&mut self, a: Adr, bus: &mut impl Write<u16>) {
|
||||
pub(super) fn call(&mut self, a: Adr, bus: &mut impl ReadWrite<u16>) {
|
||||
bus.write(self.sp, self.pc);
|
||||
self.sp = self.sp.wrapping_sub(2);
|
||||
self.pc = a;
|
||||
@ -518,7 +518,7 @@ impl CPU {
|
||||
|
||||
/// |`00fb`| Scroll the screen right
|
||||
#[inline(always)]
|
||||
pub(super) fn scroll_right(&mut self, bus: &mut (impl Read<u128> + Write<u128>)) {
|
||||
pub(super) fn scroll_right(&mut self, bus: &mut (impl ReadWrite<u128> + ReadWrite<u128>)) {
|
||||
// Get a line from the bus
|
||||
for i in (0..16 * 64).step_by(16) {
|
||||
//let line: u128 = bus.read(self.screen + i) >> 4;
|
||||
@ -527,7 +527,7 @@ impl CPU {
|
||||
}
|
||||
/// |`00fc`| Scroll the screen right
|
||||
#[inline(always)]
|
||||
pub(super) fn scroll_left(&mut self, bus: &mut (impl Read<u128> + Write<u128>)) {
|
||||
pub(super) fn scroll_left(&mut self, bus: &mut (impl ReadWrite<u128> + ReadWrite<u128>)) {
|
||||
// Get a line from the bus
|
||||
for i in (0..16 * 64).step_by(16) {
|
||||
let line: u128 = (bus.read(self.screen + i) & !(0xf << 124)) << 4;
|
||||
@ -559,7 +559,7 @@ impl CPU {
|
||||
let w_bytes = w / 8;
|
||||
if let Some(sprite) = bus.get(self.i as usize..(self.i + 32) as usize) {
|
||||
let sprite = sprite.to_owned();
|
||||
for (line, sprite) in sprite.chunks(2).enumerate() {
|
||||
for (line, sprite) in sprite.chunks_exact(2).enumerate() {
|
||||
let sprite = u16::from_be_bytes(
|
||||
sprite
|
||||
.try_into()
|
||||
|
@ -15,7 +15,7 @@
|
||||
use super::*;
|
||||
use crate::{
|
||||
bus,
|
||||
bus::{Bus, Region::*},
|
||||
cpu::bus::{Bus, Region::*},
|
||||
};
|
||||
|
||||
mod decode;
|
||||
@ -758,7 +758,7 @@ mod io {
|
||||
// Debug mode is 5x slower
|
||||
cpu.flags.debug = false;
|
||||
// Load the test program
|
||||
bus = bus.load_region(Program, test.program);
|
||||
bus = bus.load_region_owned(Program, test.program);
|
||||
// Run the test program for the specified number of steps
|
||||
while cpu.cycle() < test.steps {
|
||||
cpu.multistep(&mut bus, test.steps - cpu.cycle())
|
||||
@ -1151,7 +1151,7 @@ mod behavior {
|
||||
// The bus extends from 0x0..0x1000
|
||||
cpu.pc = 0xfff;
|
||||
match cpu.tick(&mut bus) {
|
||||
Err(Error::InvalidBusRange { range }) => {
|
||||
Err(Error::InvalidAddressRange { range }) => {
|
||||
eprintln!("InvalidBusRange {{ {range:04x?} }}")
|
||||
}
|
||||
other => unreachable!("{other:04x?}"),
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::bus::Region;
|
||||
use crate::cpu::bus::Region;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Result type, equivalent to [std::result::Result]<T, [enum@Error]>
|
||||
@ -36,7 +36,7 @@ pub enum Error {
|
||||
},
|
||||
/// Tried to fetch [Range] from bus, received nothing
|
||||
#[error("Invalid range {range:04x?} for bus")]
|
||||
InvalidBusRange {
|
||||
InvalidAddressRange {
|
||||
/// The offending [Range]
|
||||
range: Range<usize>,
|
||||
},
|
||||
|
@ -8,13 +8,12 @@
|
||||
//!
|
||||
//! Hopefully, though, you'll find some use in it.
|
||||
|
||||
pub mod bus;
|
||||
pub mod cpu;
|
||||
pub mod error;
|
||||
|
||||
// Common imports for Chirp
|
||||
pub use bus::{Bus, Read, Region::*, Write};
|
||||
pub use cpu::{
|
||||
bus::{Bus, Get, ReadWrite, Region::*},
|
||||
disassembler::{Dis, Disassembler},
|
||||
flags::Flags,
|
||||
mode::Mode,
|
||||
@ -29,5 +28,5 @@ pub struct Chip8 {
|
||||
/// Contains the registers, flags, and operating state for a single Chip-8
|
||||
pub cpu: cpu::CPU,
|
||||
/// Contains the memory of a chip-8
|
||||
pub bus: bus::Bus,
|
||||
pub bus: cpu::bus::Bus,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user