experimentation: benchmarking and alternate impl's
- Do some basic benchmarking with std::time - Try writing bus writer based on iterator - Fail, because that requires mutable iterator - Begin rewriting bus based on simpler design instead. - Simpler design uses a unified memory model, which grows based on the maximum addresses expected in it - Still uses the "infallible" Read/Write traits from previous implementation. :( Alas, it's much faster during operation, even if it takes longer to instantiate. - Reassessed the syntax for bus macro - Made CPU tick generic over bus::Read and bus::Write traits
This commit is contained in:
parent
2ba807d7a8
commit
ef3d765651
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -7,6 +7,7 @@ name = "chumpulator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"owo-colors",
|
||||
"rhexdump",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
@ -36,19 +37,25 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.153"
|
||||
name = "rhexdump"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20"
|
||||
checksum = "c5e9af64574935e39f24d1c0313a997c8b880ca0e087c888bc6af8af31579847"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.153"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f"
|
||||
checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -7,5 +7,6 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
owo-colors = "^3"
|
||||
rhexdump = "0.1.1"
|
||||
serde = { version = "^1.0", features = ["derive"] }
|
||||
thiserror = "1.0.39"
|
||||
|
206
src/bus.rs
206
src/bus.rs
@ -1,11 +1,15 @@
|
||||
//! The Bus connects the CPU to Memory
|
||||
mod bus_device;
|
||||
mod iterator;
|
||||
|
||||
use crate::dump::{BinDumpable, Dumpable};
|
||||
use bus_device::BusDevice;
|
||||
use iterator::BusIterator;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{Debug, Display, Formatter, Result},
|
||||
ops::Range,
|
||||
slice::SliceIndex,
|
||||
};
|
||||
|
||||
/// Creates a new bus, instantiating BusConnectable devices
|
||||
@ -27,6 +31,127 @@ macro_rules! bus {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! newbus {
|
||||
($($name:literal $(:)? [$range:expr] $(= $data:expr)?) ,* $(,)?) => {
|
||||
$crate::bus::NewBus::new()
|
||||
$(
|
||||
.add_region($name, $range)
|
||||
$(
|
||||
.load_region($name, $data)
|
||||
)?
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
/// Store memory in a series of named regions with ranges
|
||||
#[derive(Debug, Default)]
|
||||
pub struct NewBus {
|
||||
memory: Vec<u8>,
|
||||
region: HashMap<&'static str, Range<usize>>,
|
||||
}
|
||||
|
||||
impl NewBus {
|
||||
/// Construct a new bus
|
||||
pub fn new() -> Self {
|
||||
NewBus::default()
|
||||
}
|
||||
/// Gets the length of the bus' backing memory
|
||||
pub fn len(&self) -> usize {
|
||||
self.memory.len()
|
||||
}
|
||||
/// Returns true if the backing memory contains no elements
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.memory.is_empty()
|
||||
}
|
||||
/// Grows the NewBus backing memory to at least size bytes, but does not truncate
|
||||
pub fn with_size(&mut self, size: usize){
|
||||
if self.len() < size {
|
||||
self.memory.resize(size, 0);
|
||||
}
|
||||
}
|
||||
pub fn add_region(mut self, name: &'static str, range: Range<usize>) -> Self {
|
||||
self.with_size(range.end);
|
||||
self.region.insert(name, range);
|
||||
self
|
||||
}
|
||||
pub fn load_region(mut self, name: &str, data: &[u8]) -> Self {
|
||||
use std::io::Write;
|
||||
if let Some(mut region) = self.get_region_mut(name) {
|
||||
dbg!(region.write(data)).ok(); // TODO: THIS SUCKS
|
||||
}
|
||||
self
|
||||
}
|
||||
/// Gets a slice of bus memory
|
||||
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
|
||||
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
|
||||
pub fn get_region(&self, name: &str) -> Option<&[u8]> {
|
||||
self.get(self.region.get(name)?.clone())
|
||||
}
|
||||
/// Gets a mutable slice to a named region of memory
|
||||
pub fn get_region_mut(&mut self, name: &str) -> Option<&mut [u8]> {
|
||||
self.get_mut(self.region.get(name)?.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Read<u8> for NewBus {
|
||||
fn read(&self, addr: impl Into<usize>) -> u8 {
|
||||
*self.memory.get(addr.into()).unwrap_or(&0xc5)
|
||||
}
|
||||
}
|
||||
|
||||
impl Read<u16> for NewBus {
|
||||
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("asked for 2 bytes, got != 2 bytes"))
|
||||
} else {
|
||||
0xc5c5
|
||||
}
|
||||
//u16::from_le_bytes(self.memory.get([addr;2]))
|
||||
}
|
||||
}
|
||||
|
||||
impl Write<u8> for NewBus {
|
||||
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 NewBus {
|
||||
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) {
|
||||
slice.swap_with_slice(data.to_be_bytes().as_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NewBus {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
use rhexdump::Rhexdump;
|
||||
let mut rhx = Rhexdump::default();
|
||||
rhx.set_bytes_per_group(2).expect("2 <= MAX_BYTES_PER_GROUP (8)");
|
||||
rhx.display_duplicate_lines(false);
|
||||
write!(f, "{}", rhx.hexdump(&self.memory))
|
||||
}
|
||||
}
|
||||
|
||||
/// BusConnectable objects can be connected to a bus with `Bus::connect()`
|
||||
///
|
||||
/// The bus performs address translation, so your object will receive
|
||||
@ -34,19 +159,20 @@ macro_rules! bus {
|
||||
pub trait BusConnectible: Debug + Display {
|
||||
fn read_at(&self, addr: u16) -> Option<u8>;
|
||||
fn write_to(&mut self, addr: u16, data: u8);
|
||||
fn get_mut(&mut self, addr: u16) -> Option<&mut u8>;
|
||||
}
|
||||
|
||||
// Traits Read and Write are here purely to make implementing other things more bearable
|
||||
/// Do whatever `Read` means to you
|
||||
pub trait Read<T> {
|
||||
/// Read a T from address `addr`
|
||||
fn read(&self, addr: u16) -> T;
|
||||
fn read(&self, addr: impl Into<usize>) -> T;
|
||||
}
|
||||
|
||||
/// Write "some data" to the Bus
|
||||
pub trait Write<T> {
|
||||
/// Write a T to address `addr`
|
||||
fn write(&mut self, addr: u16, data: T);
|
||||
fn write(&mut self, addr: impl Into<usize>, data: T);
|
||||
}
|
||||
|
||||
/// The Bus connects bus readers with bus writers.
|
||||
@ -71,28 +197,39 @@ impl Bus {
|
||||
self.devices.push(BusDevice::new(name, range, device));
|
||||
self
|
||||
}
|
||||
pub fn get_region_by_name(&self, name: &str) -> Option<Range<u16>> {
|
||||
for item in &self.devices {
|
||||
if item.name == name {
|
||||
return Some(item.range.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
pub fn get_region_by_name(&mut self, name: &str) -> Option<&mut BusDevice> {
|
||||
self.devices.iter_mut().find(|item| item.name == name)
|
||||
}
|
||||
}
|
||||
|
||||
/// lmao
|
||||
impl BusConnectible for Bus {
|
||||
fn read_at(&self, addr: u16) -> Option<u8> {
|
||||
Some(self.read(addr))
|
||||
let mut result: u8 = 0;
|
||||
for item in &self.devices {
|
||||
result |= item.read_at(addr).unwrap_or(0)
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
fn write_to(&mut self, addr: u16, data: u8) {
|
||||
self.write(addr, data)
|
||||
for item in &mut self.devices {
|
||||
item.write_to(addr, data)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, addr: u16) -> Option<&mut u8> {
|
||||
for item in &mut self.devices {
|
||||
if let Some(mutable) = item.get_mut(addr) {
|
||||
return Some(mutable);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Read<u8> for Bus {
|
||||
fn read(&self, addr: u16) -> u8 {
|
||||
fn read(&self, addr: impl Into<usize>) -> u8 {
|
||||
let addr = addr.into() as u16;
|
||||
let mut result: u8 = 0;
|
||||
for item in &self.devices {
|
||||
result |= item.read_at(addr).unwrap_or(0)
|
||||
@ -102,18 +239,18 @@ impl Read<u8> for Bus {
|
||||
}
|
||||
|
||||
impl Read<u16> for Bus {
|
||||
fn read(&self, addr: u16) -> u16 {
|
||||
fn read(&self, addr: impl Into<usize>) -> u16 {
|
||||
let addr = addr.into() as u16;
|
||||
let mut result = 0;
|
||||
for item in &self.devices {
|
||||
result |= (item.read_at(addr).unwrap_or(0) as u16) << 8;
|
||||
result |= item.read_at(addr.wrapping_add(1)).unwrap_or(0) as u16;
|
||||
}
|
||||
result |= (self.read_at(addr).unwrap_or(0) as u16) << 8;
|
||||
result |= self.read_at(addr.wrapping_add(1)).unwrap_or(0) as u16;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Write<u8> for Bus {
|
||||
fn write(&mut self, addr: u16, data: u8) {
|
||||
fn write(&mut self, addr: impl Into<usize>, data: u8) {
|
||||
let addr = addr.into() as u16;
|
||||
for item in &mut self.devices {
|
||||
item.write_to(addr, data)
|
||||
}
|
||||
@ -121,10 +258,18 @@ impl Write<u8> for Bus {
|
||||
}
|
||||
|
||||
impl Write<u16> for Bus {
|
||||
fn write(&mut self, addr: u16, data: u16) {
|
||||
for item in &mut self.devices {
|
||||
item.write_to(addr, (data >> 8) as u8);
|
||||
item.write_to(addr.wrapping_add(1), data as u8);
|
||||
fn write(&mut self, addr: impl Into<usize>, data: u16) {
|
||||
let addr = addr.into() as u16;
|
||||
self.write_to(addr, (data >> 8) as u8);
|
||||
self.write_to(addr.wrapping_add(1), data as u8);
|
||||
}
|
||||
}
|
||||
|
||||
impl Write<u32> for Bus {
|
||||
fn write(&mut self, addr: impl Into<usize>, data: u32) {
|
||||
let addr = addr.into() as u16;
|
||||
for i in 0..4 {
|
||||
self.write_to(addr.wrapping_add(i), (data >> (3 - i * 8)) as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -140,8 +285,11 @@ impl Display for Bus {
|
||||
|
||||
impl Dumpable for Bus {
|
||||
fn dump(&self, range: Range<usize>) {
|
||||
for index in range {
|
||||
let byte: u8 = self.read(index as u16);
|
||||
for (index, byte) in self
|
||||
.into_iter()
|
||||
.range(range.start as u16..range.end as u16) // this causes a truncation
|
||||
.enumerate()
|
||||
{
|
||||
crate::dump::as_hexdump(index, byte);
|
||||
}
|
||||
}
|
||||
@ -155,3 +303,13 @@ impl BinDumpable for Bus {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Bus {
|
||||
type Item = u8;
|
||||
|
||||
type IntoIter = iterator::BusIterator<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
BusIterator::new(0..u16::MAX, self)
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ impl BusConnectible for BusDevice {
|
||||
self.device.write_to(addr, data);
|
||||
}
|
||||
}
|
||||
fn get_mut(&mut self, addr: u16) -> Option<&mut u8> {
|
||||
return self.device.get_mut(addr);
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BusDevice {
|
||||
|
52
src/bus/iterator.rs
Normal file
52
src/bus/iterator.rs
Normal file
@ -0,0 +1,52 @@
|
||||
//! Iterators for working with Busses
|
||||
|
||||
use super::{Bus, Read};
|
||||
use std::ops::Range;
|
||||
|
||||
pub trait IterMut<'a> {
|
||||
type Item;
|
||||
fn next(&'a mut self) -> Option<&'a mut Self::Item>;
|
||||
}
|
||||
|
||||
pub trait IntoIterMut<'a> {
|
||||
type Item;
|
||||
|
||||
type IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BusIterator<'a> {
|
||||
range: Range<u16>,
|
||||
addr: u16,
|
||||
bus: &'a Bus,
|
||||
}
|
||||
|
||||
impl<'a> BusIterator<'a> {
|
||||
/// Creates a new BusIterator with a specified range
|
||||
pub fn new(range: Range<u16>, bus: &'a Bus) -> BusIterator<'a> {
|
||||
BusIterator {
|
||||
addr: range.start,
|
||||
range,
|
||||
bus,
|
||||
}
|
||||
}
|
||||
pub fn range(mut self, range: Range<u16>) -> Self {
|
||||
self.range = range;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BusIterator<'a> {
|
||||
type Item = u8;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut res = None;
|
||||
if self.range.contains(&self.addr) {
|
||||
res = Some(self.bus.read(self.addr));
|
||||
self.addr += 1;
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
62
src/cpu.rs
62
src/cpu.rs
@ -3,7 +3,7 @@
|
||||
pub mod disassemble;
|
||||
|
||||
use self::disassemble::Disassemble;
|
||||
use crate::bus::{Bus, Read, Write};
|
||||
use crate::bus::{Read, Write};
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
type Reg = usize;
|
||||
@ -116,7 +116,10 @@ impl CPU {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, bus: &mut Bus) {
|
||||
pub fn tick<B>(&mut self, bus: &mut B)
|
||||
where
|
||||
B: Read<u8> + Write<u8> + Read<u16> + Write<u16>
|
||||
{
|
||||
std::print!("{:3} {:03x}: ", self.cycle.bright_black(), self.pc);
|
||||
// fetch opcode
|
||||
let opcode: u16 = bus.read(self.pc);
|
||||
@ -205,7 +208,7 @@ impl CPU {
|
||||
// Cxbb: Stores a random number + the provided byte into vX
|
||||
0xc => self.rand(x, b),
|
||||
// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
||||
0xd => self.draw(x, y, n),
|
||||
0xd => self.draw(x, y, n, bus),
|
||||
|
||||
// # Skips instruction on value of keypress
|
||||
// |opcode| effect |
|
||||
@ -231,12 +234,12 @@ impl CPU {
|
||||
// | fX55 | DMA Stor from I to registers 0..X |
|
||||
// | fX65 | DMA Load from I to registers 0..X |
|
||||
0xf => match b {
|
||||
0x07 => self.get_delay_timer(x, bus),
|
||||
0x0A => self.wait_for_key(x, bus),
|
||||
0x15 => self.load_delay_timer(x, bus),
|
||||
0x18 => self.load_sound_timer(x, bus),
|
||||
0x1E => self.add_to_indirect(x, bus),
|
||||
0x29 => self.load_sprite_x(x, bus),
|
||||
0x07 => self.get_delay_timer(x),
|
||||
0x0A => self.wait_for_key(x),
|
||||
0x15 => self.load_delay_timer(x),
|
||||
0x18 => self.load_sound_timer(x),
|
||||
0x1E => self.add_to_indirect(x),
|
||||
0x29 => self.load_sprite_x(x),
|
||||
0x33 => self.bcd_convert_i(x, bus),
|
||||
0x55 => self.dma_store(x, bus),
|
||||
0x65 => self.dma_load(x, bus),
|
||||
@ -290,7 +293,7 @@ impl CPU {
|
||||
}
|
||||
/// 00e0: Clears the screen memory to 0
|
||||
#[inline]
|
||||
fn clear_screen(&mut self, bus: &mut Bus) {
|
||||
fn clear_screen(&mut self, bus: &mut impl Write<u8>) {
|
||||
for addr in self.screen..self.screen + 0x100 {
|
||||
bus.write(addr, 0u8);
|
||||
}
|
||||
@ -299,7 +302,7 @@ impl CPU {
|
||||
}
|
||||
/// 00ee: Returns from subroutine
|
||||
#[inline]
|
||||
fn ret(&mut self, bus: &mut Bus) {
|
||||
fn ret(&mut self, bus: &impl Read<u16>) {
|
||||
self.sp = self.sp.wrapping_add(2);
|
||||
self.pc = bus.read(self.sp);
|
||||
}
|
||||
@ -310,7 +313,7 @@ impl CPU {
|
||||
}
|
||||
/// 2aaa: Pushes pc onto the stack, then jumps to a
|
||||
#[inline]
|
||||
fn call(&mut self, a: Adr, bus: &mut Bus) {
|
||||
fn call(&mut self, a: Adr, bus: &mut impl Write<u16>) {
|
||||
bus.write(self.sp, self.pc);
|
||||
self.sp = self.sp.wrapping_sub(2);
|
||||
self.pc = a;
|
||||
@ -427,12 +430,23 @@ impl CPU {
|
||||
}
|
||||
/// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
||||
#[inline]
|
||||
fn draw(&mut self, x: Reg, y: Reg, n: Nib) {
|
||||
// TODO: Screen
|
||||
todo!("{}", format_args!("draw\t#{n:x}, v{x:x}, v{y:x}").red());
|
||||
fn draw<I>(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut I)
|
||||
where
|
||||
I: Read<u8> + Read<u16>
|
||||
{
|
||||
println!("{}", format_args!("draw\t#{n:x}, v{x:x}, v{y:x}").red());
|
||||
self.v[0xf] = 0;
|
||||
// TODO: Repeat for all N
|
||||
for byte in 0..n as u16 {
|
||||
// TODO: Calculate the lower bound address based on the X,Y position on the screen
|
||||
let lower_bound = ((y as u16 + byte) * 8) + x as u16 / 8;
|
||||
// TODO: Read a byte of sprite data into a u16, and shift it x % 8 bits
|
||||
let sprite_line: u8 = bus.read(self.i);
|
||||
// TODO: Read a u16 from the bus containing the two bytes which might need to be updated
|
||||
let screen_word: u16 = bus.read(self.screen + lower_bound);
|
||||
// TODO: Update the screen word by XORing the sprite byte
|
||||
todo!("{sprite_line}, {screen_word}")
|
||||
}
|
||||
}
|
||||
/// Ex9E: Skip next instruction if key == #X
|
||||
#[inline]
|
||||
@ -457,12 +471,12 @@ impl CPU {
|
||||
/// vX = DT
|
||||
/// ```
|
||||
#[inline]
|
||||
fn get_delay_timer(&mut self, x: Reg, _bus: &mut Bus) {
|
||||
fn get_delay_timer(&mut self, x: Reg) {
|
||||
self.v[x] = self.delay;
|
||||
}
|
||||
/// Fx0A: Wait for key, then vX = K
|
||||
#[inline]
|
||||
fn wait_for_key(&mut self, x: Reg, _bus: &mut Bus) {
|
||||
fn wait_for_key(&mut self, x: Reg) {
|
||||
// TODO: I/O
|
||||
|
||||
std::println!("{}", format_args!("waitk\tv{x:x}").red());
|
||||
@ -472,7 +486,7 @@ impl CPU {
|
||||
/// DT = vX
|
||||
/// ```
|
||||
#[inline]
|
||||
fn load_delay_timer(&mut self, x: Reg, _bus: &mut Bus) {
|
||||
fn load_delay_timer(&mut self, x: Reg) {
|
||||
self.delay = self.v[x];
|
||||
}
|
||||
/// Fx18: Load vX into ST
|
||||
@ -480,7 +494,7 @@ impl CPU {
|
||||
/// ST = vX;
|
||||
/// ```
|
||||
#[inline]
|
||||
fn load_sound_timer(&mut self, x: Reg, _bus: &mut Bus) {
|
||||
fn load_sound_timer(&mut self, x: Reg) {
|
||||
self.sound = self.v[x];
|
||||
}
|
||||
/// Fx1e: Add vX to I,
|
||||
@ -488,7 +502,7 @@ impl CPU {
|
||||
/// I += vX;
|
||||
/// ```
|
||||
#[inline]
|
||||
fn add_to_indirect(&mut self, x: Reg, _bus: &mut Bus) {
|
||||
fn add_to_indirect(&mut self, x: Reg) {
|
||||
self.i += self.v[x] as u16;
|
||||
}
|
||||
/// Fx29: Load sprite for character x into I
|
||||
@ -496,19 +510,19 @@ impl CPU {
|
||||
/// I = sprite(X);
|
||||
/// ```
|
||||
#[inline]
|
||||
fn load_sprite_x(&mut self, x: Reg, _bus: &mut Bus) {
|
||||
fn load_sprite_x(&mut self, x: Reg) {
|
||||
self.i = self.font + (5 * x as Adr);
|
||||
}
|
||||
/// Fx33: BCD convert X into I`[0..3]`
|
||||
#[inline]
|
||||
fn bcd_convert_i(&mut self, x: Reg, _bus: &mut Bus) {
|
||||
fn bcd_convert_i(&mut self, x: Reg, _bus: &mut impl Write<u8>) {
|
||||
// TODO: I/O
|
||||
|
||||
std::println!("{}", format_args!("bcd\t{x:x}, &I").red());
|
||||
}
|
||||
/// Fx55: DMA Stor from I to registers 0..X
|
||||
#[inline]
|
||||
fn dma_store(&mut self, x: Reg, bus: &mut Bus) {
|
||||
fn dma_store(&mut self, x: Reg, bus: &mut impl Write<u8>) {
|
||||
for reg in 0..=x {
|
||||
bus.write(self.i + reg as u16, self.v[reg]);
|
||||
}
|
||||
@ -516,7 +530,7 @@ impl CPU {
|
||||
}
|
||||
/// Fx65: DMA Load from I to registers 0..X
|
||||
#[inline]
|
||||
fn dma_load(&mut self, x: Reg, bus: &mut Bus) {
|
||||
fn dma_load(&mut self, x: Reg, bus: &mut impl Read<u8>) {
|
||||
for reg in 0..=x {
|
||||
self.v[reg] = bus.read(self.i + reg as u16);
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ pub mod screen;
|
||||
pub mod prelude {
|
||||
use super::*;
|
||||
pub use crate::bus;
|
||||
pub use bus::{Bus, BusConnectible};
|
||||
pub use crate::newbus;
|
||||
pub use bus::{Bus, BusConnectible, Read, Write};
|
||||
pub use cpu::{disassemble::Disassemble, CPU};
|
||||
pub use dump::{BinDumpable, Dumpable};
|
||||
pub use mem::Mem;
|
||||
|
50
src/main.rs
50
src/main.rs
@ -1,7 +1,15 @@
|
||||
use chumpulator::{bus::Read, prelude::*};
|
||||
use std::fs::read;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// What I want:
|
||||
/// I want a data bus that stores as much memory as I need to implement a chip 8 emulator
|
||||
/// I want that data bus to hold named memory ranges and have a way to get a memory region
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
let mut now;
|
||||
println!("Building Bus...");
|
||||
let mut time = Instant::now();
|
||||
let mut bus = bus! {
|
||||
// Load the charset into ROM
|
||||
"charset" [0x0050..0x00a0] = Mem::new(0x50).load_charset(0).w(false),
|
||||
@ -12,20 +20,52 @@ fn main() -> Result<(), std::io::Error> {
|
||||
// Create some stack memory
|
||||
"stack" [0xF000..0xF800] = Mem::new(0x800).r(true).w(true),
|
||||
};
|
||||
|
||||
println!("{bus}");
|
||||
now = time.elapsed();
|
||||
println!("Elapsed: {:?}\nBuilding NewBus...", now);
|
||||
time = Instant::now();
|
||||
let mut newbus = newbus! {
|
||||
// Load the charset into ROM
|
||||
"charset" [0x0050..0x00a0] = include_bytes!("mem/charset.bin"),
|
||||
// Load the ROM file into RAM
|
||||
"userram" [0x0200..0x0F00] = &read("chip-8/Fishie.ch8")?,
|
||||
// Create a screen
|
||||
"screen" [0x0F00..0x1000],
|
||||
// Create some stack memory
|
||||
"stack" [0x2000..0x2800],
|
||||
};
|
||||
now = time.elapsed();
|
||||
println!("Elapsed: {:?}", now);
|
||||
println!("{newbus}");
|
||||
|
||||
let disassembler = Disassemble::default();
|
||||
if false {
|
||||
for addr in 0x200..0x290 {
|
||||
if addr % 2 == 0 {
|
||||
println!("{addr:03x}: {}", disassembler.instruction(bus.read(addr)));
|
||||
println!(
|
||||
"{addr:03x}: {}",
|
||||
disassembler.instruction(bus.read(addr as usize))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut cpu = CPU::new(0xf00, 0x50, 0x200, 0xf7fe, disassembler);
|
||||
for _instruction in 0..100 {
|
||||
let mut cpu2 = cpu.clone();
|
||||
println!("Old Bus:");
|
||||
for _instruction in 0..6 {
|
||||
time = Instant::now();
|
||||
cpu.tick(&mut bus);
|
||||
//bus.dump(0xF7e0..0xf800);
|
||||
now = time.elapsed();
|
||||
println!(" Elapsed: {:?}", now);
|
||||
std::thread::sleep(Duration::from_micros(2000).saturating_sub(time.elapsed()));
|
||||
}
|
||||
println!("New Bus:");
|
||||
for _instruction in 0..6 {
|
||||
time = Instant::now();
|
||||
cpu2.tick(&mut newbus);
|
||||
now = time.elapsed();
|
||||
println!(" Elapsed: {:?}", now);
|
||||
std::thread::sleep(Duration::from_micros(2000).saturating_sub(time.elapsed()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -144,4 +144,10 @@ impl BusConnectible for Mem {
|
||||
*value = data
|
||||
}
|
||||
}
|
||||
fn get_mut(&mut self, addr: u16) -> Option<&mut u8> {
|
||||
if !self.attr.w {
|
||||
return None;
|
||||
}
|
||||
self.mem.get_mut(addr as usize)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
//! Stores and displays the Chip-8's screen memory
|
||||
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::{bus::BusConnectible, dump::Dumpable, mem::Mem};
|
||||
use std::{
|
||||
fmt::{Display, Formatter, Result},
|
||||
@ -8,39 +10,21 @@ use std::{
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Screen {
|
||||
mem: Mem,
|
||||
width: usize,
|
||||
height: usize,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
pub fn new(width: usize, height: usize) -> Screen {
|
||||
Screen { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Screen {
|
||||
fn default() -> Self {
|
||||
Screen {
|
||||
mem: Mem::new(width * height / 8),
|
||||
width,
|
||||
height,
|
||||
width: 64,
|
||||
height: 32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BusConnectible for Screen {
|
||||
fn read_at(&self, addr: u16) -> Option<u8> {
|
||||
self.mem.read_at(addr)
|
||||
}
|
||||
|
||||
fn write_to(&mut self, addr: u16, data: u8) {
|
||||
self.mem.write_to(addr, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dumpable for Screen {
|
||||
fn dump(&self, range: Range<usize>) {
|
||||
self.mem.dump(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Screen {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
write!(f, "{}", self.mem.window(0..self.width * self.height / 8))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user