bus.rs/cpu.rs: Improve doctest coverage
This commit is contained in:
parent
d5cfdc6802
commit
59a0b2fada
163
src/bus.rs
163
src/bus.rs
@ -2,6 +2,7 @@
|
|||||||
// This code is licensed under MIT license (see LICENSE.txt for details)
|
// This code is licensed under MIT license (see LICENSE.txt for details)
|
||||||
|
|
||||||
//! The Bus connects the CPU to Memory
|
//! The Bus connects the CPU to Memory
|
||||||
|
//!
|
||||||
//! This is more of a memory management unit + some utils for reading/writing
|
//! This is more of a memory management unit + some utils for reading/writing
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
@ -12,7 +13,7 @@ use std::{
|
|||||||
slice::SliceIndex,
|
slice::SliceIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Creates a new bus, instantiating BusConnectable devices
|
/// Creates a new bus, growing the backing memory as needed
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use chirp::prelude::*;
|
/// # use chirp::prelude::*;
|
||||||
@ -24,7 +25,7 @@ use std::{
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! bus {
|
macro_rules! bus {
|
||||||
($($name:path $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => {
|
($($name:path $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => {
|
||||||
$crate::bus::Bus::new()
|
$crate::bus::Bus::default()
|
||||||
$(
|
$(
|
||||||
.add_region($name, $range)
|
.add_region($name, $range)
|
||||||
$(
|
$(
|
||||||
@ -35,7 +36,7 @@ macro_rules! bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
/// Do whatever `Read` means to you
|
/// Read a T from address `addr`
|
||||||
pub trait Read<T> {
|
pub trait Read<T> {
|
||||||
/// Read a T from address `addr`
|
/// Read a T from address `addr`
|
||||||
fn read(&self, addr: impl Into<usize>) -> T;
|
fn read(&self, addr: impl Into<usize>) -> T;
|
||||||
@ -47,6 +48,7 @@ pub trait Write<T> {
|
|||||||
fn write(&mut self, addr: impl Into<usize>, data: T);
|
fn write(&mut self, addr: impl Into<usize>, data: T);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a named region in memory
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum Region {
|
pub enum Region {
|
||||||
Charset,
|
Charset,
|
||||||
@ -70,7 +72,7 @@ impl Display for Region {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store 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)]
|
||||||
pub struct Bus {
|
pub struct Bus {
|
||||||
memory: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
@ -78,29 +80,94 @@ pub struct Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Bus {
|
impl Bus {
|
||||||
/// Construct a new bus
|
// TODO: make bus::new() give a properly set up bus with a default memory map
|
||||||
|
/// Constructs a new bus
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let bus = Bus::new();
|
||||||
|
/// assert!(bus.is_empty());
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Bus::default()
|
Bus::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the length of the bus' backing memory
|
/// Gets the length of the bus' backing memory
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let bus = Bus::new()
|
||||||
|
/// .add_region(Program, 0..1234);
|
||||||
|
/// assert_eq!(1234, bus.len());
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.memory.len()
|
self.memory.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the backing memory contains no elements
|
/// Returns true if the backing memory contains no elements
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let bus = Bus::new();
|
||||||
|
/// assert!(bus.is_empty());
|
||||||
|
///# 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 Bus backing memory to at least size bytes, but does not truncate
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut bus = Bus::new();
|
||||||
|
/// bus.with_size(1234);
|
||||||
|
/// assert_eq!(1234, bus.len());
|
||||||
|
/// bus.with_size(0);
|
||||||
|
/// assert_eq!(1234, bus.len());
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn with_size(&mut self, size: usize) {
|
pub 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 named range (Region) to the bus
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let bus = Bus::new().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>) -> Self {
|
||||||
self.with_size(range.end);
|
self.with_size(range.end);
|
||||||
self.region.insert(name, range);
|
self.region.insert(name, range);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Loads data into a named region
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let bus = Bus::new()
|
||||||
|
/// .add_region(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]) -> Self {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
if let Some(mut region) = self.get_region_mut(name) {
|
if let Some(mut region) = self.get_region_mut(name) {
|
||||||
@ -108,34 +175,114 @@ impl Bus {
|
|||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Fills a named region with zeroes
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let bus = Bus::new()
|
||||||
|
/// .add_region(Program, 0..1234)
|
||||||
|
/// .clear_region(Program);
|
||||||
|
///# // TODO: test if region actually clear
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn clear_region(&mut self, name: Region) -> &mut Self {
|
pub fn clear_region(&mut self, name: Region) -> &mut Self {
|
||||||
if let Some(region) = self.get_region_mut(name) {
|
if let Some(region) = self.get_region_mut(name) {
|
||||||
region.fill(0)
|
region.fill(0)
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a slice of bus memory
|
/// Gets a slice of bus memory
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# 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>
|
pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[u8]>>::Output>
|
||||||
where
|
where
|
||||||
I: SliceIndex<[u8]>,
|
I: SliceIndex<[u8]>,
|
||||||
{
|
{
|
||||||
self.memory.get(index)
|
self.memory.get(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a mutable slice of bus memory
|
/// Gets a mutable slice of bus memory
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# 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>
|
pub fn get_mut<I>(&mut self, index: I) -> Option<&mut <I as SliceIndex<[u8]>>::Output>
|
||||||
where
|
where
|
||||||
I: SliceIndex<[u8]>,
|
I: SliceIndex<[u8]>,
|
||||||
{
|
{
|
||||||
self.memory.get_mut(index)
|
self.memory.get_mut(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a slice of a named region of memory
|
/// Gets a slice of a named region of memory
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let bus = Bus::new()
|
||||||
|
/// .add_region(Program, 0..10);
|
||||||
|
/// assert!([0;10].as_slice() == bus.get_region(Program).unwrap());
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn get_region(&self, name: Region) -> Option<&[u8]> {
|
pub fn get_region(&self, name: Region) -> Option<&[u8]> {
|
||||||
self.get(self.region.get(&name)?.clone())
|
self.get(self.region.get(&name)?.clone())
|
||||||
}
|
}
|
||||||
/// Gets a mutable slice to a named region of memory
|
|
||||||
|
/// Gets a mutable slice of a named region of memory
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut bus = Bus::new()
|
||||||
|
/// .add_region(Program, 0..10);
|
||||||
|
/// assert!([0;10].as_slice() == bus.get_region_mut(Program).unwrap());
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn get_region_mut(&mut self, name: Region) -> Option<&mut [u8]> {
|
pub fn get_region_mut(&mut self, name: Region) -> Option<&mut [u8]> {
|
||||||
self.get_mut(self.region.get(&name)?.clone())
|
self.get_mut(self.region.get(&name)?.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prints the region of memory called `Screen` at 1bpp using box characters
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// [Bus::print_screen] will print the screen
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let bus = Bus::new()
|
||||||
|
/// .add_region(Screen, 0x000..0x100);
|
||||||
|
/// bus.print_screen()?;
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
|
/// If there is no Screen region, it will return Err(Error::MissingRegion)
|
||||||
|
/// ```rust,should_panic
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut bus = Bus::new()
|
||||||
|
/// .add_region(Program, 0..10);
|
||||||
|
/// bus.print_screen()?;
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn print_screen(&self) -> Result<()> {
|
pub fn print_screen(&self) -> Result<()> {
|
||||||
const REGION: Region = Region::Screen;
|
const REGION: Region = Region::Screen;
|
||||||
if let Some(screen) = self.get_region(REGION) {
|
if let Some(screen) = self.get_region(REGION) {
|
||||||
@ -161,6 +308,7 @@ impl Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Read<u8> for Bus {
|
impl Read<u8> for Bus {
|
||||||
|
/// Read a u8 from address `addr`
|
||||||
fn read(&self, addr: impl Into<usize>) -> u8 {
|
fn read(&self, addr: impl Into<usize>) -> u8 {
|
||||||
let addr: usize = addr.into();
|
let addr: usize = addr.into();
|
||||||
*self.memory.get(addr).unwrap_or(&0xc5)
|
*self.memory.get(addr).unwrap_or(&0xc5)
|
||||||
@ -168,6 +316,7 @@ impl Read<u8> for Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Read<u16> for Bus {
|
impl Read<u16> for Bus {
|
||||||
|
/// Read a u16 from address `addr`
|
||||||
fn read(&self, addr: impl Into<usize>) -> u16 {
|
fn read(&self, addr: impl Into<usize>) -> u16 {
|
||||||
let addr: usize = addr.into();
|
let addr: usize = addr.into();
|
||||||
if let Some(bytes) = self.memory.get(addr..addr + 2) {
|
if let Some(bytes) = self.memory.get(addr..addr + 2) {
|
||||||
@ -179,6 +328,7 @@ impl Read<u16> for Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Write<u8> for Bus {
|
impl Write<u8> for Bus {
|
||||||
|
/// Write a u8 to address `addr`
|
||||||
fn write(&mut self, addr: impl Into<usize>, data: u8) {
|
fn write(&mut self, addr: impl Into<usize>, data: u8) {
|
||||||
let addr: usize = addr.into();
|
let addr: usize = addr.into();
|
||||||
if let Some(byte) = self.get_mut(addr) {
|
if let Some(byte) = self.get_mut(addr) {
|
||||||
@ -188,6 +338,7 @@ impl Write<u8> for Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Write<u16> for Bus {
|
impl Write<u16> for Bus {
|
||||||
|
/// Write a u16 to address `addr`
|
||||||
fn write(&mut self, addr: impl Into<usize>, data: u16) {
|
fn write(&mut self, addr: impl Into<usize>, data: u16) {
|
||||||
let addr: usize = addr.into();
|
let addr: usize = addr.into();
|
||||||
if let Some(slice) = self.get_mut(addr..addr + 2) {
|
if let Some(slice) = self.get_mut(addr..addr + 2) {
|
||||||
|
217
src/cpu.rs
217
src/cpu.rs
@ -29,7 +29,7 @@ pub struct Quirks {
|
|||||||
pub draw_wait: bool,
|
pub draw_wait: bool,
|
||||||
/// DMA instructions `Fx55`/`Fx65` should change I to I + x + 1
|
/// DMA instructions `Fx55`/`Fx65` should change I to I + x + 1
|
||||||
pub dma_inc: bool,
|
pub dma_inc: bool,
|
||||||
/// Indexed jump instructions should go to ADR + v[N] where N is high nibble of adr
|
/// Indexed jump instructions should go to ADR + v`N` where `N` is high nibble of adr
|
||||||
pub stupid_jumps: bool,
|
pub stupid_jumps: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,19 +73,16 @@ pub struct ControlFlags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ControlFlags {
|
impl ControlFlags {
|
||||||
|
/// Toggles debug mode
|
||||||
pub fn debug(&mut self) {
|
pub fn debug(&mut self) {
|
||||||
self.debug = !self.debug
|
self.debug = !self.debug
|
||||||
}
|
}
|
||||||
|
/// Toggles pause
|
||||||
pub fn pause(&mut self) {
|
pub fn pause(&mut self) {
|
||||||
self.pause = !self.pause
|
self.pause = !self.pause
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct Keys {
|
|
||||||
keys: [bool; 16],
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the internal state of the CPU interpreter
|
/// Represents the internal state of the CPU interpreter
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct CPU {
|
pub struct CPU {
|
||||||
@ -111,11 +108,21 @@ pub struct CPU {
|
|||||||
|
|
||||||
// public interface
|
// public interface
|
||||||
impl CPU {
|
impl CPU {
|
||||||
|
// TODO: implement From<&bus> for CPU
|
||||||
/// Constructs a new CPU, taking all configurable parameters
|
/// Constructs a new CPU, taking all configurable parameters
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use chirp::prelude::*;
|
/// # use chirp::prelude::*;
|
||||||
/// let mut cpu = CPU::new(0xf00, 0x50, 0x200, 0xefe, Disassemble::default(), vec![], ControlFlags::default());
|
/// let cpu = CPU::new(
|
||||||
|
/// 0xf00, // screen location
|
||||||
|
/// 0x50, // font location
|
||||||
|
/// 0x200, // start of program
|
||||||
|
/// 0xefe, // top of stack
|
||||||
|
/// Disassemble::default(),
|
||||||
|
/// vec![], // Breakpoints
|
||||||
|
/// ControlFlags::default()
|
||||||
|
/// );
|
||||||
|
/// dbg!(cpu);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new(
|
pub fn new(
|
||||||
screen: Adr,
|
screen: Adr,
|
||||||
@ -138,13 +145,36 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Press a key
|
/// Presses a key
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// cpu.press(0x7);
|
||||||
|
/// cpu.press(0xF);
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn press(&mut self, key: usize) {
|
pub fn press(&mut self, key: usize) {
|
||||||
if (0..16).contains(&key) {
|
if (0..16).contains(&key) {
|
||||||
self.keys[key] = true;
|
self.keys[key] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Release a key
|
|
||||||
|
/// Releases a key
|
||||||
|
///
|
||||||
|
/// If keypause is enabled, this disables keypause and records the last released key.
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// cpu.press(0x7);
|
||||||
|
/// cpu.release(0x7);
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn release(&mut self, key: usize) {
|
pub fn release(&mut self, key: usize) {
|
||||||
if (0..16).contains(&key) {
|
if (0..16).contains(&key) {
|
||||||
self.keys[key] = false;
|
self.keys[key] = false;
|
||||||
@ -155,7 +185,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a general purpose register in the CPU
|
/// Sets a general purpose register in the CPU
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use chirp::prelude::*;
|
/// # use chirp::prelude::*;
|
||||||
@ -171,16 +201,62 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the program counter
|
/// Gets the program counter
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// assert_eq!(0x200, cpu.pc());
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn pc(&self) -> Adr {
|
pub fn pc(&self) -> Adr {
|
||||||
self.pc
|
self.pc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the number of cycles the CPU has executed
|
||||||
|
///
|
||||||
|
/// If cpu.flags.monotonic is Some, the cycle count will be
|
||||||
|
/// updated even when the CPU is in drawpause or keypause
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// assert_eq!(0x0, cpu.cycle());
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn cycle(&self) -> usize {
|
pub fn cycle(&self) -> usize {
|
||||||
self.cycle
|
self.cycle
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Soft resets the CPU, releasing keypause and reinitializing the program counter to 0x200
|
/// Soft resets the CPU, releasing keypause and
|
||||||
|
/// reinitializing the program counter to 0x200
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::new(
|
||||||
|
/// 0xf00,
|
||||||
|
/// 0x50,
|
||||||
|
/// 0x340,
|
||||||
|
/// 0xefe,
|
||||||
|
/// Disassemble::default(),
|
||||||
|
/// vec![],
|
||||||
|
/// ControlFlags::default()
|
||||||
|
/// );
|
||||||
|
/// cpu.flags.keypause = true;
|
||||||
|
/// cpu.flags.vbi_wait = true;
|
||||||
|
/// assert_eq!(0x340, cpu.pc());
|
||||||
|
/// cpu.soft_reset();
|
||||||
|
/// assert_eq!(0x200, cpu.pc());
|
||||||
|
/// assert_eq!(false, cpu.flags.keypause);
|
||||||
|
/// assert_eq!(false, cpu.flags.vbi_wait);
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn soft_reset(&mut self) {
|
pub fn soft_reset(&mut self) {
|
||||||
self.pc = 0x200;
|
self.pc = 0x200;
|
||||||
self.flags.keypause = false;
|
self.flags.keypause = false;
|
||||||
@ -188,6 +264,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set a breakpoint
|
/// Set a breakpoint
|
||||||
|
// TODO: Unit test this
|
||||||
pub fn set_break(&mut self, point: Adr) -> &mut Self {
|
pub fn set_break(&mut self, point: Adr) -> &mut Self {
|
||||||
if !self.breakpoints.contains(&point) {
|
if !self.breakpoints.contains(&point) {
|
||||||
self.breakpoints.push(point)
|
self.breakpoints.push(point)
|
||||||
@ -196,6 +273,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Unset a breakpoint
|
/// Unset a breakpoint
|
||||||
|
// TODO: Unit test this
|
||||||
pub fn unset_break(&mut self, point: Adr) -> &mut Self {
|
pub fn unset_break(&mut self, point: Adr) -> &mut Self {
|
||||||
fn linear_find(needle: Adr, haystack: &Vec<Adr>) -> Option<usize> {
|
fn linear_find(needle: Adr, haystack: &Vec<Adr>) -> Option<usize> {
|
||||||
for (i, v) in haystack.iter().enumerate() {
|
for (i, v) in haystack.iter().enumerate() {
|
||||||
@ -211,8 +289,28 @@ impl CPU {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unpauses the emulator for a single tick
|
/// Unpauses the emulator for a single tick,
|
||||||
|
/// even if cpu.flags.pause is set.
|
||||||
|
///
|
||||||
/// NOTE: does not synchronize with delay timers
|
/// NOTE: does not synchronize with delay timers
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// let mut bus = bus!{
|
||||||
|
/// Program [0x0200..0x0f00] = &[
|
||||||
|
/// 0x00, 0xe0, // cls
|
||||||
|
/// 0x22, 0x02, // jump 0x202 (pc)
|
||||||
|
/// ],
|
||||||
|
/// Screen [0x0f00..0x1000],
|
||||||
|
/// };
|
||||||
|
/// cpu.singlestep(&mut bus);
|
||||||
|
/// assert_eq!(0x202, cpu.pc());
|
||||||
|
/// assert_eq!(1, cpu.cycle());
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn singlestep(&mut self, bus: &mut Bus) -> &mut Self {
|
pub fn singlestep(&mut self, bus: &mut Bus) -> &mut Self {
|
||||||
self.flags.pause = false;
|
self.flags.pause = false;
|
||||||
self.tick(bus);
|
self.tick(bus);
|
||||||
@ -222,7 +320,26 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Unpauses the emulator for `steps` ticks
|
/// Unpauses the emulator for `steps` ticks
|
||||||
|
///
|
||||||
/// Ticks the timers every `rate` ticks
|
/// Ticks the timers every `rate` ticks
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// let mut bus = bus!{
|
||||||
|
/// Program [0x0200..0x0f00] = &[
|
||||||
|
/// 0x00, 0xe0, // cls
|
||||||
|
/// 0x22, 0x02, // jump 0x202 (pc)
|
||||||
|
/// ],
|
||||||
|
/// Screen [0x0f00..0x1000],
|
||||||
|
/// };
|
||||||
|
/// cpu.multistep(&mut bus, 0x20);
|
||||||
|
/// assert_eq!(0x202, cpu.pc());
|
||||||
|
/// assert_eq!(0x20, cpu.cycle());
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn multistep(&mut self, bus: &mut Bus, steps: usize) -> &mut Self {
|
pub fn multistep(&mut self, bus: &mut Bus, steps: usize) -> &mut Self {
|
||||||
for _ in 0..steps {
|
for _ in 0..steps {
|
||||||
self.tick(bus);
|
self.tick(bus);
|
||||||
@ -231,10 +348,15 @@ impl CPU {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signals the start of a vertical blank
|
/// Simulates vertical blanking
|
||||||
///
|
///
|
||||||
/// - Ticks the sound and delay timers
|
/// If monotonic timing is `enabled`:
|
||||||
|
/// - Ticks the sound and delay timers according to CPU cycle count
|
||||||
/// - Disables framepause
|
/// - Disables framepause
|
||||||
|
/// If monotonic timing is `disabled`:
|
||||||
|
/// - Subtracts the elapsed time in fractions of a frame
|
||||||
|
/// from st/dt
|
||||||
|
/// - Disables framepause if the duration exceeds that of a frame
|
||||||
pub fn vertical_blank(&mut self) -> &mut Self {
|
pub fn vertical_blank(&mut self) -> &mut Self {
|
||||||
if self.flags.pause {
|
if self.flags.pause {
|
||||||
return self;
|
return self;
|
||||||
@ -263,7 +385,44 @@ impl CPU {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a single instruction
|
/// Executes a single instruction
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// let mut bus = bus!{
|
||||||
|
/// Program [0x0200..0x0f00] = &[
|
||||||
|
/// 0x00, 0xe0, // cls
|
||||||
|
/// 0x22, 0x02, // jump 0x202 (pc)
|
||||||
|
/// ],
|
||||||
|
/// Screen [0x0f00..0x1000],
|
||||||
|
/// };
|
||||||
|
/// cpu.tick(&mut bus);
|
||||||
|
/// assert_eq!(0x202, cpu.pc());
|
||||||
|
/// assert_eq!(1, cpu.cycle());
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
|
/// # Panics
|
||||||
|
/// Will panic if an invalid instruction is executed
|
||||||
|
/// ```rust,should_panic
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
///# cpu.flags.debug = true; // enable live disassembly
|
||||||
|
///# cpu.flags.monotonic = Some(8); // enable monotonic/test timing
|
||||||
|
/// let mut bus = bus!{
|
||||||
|
/// Program [0x0200..0x0f00] = &[
|
||||||
|
/// 0xff, 0xff, // invalid!
|
||||||
|
/// 0x22, 0x02, // jump 0x202 (pc)
|
||||||
|
/// ],
|
||||||
|
/// Screen [0x0f00..0x1000],
|
||||||
|
/// };
|
||||||
|
/// cpu.multistep(&mut bus, 0x10); // panics!
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn tick(&mut self, bus: &mut Bus) -> &mut Self {
|
pub fn tick(&mut self, bus: &mut Bus) -> &mut Self {
|
||||||
// Do nothing if paused
|
// Do nothing if paused
|
||||||
if self.flags.pause || self.flags.vbi_wait || self.flags.keypause {
|
if self.flags.pause || self.flags.vbi_wait || self.flags.keypause {
|
||||||
@ -418,6 +577,25 @@ impl CPU {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dumps the current state of all CPU registers, and the cycle count
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// cpu.dump();
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
|
/// outputs
|
||||||
|
/// ```text
|
||||||
|
/// PC: 0200, SP: 0efe, I: 0000
|
||||||
|
/// v0: 00 v1: 00 v2: 00 v3: 00
|
||||||
|
/// v4: 00 v5: 00 v6: 00 v7: 00
|
||||||
|
/// v8: 00 v9: 00 vA: 00 vB: 00
|
||||||
|
/// vC: 00 vD: 00 vE: 00 vF: 00
|
||||||
|
/// DLY: 0, SND: 0, CYC: 0
|
||||||
|
/// ```
|
||||||
pub fn dump(&self) {
|
pub fn dump(&self) {
|
||||||
//let dumpstyle = owo_colors::Style::new().bright_black();
|
//let dumpstyle = owo_colors::Style::new().bright_black();
|
||||||
std::println!(
|
std::println!(
|
||||||
@ -454,6 +632,13 @@ impl Default for CPU {
|
|||||||
/// | font |`0x0050` | Location of font memory.
|
/// | font |`0x0050` | Location of font memory.
|
||||||
/// | pc |`0x0200` | Start location. Generally 0x200 or 0x600.
|
/// | pc |`0x0200` | Start location. Generally 0x200 or 0x600.
|
||||||
/// | sp |`0x0efe` | Initial top of stack.
|
/// | sp |`0x0efe` | Initial top of stack.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
/// use chirp::prelude::*;
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// ```
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
CPU {
|
CPU {
|
||||||
screen: 0xf00,
|
screen: 0xf00,
|
||||||
|
Loading…
Reference in New Issue
Block a user