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"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
|
"rhexdump",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
@ -36,19 +37,25 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "rhexdump"
|
||||||
version = "1.0.153"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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 = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.153"
|
version = "1.0.154"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f"
|
checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -7,5 +7,6 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
owo-colors = "^3"
|
owo-colors = "^3"
|
||||||
|
rhexdump = "0.1.1"
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
thiserror = "1.0.39"
|
thiserror = "1.0.39"
|
||||||
|
206
src/bus.rs
206
src/bus.rs
@ -1,11 +1,15 @@
|
|||||||
//! The Bus connects the CPU to Memory
|
//! The Bus connects the CPU to Memory
|
||||||
mod bus_device;
|
mod bus_device;
|
||||||
|
mod iterator;
|
||||||
|
|
||||||
use crate::dump::{BinDumpable, Dumpable};
|
use crate::dump::{BinDumpable, Dumpable};
|
||||||
use bus_device::BusDevice;
|
use bus_device::BusDevice;
|
||||||
|
use iterator::BusIterator;
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
fmt::{Debug, Display, Formatter, Result},
|
fmt::{Debug, Display, Formatter, Result},
|
||||||
ops::Range,
|
ops::Range,
|
||||||
|
slice::SliceIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Creates a new bus, instantiating BusConnectable devices
|
/// 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()`
|
/// BusConnectable objects can be connected to a bus with `Bus::connect()`
|
||||||
///
|
///
|
||||||
/// The bus performs address translation, so your object will receive
|
/// The bus performs address translation, so your object will receive
|
||||||
@ -34,19 +159,20 @@ macro_rules! bus {
|
|||||||
pub trait BusConnectible: Debug + Display {
|
pub trait BusConnectible: Debug + Display {
|
||||||
fn read_at(&self, addr: u16) -> Option<u8>;
|
fn read_at(&self, addr: u16) -> Option<u8>;
|
||||||
fn write_to(&mut self, addr: u16, data: 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
|
// Traits Read and Write are here purely to make implementing other things more bearable
|
||||||
/// Do whatever `Read` means to you
|
/// Do whatever `Read` means to you
|
||||||
pub trait Read<T> {
|
pub trait Read<T> {
|
||||||
/// Read a T from address `addr`
|
/// 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
|
/// Write "some data" to the Bus
|
||||||
pub trait Write<T> {
|
pub trait Write<T> {
|
||||||
/// Write a T to address `addr`
|
/// 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.
|
/// The Bus connects bus readers with bus writers.
|
||||||
@ -71,28 +197,39 @@ impl Bus {
|
|||||||
self.devices.push(BusDevice::new(name, range, device));
|
self.devices.push(BusDevice::new(name, range, device));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn get_region_by_name(&self, name: &str) -> Option<Range<u16>> {
|
pub fn get_region_by_name(&mut self, name: &str) -> Option<&mut BusDevice> {
|
||||||
for item in &self.devices {
|
self.devices.iter_mut().find(|item| item.name == name)
|
||||||
if item.name == name {
|
|
||||||
return Some(item.range.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// lmao
|
/// lmao
|
||||||
impl BusConnectible for Bus {
|
impl BusConnectible for Bus {
|
||||||
fn read_at(&self, addr: u16) -> Option<u8> {
|
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) {
|
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 {
|
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;
|
let mut result: u8 = 0;
|
||||||
for item in &self.devices {
|
for item in &self.devices {
|
||||||
result |= item.read_at(addr).unwrap_or(0)
|
result |= item.read_at(addr).unwrap_or(0)
|
||||||
@ -102,18 +239,18 @@ impl Read<u8> for Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Read<u16> 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;
|
let mut result = 0;
|
||||||
for item in &self.devices {
|
result |= (self.read_at(addr).unwrap_or(0) as u16) << 8;
|
||||||
result |= (item.read_at(addr).unwrap_or(0) as u16) << 8;
|
result |= self.read_at(addr.wrapping_add(1)).unwrap_or(0) as u16;
|
||||||
result |= item.read_at(addr.wrapping_add(1)).unwrap_or(0) as u16;
|
|
||||||
}
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write<u8> for Bus {
|
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 {
|
for item in &mut self.devices {
|
||||||
item.write_to(addr, data)
|
item.write_to(addr, data)
|
||||||
}
|
}
|
||||||
@ -121,10 +258,18 @@ impl Write<u8> for Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Write<u16> for Bus {
|
impl Write<u16> for Bus {
|
||||||
fn write(&mut self, addr: u16, data: u16) {
|
fn write(&mut self, addr: impl Into<usize>, data: u16) {
|
||||||
for item in &mut self.devices {
|
let addr = addr.into() as u16;
|
||||||
item.write_to(addr, (data >> 8) as u8);
|
self.write_to(addr, (data >> 8) as u8);
|
||||||
item.write_to(addr.wrapping_add(1), data 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 {
|
impl Dumpable for Bus {
|
||||||
fn dump(&self, range: Range<usize>) {
|
fn dump(&self, range: Range<usize>) {
|
||||||
for index in range {
|
for (index, byte) in self
|
||||||
let byte: u8 = self.read(index as u16);
|
.into_iter()
|
||||||
|
.range(range.start as u16..range.end as u16) // this causes a truncation
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
crate::dump::as_hexdump(index, byte);
|
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);
|
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 {
|
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;
|
pub mod disassemble;
|
||||||
|
|
||||||
use self::disassemble::Disassemble;
|
use self::disassemble::Disassemble;
|
||||||
use crate::bus::{Bus, Read, Write};
|
use crate::bus::{Read, Write};
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
|
|
||||||
type Reg = usize;
|
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);
|
std::print!("{:3} {:03x}: ", self.cycle.bright_black(), self.pc);
|
||||||
// fetch opcode
|
// fetch opcode
|
||||||
let opcode: u16 = bus.read(self.pc);
|
let opcode: u16 = bus.read(self.pc);
|
||||||
@ -205,7 +208,7 @@ impl CPU {
|
|||||||
// Cxbb: Stores a random number + the provided byte into vX
|
// Cxbb: Stores a random number + the provided byte into vX
|
||||||
0xc => self.rand(x, b),
|
0xc => self.rand(x, b),
|
||||||
// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
// 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
|
// # Skips instruction on value of keypress
|
||||||
// |opcode| effect |
|
// |opcode| effect |
|
||||||
@ -231,12 +234,12 @@ impl CPU {
|
|||||||
// | fX55 | DMA Stor from I to registers 0..X |
|
// | fX55 | DMA Stor from I to registers 0..X |
|
||||||
// | fX65 | DMA Load from I to registers 0..X |
|
// | fX65 | DMA Load from I to registers 0..X |
|
||||||
0xf => match b {
|
0xf => match b {
|
||||||
0x07 => self.get_delay_timer(x, bus),
|
0x07 => self.get_delay_timer(x),
|
||||||
0x0A => self.wait_for_key(x, bus),
|
0x0A => self.wait_for_key(x),
|
||||||
0x15 => self.load_delay_timer(x, bus),
|
0x15 => self.load_delay_timer(x),
|
||||||
0x18 => self.load_sound_timer(x, bus),
|
0x18 => self.load_sound_timer(x),
|
||||||
0x1E => self.add_to_indirect(x, bus),
|
0x1E => self.add_to_indirect(x),
|
||||||
0x29 => self.load_sprite_x(x, bus),
|
0x29 => self.load_sprite_x(x),
|
||||||
0x33 => self.bcd_convert_i(x, bus),
|
0x33 => self.bcd_convert_i(x, bus),
|
||||||
0x55 => self.dma_store(x, bus),
|
0x55 => self.dma_store(x, bus),
|
||||||
0x65 => self.dma_load(x, bus),
|
0x65 => self.dma_load(x, bus),
|
||||||
@ -290,7 +293,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
/// 00e0: Clears the screen memory to 0
|
/// 00e0: Clears the screen memory to 0
|
||||||
#[inline]
|
#[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 {
|
for addr in self.screen..self.screen + 0x100 {
|
||||||
bus.write(addr, 0u8);
|
bus.write(addr, 0u8);
|
||||||
}
|
}
|
||||||
@ -299,7 +302,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
/// 00ee: Returns from subroutine
|
/// 00ee: Returns from subroutine
|
||||||
#[inline]
|
#[inline]
|
||||||
fn ret(&mut self, bus: &mut Bus) {
|
fn ret(&mut self, bus: &impl Read<u16>) {
|
||||||
self.sp = self.sp.wrapping_add(2);
|
self.sp = self.sp.wrapping_add(2);
|
||||||
self.pc = bus.read(self.sp);
|
self.pc = bus.read(self.sp);
|
||||||
}
|
}
|
||||||
@ -310,7 +313,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
/// 2aaa: Pushes pc onto the stack, then jumps to a
|
/// 2aaa: Pushes pc onto the stack, then jumps to a
|
||||||
#[inline]
|
#[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);
|
bus.write(self.sp, self.pc);
|
||||||
self.sp = self.sp.wrapping_sub(2);
|
self.sp = self.sp.wrapping_sub(2);
|
||||||
self.pc = a;
|
self.pc = a;
|
||||||
@ -427,12 +430,23 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
/// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
/// Dxyn: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
||||||
#[inline]
|
#[inline]
|
||||||
fn draw(&mut self, x: Reg, y: Reg, n: Nib) {
|
fn draw<I>(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut I)
|
||||||
// TODO: Screen
|
where
|
||||||
todo!("{}", format_args!("draw\t#{n:x}, v{x:x}, v{y:x}").red());
|
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
|
// 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
|
// 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
|
// 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
|
/// Ex9E: Skip next instruction if key == #X
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -457,12 +471,12 @@ impl CPU {
|
|||||||
/// vX = DT
|
/// vX = DT
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[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;
|
self.v[x] = self.delay;
|
||||||
}
|
}
|
||||||
/// Fx0A: Wait for key, then vX = K
|
/// Fx0A: Wait for key, then vX = K
|
||||||
#[inline]
|
#[inline]
|
||||||
fn wait_for_key(&mut self, x: Reg, _bus: &mut Bus) {
|
fn wait_for_key(&mut self, x: Reg) {
|
||||||
// TODO: I/O
|
// TODO: I/O
|
||||||
|
|
||||||
std::println!("{}", format_args!("waitk\tv{x:x}").red());
|
std::println!("{}", format_args!("waitk\tv{x:x}").red());
|
||||||
@ -472,7 +486,7 @@ impl CPU {
|
|||||||
/// DT = vX
|
/// DT = vX
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[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];
|
self.delay = self.v[x];
|
||||||
}
|
}
|
||||||
/// Fx18: Load vX into ST
|
/// Fx18: Load vX into ST
|
||||||
@ -480,7 +494,7 @@ impl CPU {
|
|||||||
/// ST = vX;
|
/// ST = vX;
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[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];
|
self.sound = self.v[x];
|
||||||
}
|
}
|
||||||
/// Fx1e: Add vX to I,
|
/// Fx1e: Add vX to I,
|
||||||
@ -488,7 +502,7 @@ impl CPU {
|
|||||||
/// I += vX;
|
/// I += vX;
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[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;
|
self.i += self.v[x] as u16;
|
||||||
}
|
}
|
||||||
/// Fx29: Load sprite for character x into I
|
/// Fx29: Load sprite for character x into I
|
||||||
@ -496,19 +510,19 @@ impl CPU {
|
|||||||
/// I = sprite(X);
|
/// I = sprite(X);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[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);
|
self.i = self.font + (5 * x as Adr);
|
||||||
}
|
}
|
||||||
/// Fx33: BCD convert X into I`[0..3]`
|
/// Fx33: BCD convert X into I`[0..3]`
|
||||||
#[inline]
|
#[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
|
// TODO: I/O
|
||||||
|
|
||||||
std::println!("{}", format_args!("bcd\t{x:x}, &I").red());
|
std::println!("{}", format_args!("bcd\t{x:x}, &I").red());
|
||||||
}
|
}
|
||||||
/// Fx55: DMA Stor from I to registers 0..X
|
/// Fx55: DMA Stor from I to registers 0..X
|
||||||
#[inline]
|
#[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 {
|
for reg in 0..=x {
|
||||||
bus.write(self.i + reg as u16, self.v[reg]);
|
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
|
/// Fx65: DMA Load from I to registers 0..X
|
||||||
#[inline]
|
#[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 {
|
for reg in 0..=x {
|
||||||
self.v[reg] = bus.read(self.i + reg as u16);
|
self.v[reg] = bus.read(self.i + reg as u16);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,8 @@ pub mod screen;
|
|||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
use super::*;
|
use super::*;
|
||||||
pub use crate::bus;
|
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 cpu::{disassemble::Disassemble, CPU};
|
||||||
pub use dump::{BinDumpable, Dumpable};
|
pub use dump::{BinDumpable, Dumpable};
|
||||||
pub use mem::Mem;
|
pub use mem::Mem;
|
||||||
|
50
src/main.rs
50
src/main.rs
@ -1,7 +1,15 @@
|
|||||||
use chumpulator::{bus::Read, prelude::*};
|
use chumpulator::{bus::Read, prelude::*};
|
||||||
use std::fs::read;
|
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> {
|
fn main() -> Result<(), std::io::Error> {
|
||||||
|
let mut now;
|
||||||
|
println!("Building Bus...");
|
||||||
|
let mut time = Instant::now();
|
||||||
let mut bus = bus! {
|
let mut bus = bus! {
|
||||||
// Load the charset into ROM
|
// Load the charset into ROM
|
||||||
"charset" [0x0050..0x00a0] = Mem::new(0x50).load_charset(0).w(false),
|
"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
|
// Create some stack memory
|
||||||
"stack" [0xF000..0xF800] = Mem::new(0x800).r(true).w(true),
|
"stack" [0xF000..0xF800] = Mem::new(0x800).r(true).w(true),
|
||||||
};
|
};
|
||||||
|
now = time.elapsed();
|
||||||
println!("{bus}");
|
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();
|
let disassembler = Disassemble::default();
|
||||||
|
if false {
|
||||||
for addr in 0x200..0x290 {
|
for addr in 0x200..0x290 {
|
||||||
if addr % 2 == 0 {
|
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);
|
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);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -144,4 +144,10 @@ impl BusConnectible for Mem {
|
|||||||
*value = data
|
*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
|
//! Stores and displays the Chip-8's screen memory
|
||||||
|
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
use crate::{bus::BusConnectible, dump::Dumpable, mem::Mem};
|
use crate::{bus::BusConnectible, dump::Dumpable, mem::Mem};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{Display, Formatter, Result},
|
fmt::{Display, Formatter, Result},
|
||||||
@ -8,39 +10,21 @@ use std::{
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Screen {
|
pub struct Screen {
|
||||||
mem: Mem,
|
pub width: usize,
|
||||||
width: usize,
|
pub height: usize,
|
||||||
height: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Screen {
|
impl Screen {
|
||||||
pub fn new(width: usize, height: usize) -> Screen {
|
pub fn new(width: usize, height: usize) -> Screen {
|
||||||
|
Screen { width, height }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Screen {
|
||||||
|
fn default() -> Self {
|
||||||
Screen {
|
Screen {
|
||||||
mem: Mem::new(width * height / 8),
|
width: 64,
|
||||||
width,
|
height: 32,
|
||||||
height,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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