From 2ba807d7a81b32872509b31336397a70f6dc153f Mon Sep 17 00:00:00 2001 From: John Breaux Date: Fri, 10 Mar 2023 15:33:36 -0600 Subject: [PATCH] Rumpulator: Change name to Chumpulator --- Cargo.lock | 18 ++-- Cargo.toml | 2 +- src/bus.rs | 2 +- src/cpu.rs | 4 +- src/cpu/instruction.rs | 232 +++++++++++++++++++++++++++++++++++++++++ src/dump.rs | 4 +- src/error.rs | 2 +- src/lib.rs | 2 +- src/main.rs | 4 +- src/mem.rs | 6 +- 10 files changed, 254 insertions(+), 22 deletions(-) create mode 100644 src/cpu/instruction.rs diff --git a/Cargo.lock b/Cargo.lock index 96dc036..14e2eff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "chumpulator" +version = "0.1.0" +dependencies = [ + "owo-colors", + "serde", + "thiserror", +] + [[package]] name = "owo-colors" version = "3.5.0" @@ -26,15 +35,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rumpulator" -version = "0.1.0" -dependencies = [ - "owo-colors", - "serde", - "thiserror", -] - [[package]] name = "serde" version = "1.0.153" diff --git a/Cargo.toml b/Cargo.toml index e410414..11efab7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rumpulator" +name = "chumpulator" version = "0.1.0" edition = "2021" diff --git a/src/bus.rs b/src/bus.rs index 83c02d0..1648cf9 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -11,7 +11,7 @@ use std::{ /// Creates a new bus, instantiating BusConnectable devices /// # Examples /// ```rust -/// # use rumpulator::prelude::*; +/// # use chumpulator::prelude::*; /// let mut bus = bus! { /// "RAM" [0x0000..0x8000] Mem::new(0x8000), /// "ROM" [0x8000..0xFFFF] Mem::new(0x8000).w(false), diff --git a/src/cpu.rs b/src/cpu.rs index 35d2cb1..142fdc5 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -73,7 +73,7 @@ impl CPU { /// Set a general purpose register in the CPU /// # Examples /// ```rust - /// # use rumpulator::prelude::*; + /// # use chumpulator::prelude::*; /// // Create a new CPU, and set v4 to 0x41 /// let cpu = CPU::default() /// .set_gpr(0x4, 0x41); @@ -97,7 +97,7 @@ impl CPU { /// | sp | 0x0efe | Initial top of stack. /// # Examples /// ```rust - /// # use rumpulator::prelude::*; + /// # use chumpulator::prelude::*; /// let mut cpu = CPU::new(0xf00, 0x50, 0x200, 0xefe, Disassemble::default()); /// ``` pub fn new(screen: Adr, font: Adr, pc: Adr, sp: Adr, disassembler: Disassemble) -> Self { diff --git a/src/cpu/instruction.rs b/src/cpu/instruction.rs new file mode 100644 index 0000000..07a4561 --- /dev/null +++ b/src/cpu/instruction.rs @@ -0,0 +1,232 @@ +//! Represents a chip-8 instruction as a Rust enum + +use super::{Adr, Nib, Reg}; +type Word = Adr; +type Byte = u8; +type Ins = Nib; + +/// Extract the instruction nibble from a word +#[inline] +pub fn i(ins: Word) -> Ins { + (ins >> 12) as Ins & 0xf +} +/// Extracts the X-register nibble from a word +#[inline] +pub fn x(ins: Word) -> Reg { + ins as Reg >> 8 & 0xf +} +/// Extracts the Y-register nibble from a word +#[inline] +pub fn y(ins: u16) -> Reg { + ins as Reg >> 4 & 0xf +} +/// Extracts the nibble-sized immediate from a word +#[inline] +pub fn n(ins: Word) -> Nib { + ins as Nib & 0xf +} +/// Extracts the byte-sized immediate from a word +#[inline] +pub fn b(ins: Word) -> Byte { + ins as Byte +} +/// Extracts the address-sized immediate from a word +#[inline] +pub fn a(ins: Word) -> Adr { + ins & 0x0fff +} +/// Restores the instruction nibble into a word +#[inline] +pub fn ii(i: Ins) -> u16 { + (i as Word & 0xf) << 12 +} +/// Restores the X-register nibble into a word +#[inline] +pub fn xi(x: Reg) -> Word { + (x as Word & 0xf) << 8 +} +/// Restores the Y-register nibble into a word +#[inline] +pub fn yi(y: Reg) -> Word { + (y as Word & 0xf) << 4 +} +/// Restores the nibble-sized immediate into a word +#[inline] +pub fn ni(n: Nib) -> Word { + n as Word & 0xf +} +/// Restores the byte-sized immediate into a word +#[inline] +pub fn bi(b: Byte) -> Word { + b as Word +} +/// Captures the operand and type of a Chip-8 instruction +pub enum Chip8Instruction { + Unimplemented(Word), + Clear, + Return, + Sys(Adr), + Jump(Adr), + Call(Adr), + SkipEqualsByte(Reg, Byte), + SkipNotEqualsByte(Reg, Byte), + SkipEquals(Reg, Reg), + LoadImmediate(Reg, Byte), + AddImmediate(Reg, Byte), + Copy(Reg, Reg), + Or(Reg, Reg), + And(Reg, Reg), + Xor(Reg, Reg), + Add(Reg, Reg), + Sub(Reg, Reg), + ShiftRight(Reg, Reg), + BackwardsSub(Reg, Reg), + ShiftLeft(Reg, Reg), + SkipNotEquals(Reg, Reg), + LoadIndirect(Adr), + JumpIndexed(Adr), + Rand(Reg, Byte), + Draw(Reg, Reg, Nib), + SkipEqualsKey(Reg), + SkipNotEqualsKey(Reg), + StoreDelay(Reg), + WaitForKey(Reg), + LoadDelay(Reg), + LoadSound(Reg), + AddIndirect(Reg), + LoadSprite(Reg), + BcdConvert(Reg), + DmaStore(Reg), + DmaLoad(Reg), +} + +impl TryFrom for Chip8Instruction { + type Error = crate::error::Error; + /// Converts a 16-bit word into a Chip8Instruction, when possible. + fn try_from(opcode: Word) -> Result { + use crate::error::Error::*; + let (i, x, y, n, b, a) = ( + i(opcode), + x(opcode), + y(opcode), + n(opcode), + b(opcode), + a(opcode), + ); + if i > 0xf { + return Err(FunkyMath { + word: opcode, + explanation: "Instruction nibble greater than 0xf".into(), + }); + } + Ok(match i { + // # Issue a system call + // |opcode| effect | + // |------|------------------------------------| + // | 00e0 | Clear screen memory to all 0 | + // | 00ee | Return from subroutine | + 0x0 => match a { + 0xe0 => Self::Clear, + 0xee => Self::Return, + _ => Self::Sys(a), + }, + // | 1aaa | Sets pc to an absolute address + 0x1 => Self::Jump(a), + // | 2aaa | Pushes pc onto the stack, then jumps to a + 0x2 => Self::Call(a), + // | 3xbb | Skips next instruction if register X == b + 0x3 => Self::SkipEqualsByte(x, b), + // | 4xbb | Skips next instruction if register X != b + 0x4 => Self::SkipNotEqualsByte(x, b), + // # Performs a register-register comparison + // |opcode| effect | + // |------|------------------------------------| + // | 9XY0 | Skip next instruction if vX == vY | + 0x5 => match n { + 0x0 => Self::SkipEquals(x, y), + _ => Self::Unimplemented(opcode), + }, + // 6xbb: Loads immediate byte b into register vX + 0x6 => Self::LoadImmediate(x, b), + // 7xbb: Adds immediate byte b to register vX + 0x7 => Self::AddImmediate(x, b), + // # Performs ALU operation + // |opcode| effect | + // |------|------------------------------------| + // | 8xy0 | X = Y | + // | 8xy1 | X = X | Y | + // | 8xy2 | X = X & Y | + // | 8xy3 | X = X ^ Y | + // | 8xy4 | X = X + Y; Set vF=carry | + // | 8xy5 | X = X - Y; Set vF=carry | + // | 8xy6 | X = X >> 1 | + // | 8xy7 | X = Y - X; Set vF=carry | + // | 8xyE | X = X << 1 | + 0x8 => match n { + 0x0 => Self::Copy(x, y), + 0x1 => Self::Or(x, y), + 0x2 => Self::And(x, y), + 0x3 => Self::Xor(x, y), + 0x4 => Self::Add(x, y), + 0x5 => Self::Sub(x, y), + 0x6 => Self::ShiftRight(x, y), + 0x7 => Self::BackwardsSub(x, y), + 0xE => Self::ShiftLeft(x, y), + _ => Self::Unimplemented(opcode), + }, + // # Performs a register-register comparison + // |opcode| effect | + // |------|------------------------------------| + // | 9XY0 | Skip next instruction if vX != vY | + 0x9 => match n { + 0 => Self::SkipNotEquals(x, y), + _ => Self::Unimplemented(opcode), + }, + // Aaaa: Load address #a into register I + 0xa => Self::LoadIndirect(a), + // Baaa: Jump to &adr + v0 + 0xb => Self::JumpIndexed(a), + // 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), + + // # Skips instruction on value of keypress + // |opcode| effect | + // |------|------------------------------------| + // | eX9e | Skip next instruction if key == #X | + // | eXa1 | Skip next instruction if key != #X | + 0xe => match b { + 0x9e => Self::SkipEqualsKey(x), + 0xa1 => Self::SkipNotEqualsKey(x), + _ => Self::Unimplemented(opcode), + }, + + // # Performs IO + // |opcode| effect | + // |------|------------------------------------| + // | fX07 | Set vX to value in delay timer | + // | fX0a | Wait for input, store in vX m | + // | fX15 | Set sound timer to the value in vX | + // | fX18 | set delay timer to the value in vX | + // | fX1e | Add x to I | + // | fX29 | Load sprite for character x into I | + // | fX33 | BCD convert X into I[0..3] | + // | fX55 | DMA Stor from I to registers 0..X | + // | fX65 | DMA Load from I to registers 0..X | + 0xf => match b { + 0x07 => Self::StoreDelay(x), + 0x0A => Self::WaitForKey(x), + 0x15 => Self::LoadDelay(x), + 0x18 => Self::LoadSound(x), + 0x1E => Self::AddIndirect(x), + 0x29 => Self::LoadSprite(x), + 0x33 => Self::BcdConvert(x), + 0x55 => Self::DmaStore(x), + 0x65 => Self::DmaLoad(x), + _ => Self::Unimplemented(opcode), + }, + _ => unreachable!("i somehow mutated from <= 0xf to > 0xf"), + }) + } +} diff --git a/src/dump.rs b/src/dump.rs index 47f5f84..6a26200 100644 --- a/src/dump.rs +++ b/src/dump.rs @@ -5,7 +5,7 @@ use std::ops::Range; /// /// # Examples /// ```rust -/// # use rumpulator::prelude::*; +/// # use chumpulator::prelude::*; /// let mem = Mem::new(0x50); /// // Dumps the first 0x10 bytes /// mem.dump(0x00..0x10); @@ -19,7 +19,7 @@ pub trait Dumpable { /// /// # Examples /// ```rust -/// # use rumpulator::prelude::*; +/// # use chumpulator::prelude::*; /// let mem = bus! { /// "mem" [0..0x10] = Mem::new(0x10) /// }; diff --git a/src/error.rs b/src/error.rs index 657ee42..522ed7a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -//! Error type for rumpulator +//! Error type for chumpulator use thiserror::Error; diff --git a/src/lib.rs b/src/lib.rs index b75d8f9..d0d41b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ pub mod dump; pub mod error; pub mod screen; -/// Common imports for rumpulator +/// Common imports for chumpulator pub mod prelude { use super::*; pub use crate::bus; diff --git a/src/main.rs b/src/main.rs index eabef6e..32fb17d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use rumpulator::{bus::Read, prelude::*}; +use chumpulator::{bus::Read, prelude::*}; use std::fs::read; fn main() -> Result<(), std::io::Error> { @@ -8,7 +8,7 @@ fn main() -> Result<(), std::io::Error> { // Load the ROM file into RAM "userram" [0x0200..0x0F00] = Mem::new(0xF00 - 0x200).load(0, &read("chip-8/Fishie.ch8")?), // Create a screen - "screen" [0x0F00..0x1000] = Screen::new(32, 64), + "screen" [0x0F00..0x1000] = Mem::new(32*64/8), // Create some stack memory "stack" [0xF000..0xF800] = Mem::new(0x800).r(true).w(true), }; diff --git a/src/mem.rs b/src/mem.rs index 34a1f77..a650288 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -63,7 +63,7 @@ impl Mem { /// /// # Examples /// ``` rust - /// # use rumpulator::prelude::*; + /// # use chumpulator::prelude::*; /// let mem = Mem::new(0x100); /// assert_eq!(mem.len(), 0x100) /// ``` @@ -84,7 +84,7 @@ impl Mem { self } - /// Load a character set from rumpulator/src/mem/charset.bin into this memory section + /// Load a character set from chumpulator/src/mem/charset.bin into this memory section pub fn load_charset(self, addr: u16) -> Self { let charset = include_bytes!("mem/charset.bin"); self.load(addr, charset) @@ -94,7 +94,7 @@ impl Mem { /// /// # Examples /// ```rust - /// # use rumpulator::prelude::*; + /// # use chumpulator::prelude::*; /// let length = 0x100; /// let mem = Mem::new(length); /// ```