cpu.rs: Refactor for modularity
- Break into submodules - Move bus into submodule of CPU - Keep program and charset rom inside CPU - Take only the screen on the external Bus - Refactor the disassembler into an instruction definition and the actual "Dis" item
This commit is contained in:
		
							
								
								
									
										57
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								Cargo.toml
									
									
									
									
									
								
							| @@ -2,21 +2,11 @@ | |||||||
| name = "chirp" | name = "chirp" | ||||||
| version = "0.1.1" | version = "0.1.1" | ||||||
| edition = "2021" | edition = "2021" | ||||||
| default-run = "chirp" | default-run = "chirp-imgui" | ||||||
| authors = ["John Breaux"] | authors = ["John Breaux"] | ||||||
| license = "MIT" | license = "MIT" | ||||||
| publish = false | publish = false | ||||||
|  |  | ||||||
|  |  | ||||||
| [features] |  | ||||||
| default = ["unstable", "drawille", "minifb"] |  | ||||||
| unstable = [] |  | ||||||
| drawille = ["dep:drawille"] |  | ||||||
| iced = ["dep:iced"] |  | ||||||
| minifb = ["dep:minifb"] |  | ||||||
| rhexdump = ["dep:rhexdump"] |  | ||||||
| serde = ["dep:serde"] |  | ||||||
|  |  | ||||||
| [[bin]] | [[bin]] | ||||||
| name = "chirp" | name = "chirp" | ||||||
| path = "src/bin/chirp-minifb/main.rs" | path = "src/bin/chirp-minifb/main.rs" | ||||||
| @@ -27,13 +17,28 @@ name = "chirp-disasm" | |||||||
| required-features = ["default"] | required-features = ["default"] | ||||||
|  |  | ||||||
| [[bin]] | [[bin]] | ||||||
| name = "chirp-iced" | name = "chirp-imgui" | ||||||
| required-features = ["iced"] | required-features = ["imgui"] | ||||||
|  |  | ||||||
| [[bin]] | [[bin]] | ||||||
| name = "chirp-shot-viewer" | name = "chirp-shot-viewer" | ||||||
| required-features = ["default", "drawille"] | required-features = ["default", "drawille"] | ||||||
|  |  | ||||||
|  | [features] | ||||||
|  | default = ["drawille", "imgui", "serde"] | ||||||
|  | nightly = [] | ||||||
|  | drawille = ["dep:drawille"] | ||||||
|  | imgui = [ | ||||||
|  |     "dep:imgui", | ||||||
|  |     "dep:imgui-wgpu", | ||||||
|  |     "dep:imgui-winit-support", | ||||||
|  |     "dep:winit", | ||||||
|  |     "dep:winit_input_helper", | ||||||
|  |     "dep:pixels", | ||||||
|  | ] | ||||||
|  | minifb = ["dep:minifb"] | ||||||
|  | rhexdump = ["dep:rhexdump"] | ||||||
|  |  | ||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
| [profile.release] | [profile.release] | ||||||
| opt-level = 3 | opt-level = 3 | ||||||
| @@ -48,14 +53,22 @@ overflow-checks = false | |||||||
|  |  | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| drawille = {version = "0.3.0", optional = true} |  | ||||||
| iced = {version = "0.8.0", optional = true} |  | ||||||
| rhexdump = {version = "^0.1.1", optional = true } |  | ||||||
| serde = { version = "^1.0", features = ["derive"], optional = true } |  | ||||||
| minifb = { version = "^0.24.0", optional = true } |  | ||||||
|  |  | ||||||
| gumdrop = "^0.8.1" | imgui = { version = "^0.10", optional = true } | ||||||
|  | imgui-winit-support = { version = "^0.10", optional = true } | ||||||
|  | imgui-wgpu = { version = "^0", optional = true } | ||||||
|  | pixels = { version = "^0", optional = true } | ||||||
|  | # TODO: When imgui-winit-support updates to 0.28 (Soon:tm:), update winit and winit_input_helper | ||||||
|  | winit = { version = "0.27.5", features = ["default", "x11"], optional = true } | ||||||
|  | winit_input_helper = { version = "^0.13.0", optional = true } | ||||||
|  |  | ||||||
|  | drawille = { version = "0.3.0", optional = true } | ||||||
|  | rhexdump = { version = "0.1.1", optional = true } | ||||||
|  | serde = { version = "1.0.0", features = ["derive"], optional = true } | ||||||
|  | minifb = { version = "0.24.0", optional = true } | ||||||
|  |  | ||||||
|  | gumdrop = "0.8.1" | ||||||
| imperative-rs = "0.3.1" | imperative-rs = "0.3.1" | ||||||
| owo-colors = "^3" | owo-colors = "3" | ||||||
| rand = "^0.8.5" | rand = "0.8.5" | ||||||
| thiserror = "^1.0.39" | thiserror = "1.0.39" | ||||||
|   | |||||||
							
								
								
									
										177
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										177
									
								
								src/cpu.rs
									
									
									
									
									
								
							| @@ -6,54 +6,43 @@ | |||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests; | mod tests; | ||||||
|  |  | ||||||
|  | pub mod behavior; | ||||||
| pub mod bus; | pub mod bus; | ||||||
| pub mod disassembler; |  | ||||||
| pub mod flags; | pub mod flags; | ||||||
| pub mod instruction; | pub mod instruction; | ||||||
| pub mod mode; | pub mod mode; | ||||||
| pub mod quirks; | pub mod quirks; | ||||||
|  |  | ||||||
| use self::{ | use self::{ | ||||||
|     bus::{Bus, Get, ReadWrite, Region}, |     bus::{Bus, Get, ReadWrite, Region::*}, | ||||||
|     disassembler::{Dis, Disassembler, Insn}, |  | ||||||
|     flags::Flags, |     flags::Flags, | ||||||
|  |     instruction::{ | ||||||
|  |         disassembler::{Dis, Disassembler}, | ||||||
|  |         Insn, | ||||||
|  |     }, | ||||||
|     mode::Mode, |     mode::Mode, | ||||||
|     quirks::Quirks, |     quirks::Quirks, | ||||||
| }; | }; | ||||||
| use crate::error::{Error, Result}; | use crate::{ | ||||||
|  |     bus, | ||||||
|  |     error::{Error, Result}, | ||||||
|  | }; | ||||||
| use imperative_rs::InstructionSet; | use imperative_rs::InstructionSet; | ||||||
| use owo_colors::OwoColorize; | use owo_colors::OwoColorize; | ||||||
| use rand::random; | use std::fmt::Debug; | ||||||
| use std::time::Instant; |  | ||||||
|  |  | ||||||
| type Reg = usize; | type Reg = usize; | ||||||
| type Adr = u16; | type Adr = u16; | ||||||
| type Nib = u8; | type Nib = u8; | ||||||
|  |  | ||||||
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |  | ||||||
| struct Timers { |  | ||||||
|     frame: Instant, |  | ||||||
|     insn: Instant, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Default for Timers { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         let now = Instant::now(); |  | ||||||
|         Self { |  | ||||||
|             frame: now, |  | ||||||
|             insn: now, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Represents the internal state of the CPU interpreter | /// Represents the internal state of the CPU interpreter | ||||||
| #[derive(Clone, Debug, PartialEq)] | #[derive(Clone, PartialEq)] | ||||||
| pub struct CPU { | pub struct CPU { | ||||||
|     /// Flags that control how the CPU behaves, but which aren't inherent to the |     /// Flags that control how the CPU behaves, but which aren't inherent to the | ||||||
|     /// chip-8. Includes [Quirks], target IPF, etc. |     /// chip-8. Includes [Quirks], target IPF, etc. | ||||||
|     pub flags: Flags, |     pub flags: Flags, | ||||||
|     // memory map info |     // memory map info | ||||||
|     screen: Adr, |     screen: Bus, | ||||||
|     font: Adr, |     font: Adr, | ||||||
|     // memory |     // memory | ||||||
|     stack: Vec<Adr>, |     stack: Vec<Adr>, | ||||||
| @@ -66,7 +55,6 @@ pub struct CPU { | |||||||
|     // I/O |     // I/O | ||||||
|     keys: [bool; 16], |     keys: [bool; 16], | ||||||
|     // Execution data |     // Execution data | ||||||
|     timers: Timers, |  | ||||||
|     cycle: usize, |     cycle: usize, | ||||||
|     breakpoints: Vec<Adr>, |     breakpoints: Vec<Adr>, | ||||||
|     disassembler: Dis, |     disassembler: Dis, | ||||||
| @@ -74,7 +62,6 @@ 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 | ||||||
| @@ -90,22 +77,36 @@ impl CPU { | |||||||
|     /// dbg!(cpu); |     /// dbg!(cpu); | ||||||
|     /// ``` |     /// ``` | ||||||
|     pub fn new( |     pub fn new( | ||||||
|         screen: Adr, |         rom: impl AsRef<std::path::Path>, | ||||||
|         font: Adr, |         font: Adr, | ||||||
|         pc: Adr, |         pc: Adr, | ||||||
|         disassembler: Dis, |         disassembler: Dis, | ||||||
|         breakpoints: Vec<Adr>, |         breakpoints: Vec<Adr>, | ||||||
|         flags: Flags, |         flags: Flags, | ||||||
|     ) -> Self { |     ) -> Result<Self> { | ||||||
|         CPU { |         let mut cpu = CPU { | ||||||
|             disassembler, |             disassembler, | ||||||
|             screen, |  | ||||||
|             font, |             font, | ||||||
|             pc, |             pc, | ||||||
|             breakpoints, |             breakpoints, | ||||||
|             flags, |             flags, | ||||||
|             ..Default::default() |             ..Default::default() | ||||||
|         } |         }; | ||||||
|  |         // load the provided rom | ||||||
|  |         cpu.load_program(rom)?; | ||||||
|  |         Ok(cpu) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Loads a program into the CPU's program space | ||||||
|  |     pub fn load_program(&mut self, rom: impl AsRef<std::path::Path>) -> Result<&mut Self> { | ||||||
|  |         self.load_program_bytes(&std::fs::read(rom)?) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Loads bytes into the CPU's program space | ||||||
|  |     pub fn load_program_bytes(&mut self, rom: &[u8]) -> Result<&mut Self> { | ||||||
|  |         self.screen.clear_region(Program); | ||||||
|  |         self.screen.load_region(Program, rom)?; | ||||||
|  |         Ok(self) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Presses a key, and reports whether the key's state changed.   |     /// Presses a key, and reports whether the key's state changed.   | ||||||
| @@ -292,6 +293,35 @@ impl CPU { | |||||||
|         self.flags.draw_wait = false; |         self.flags.draw_wait = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Resets the emulator. | ||||||
|  |     /// | ||||||
|  |     /// Touches the [Flags] (keypause, draw_wait, draw_mode, and lastkey), | ||||||
|  |     /// stack, pc, registers, keys, and cycle count. | ||||||
|  |     /// | ||||||
|  |     /// Does not touch [Quirks], [Mode], [Dis], breakpoints, or memory map. | ||||||
|  |     pub fn reset(&mut self) { | ||||||
|  |         self.flags = Flags { | ||||||
|  |             keypause: false, | ||||||
|  |             draw_wait: false, | ||||||
|  |             draw_mode: false, | ||||||
|  |             lastkey: None, | ||||||
|  |             ..self.flags | ||||||
|  |         }; | ||||||
|  |         // clear the stack | ||||||
|  |         self.stack.truncate(0); | ||||||
|  |         // Reset the program counter | ||||||
|  |         self.pc = 0x200; | ||||||
|  |         // Zero the registers | ||||||
|  |         self.i = 0; | ||||||
|  |         self.v = [0; 16]; | ||||||
|  |         self.delay = 0.0; | ||||||
|  |         self.sound = 0.0; | ||||||
|  |         // I/O | ||||||
|  |         self.keys = [false; 16]; | ||||||
|  |         // Execution data | ||||||
|  |         self.cycle = 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Set a breakpoint |     /// Set a breakpoint | ||||||
|     // TODO: Unit test this |     // TODO: Unit test this | ||||||
|     pub fn set_break(&mut self, point: Adr) -> &mut Self { |     pub fn set_break(&mut self, point: Adr) -> &mut Self { | ||||||
| @@ -378,53 +408,15 @@ impl CPU { | |||||||
|     /// assert_eq!(0x202, cpu.pc()); |     /// assert_eq!(0x202, cpu.pc()); | ||||||
|     /// assert_eq!(0x20, cpu.cycle()); |     /// assert_eq!(0x20, cpu.cycle()); | ||||||
|     /// ``` |     /// ``` | ||||||
|     pub fn multistep(&mut self, bus: &mut Bus, steps: usize) -> Result<&mut Self> { |     pub fn multistep(&mut self, screen: &mut Bus, steps: usize) -> Result<&mut Self> { | ||||||
|         for _ in 0..steps { |         for _ in 0..steps { | ||||||
|             self.tick(bus)?; |             self.tick(screen)?; | ||||||
|             self.vertical_blank(); |             let speed = 1.0 / steps as f64; | ||||||
|         } |  | ||||||
|         Ok(self) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Simulates vertical blanking |  | ||||||
|     /// |  | ||||||
|     /// If monotonic timing is `enabled`: |  | ||||||
|     /// - Ticks the sound and delay timers according to CPU cycle count |  | ||||||
|     /// - 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 |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub fn vertical_blank(&mut self) -> &mut Self { |  | ||||||
|         if self.flags.pause { |  | ||||||
|             return self; |  | ||||||
|         } |  | ||||||
|         // Use a monotonic counter when testing |  | ||||||
|         if let Some(speed) = self.flags.monotonic { |  | ||||||
|             if self.flags.draw_wait { |  | ||||||
|                 self.flags.draw_wait = self.cycle % speed != 0; |  | ||||||
|             } |  | ||||||
|             let speed = 1.0 / speed as f64; |  | ||||||
|             self.delay -= speed; |             self.delay -= speed; | ||||||
|             self.sound -= speed; |             self.sound -= speed; | ||||||
|             return self; |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         // Convert the elapsed time to 60ths of a second |  | ||||||
|         let frame = Instant::now(); |  | ||||||
|         let time = (frame - self.timers.frame).as_secs_f64() * 60.0; |  | ||||||
|         self.timers.frame = frame; |  | ||||||
|         if time > 1.0 { |  | ||||||
|             self.flags.draw_wait = false; |  | ||||||
|         } |         } | ||||||
|         if self.delay > 0.0 { |         self.flags.draw_wait = false; | ||||||
|             self.delay -= time; |         Ok(self) | ||||||
|         } |  | ||||||
|         if self.sound > 0.0 { |  | ||||||
|             self.sound -= time; |  | ||||||
|         } |  | ||||||
|         self |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Executes a single instruction |     /// Executes a single instruction | ||||||
| @@ -466,7 +458,7 @@ impl CPU { | |||||||
|     /// dbg!(cpu.tick(&mut bus)) |     /// dbg!(cpu.tick(&mut bus)) | ||||||
|     ///     .expect_err("Should return Error::InvalidInstruction { 0xffff }"); |     ///     .expect_err("Should return Error::InvalidInstruction { 0xffff }"); | ||||||
|     /// ``` |     /// ``` | ||||||
|     pub fn tick(&mut self, bus: &mut Bus) -> Result<&mut Self> { |     pub fn tick(&mut self, screen: &mut Bus) -> Result<&mut Self> { | ||||||
|         // Do nothing if paused |         // Do nothing if paused | ||||||
|         if self.flags.is_paused() { |         if self.flags.is_paused() { | ||||||
|             // always tick in test mode |             // always tick in test mode | ||||||
| @@ -505,25 +497,21 @@ impl CPU { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // decode opcode |         // decode opcode | ||||||
|         if let Ok((inc, insn)) = Insn::decode(opcode) { |         if let Ok((inc, insn)) = Insn::decode(opchunk) { | ||||||
|             self.pc = self.pc.wrapping_add(inc as u16); |             self.pc = self.pc.wrapping_add(inc as u16); | ||||||
|             self.execute(bus, insn); |             self.execute(screen, insn); | ||||||
|         } else { |         } else { | ||||||
|             return Err(Error::UnimplementedInstruction { |             return Err(Error::UnimplementedInstruction { | ||||||
|                 word: u16::from_be_bytes(*opcode), |                 word: u16::from_be_bytes(*opcode), | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if self.flags.debug { |  | ||||||
|             println!("{:?}", self.timers.insn.elapsed().bright_black()); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // process breakpoints |         // process breakpoints | ||||||
|         if !self.breakpoints.is_empty() && self.breakpoints.contains(&self.pc) { |         if !self.breakpoints.is_empty() && self.breakpoints.contains(&self.pc) { | ||||||
|             self.flags.pause = true; |             self.flags.pause = true; | ||||||
|             return Err(Error::BreakpointHit { |             return Err(Error::BreakpointHit { | ||||||
|                 addr: self.pc, |                 addr: self.pc, | ||||||
|                 next: bus.read(self.pc), |                 next: self.screen.read(self.pc), | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|         Ok(self) |         Ok(self) | ||||||
| @@ -572,6 +560,25 @@ impl CPU { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl Debug for CPU { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         f.debug_struct("CPU") | ||||||
|  |             .field("flags", &self.flags) | ||||||
|  |             .field("font", &self.font) | ||||||
|  |             .field("stack", &self.stack) | ||||||
|  |             .field("pc", &self.pc) | ||||||
|  |             .field("i", &self.i) | ||||||
|  |             .field("v", &self.v) | ||||||
|  |             .field("delay", &self.delay) | ||||||
|  |             .field("sound", &self.sound) | ||||||
|  |             .field("keys", &self.keys) | ||||||
|  |             .field("cycle", &self.cycle) | ||||||
|  |             .field("breakpoints", &self.breakpoints) | ||||||
|  |             .field("disassembler", &self.disassembler) | ||||||
|  |             .finish_non_exhaustive() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl Default for CPU { | impl Default for CPU { | ||||||
|     /// Constructs a new CPU with sane defaults and debug mode ON |     /// Constructs a new CPU with sane defaults and debug mode ON | ||||||
|     /// |     /// | ||||||
| @@ -589,7 +596,10 @@ impl Default for CPU { | |||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         CPU { |         CPU { | ||||||
|             stack: vec![], |             stack: vec![], | ||||||
|             screen: 0xf00, |             screen: bus! { | ||||||
|  |                 Charset [0x0050..0x00a0] = include_bytes!("mem/charset.bin"), | ||||||
|  |                 Program [0x0200..0x1000], | ||||||
|  |             }, | ||||||
|             font: 0x050, |             font: 0x050, | ||||||
|             pc: 0x200, |             pc: 0x200, | ||||||
|             i: 0, |             i: 0, | ||||||
| @@ -602,7 +612,6 @@ impl Default for CPU { | |||||||
|                 debug: true, |                 debug: true, | ||||||
|                 ..Default::default() |                 ..Default::default() | ||||||
|             }, |             }, | ||||||
|             timers: Default::default(), |  | ||||||
|             breakpoints: vec![], |             breakpoints: vec![], | ||||||
|             disassembler: Dis::default(), |             disassembler: Dis::default(), | ||||||
|         } |         } | ||||||
|   | |||||||
							
								
								
									
										669
									
								
								src/cpu/behavior.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										669
									
								
								src/cpu/behavior.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,669 @@ | |||||||
|  | // (c) 2023 John A. Breaux | ||||||
|  | // This code is licensed under MIT license (see LICENSE for details) | ||||||
|  |  | ||||||
|  | //! Contains implementations for each Chip-8 [Insn] | ||||||
|  |  | ||||||
|  | use super::{bus::Region, *}; | ||||||
|  | use rand::random; | ||||||
|  |  | ||||||
|  | impl CPU { | ||||||
|  |     /// Executes a single [Insn] | ||||||
|  |     #[rustfmt::skip] | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn execute(&mut self, screen: &mut Bus, instruction: Insn) { | ||||||
|  |         match instruction { | ||||||
|  |             // Core Chip-8 instructions | ||||||
|  |             Insn::cls               => self.clear_screen(screen), | ||||||
|  |             Insn::ret               => self.ret(), | ||||||
|  |             Insn::jmp   {       A } => self.jump(A), | ||||||
|  |             Insn::call  {       A } => self.call(A), | ||||||
|  |             Insn::seb   {    x, B } => self.skip_equals_immediate(x, B), | ||||||
|  |             Insn::sneb  {    x, B } => self.skip_not_equals_immediate(x, B), | ||||||
|  |             Insn::se    { y, x    } => self.skip_equals(x, y), | ||||||
|  |             Insn::movb  {    x, B } => self.load_immediate(x, B), | ||||||
|  |             Insn::addb  {    x, B } => self.add_immediate(x, B), | ||||||
|  |             Insn::mov   { y, x    } => self.load(x, y), | ||||||
|  |             Insn::or    { y, x    } => self.or(x, y), | ||||||
|  |             Insn::and   { y, x    } => self.and(x, y), | ||||||
|  |             Insn::xor   { y, x    } => self.xor(x, y), | ||||||
|  |             Insn::add   { y, x    } => self.add(x, y), | ||||||
|  |             Insn::sub   { y, x    } => self.sub(x, y), | ||||||
|  |             Insn::shr   { y, x    } => self.shift_right(x, y), | ||||||
|  |             Insn::bsub  { y, x    } => self.backwards_sub(x, y), | ||||||
|  |             Insn::shl   { y, x    } => self.shift_left(x, y), | ||||||
|  |             Insn::sne   { y, x    } => self.skip_not_equals(x, y), | ||||||
|  |             Insn::movI  {       A } => self.load_i_immediate(A), | ||||||
|  |             Insn::jmpr  {       A } => self.jump_indexed(A), | ||||||
|  |             Insn::rand  {    x, B } => self.rand(x, B), | ||||||
|  |             Insn::draw  { y, x, n } => self.draw(x, y, n, screen), | ||||||
|  |             Insn::sek   {    x    } => self.skip_key_equals(x), | ||||||
|  |             Insn::snek  {    x    } => self.skip_key_not_equals(x), | ||||||
|  |             Insn::getdt {    x    } => self.load_delay_timer(x), | ||||||
|  |             Insn::waitk {    x    } => self.wait_for_key(x), | ||||||
|  |             Insn::setdt {    x    } => self.store_delay_timer(x), | ||||||
|  |             Insn::movst {    x    } => self.store_sound_timer(x), | ||||||
|  |             Insn::addI  {    x    } => self.add_i(x), | ||||||
|  |             Insn::font  {    x    } => self.load_sprite(x), | ||||||
|  |             Insn::bcd   {    x    } => self.bcd_convert(x), | ||||||
|  |             Insn::dmao  {    x    } => self.store_dma(x), | ||||||
|  |             Insn::dmai  {    x    } => self.load_dma(x), | ||||||
|  |             // Super-Chip extensions | ||||||
|  |             Insn::scd   {       n } => self.scroll_down(n, screen), | ||||||
|  |             Insn::scr               => self.scroll_right(screen), | ||||||
|  |             Insn::scl               => self.scroll_left(screen), | ||||||
|  |             Insn::halt              => self.flags.pause(), | ||||||
|  |             Insn::lores             => self.init_lores(screen), | ||||||
|  |             Insn::hires             => self.init_hires(screen), | ||||||
|  |             Insn::hfont {    x    } => self.load_big_sprite(x), | ||||||
|  |             Insn::flgo  {    x    } => self.store_flags(x), | ||||||
|  |             Insn::flgi  {    x    } => self.load_flags(x), | ||||||
|  |             // XO-Chip extensions | ||||||
|  |             _ => unimplemented!(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`0aaa`| Issues a "System call" (ML routine) | ||||||
|  | /// | ||||||
|  | /// |opcode| effect                             | | ||||||
|  | /// |------|------------------------------------| | ||||||
|  | /// |`00e0`| Clear screen memory to all 0       | | ||||||
|  | /// |`00ee`| Return from subroutine             | | ||||||
|  | impl CPU { | ||||||
|  |     /// |`00e0`| Clears the screen memory to 0 | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn clear_screen(&mut self, bus: &mut Bus) { | ||||||
|  |         bus.clear_region(Region::Screen); | ||||||
|  |     } | ||||||
|  |     /// |`00ee`| Returns from subroutine | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn ret(&mut self) { | ||||||
|  |         self.pc = self.stack.pop().unwrap_or(0x200); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Super Chip screen-control routines | ||||||
|  | /// | ||||||
|  | /// |opcode| effect                             | | ||||||
|  | /// |------|------------------------------------| | ||||||
|  | /// |`00cN`| Scroll the screen down N lines     | | ||||||
|  | /// |`00fb`| Scroll the screen right            | | ||||||
|  | /// |`00fc`| Scroll the screen left             | | ||||||
|  | /// |`00fe`| Initialize lores mode              | | ||||||
|  | /// |`00ff`| Initialize hires mode              | | ||||||
|  | impl CPU { | ||||||
|  |     /// # |`00cN`| | ||||||
|  |     /// Scroll the screen down N lines | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn scroll_down(&mut self, n: Nib, screen: &mut Bus) { | ||||||
|  |         match self.flags.draw_mode { | ||||||
|  |             true => { | ||||||
|  |                 // Get a line from the bus | ||||||
|  |                 for i in (0..16 * (64 - n as usize)).step_by(16).rev() { | ||||||
|  |                     let line: u128 = screen.read(i); | ||||||
|  |                     screen.write(i - (n as usize * 16), 0u128); | ||||||
|  |                     screen.write(i, line); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             false => { | ||||||
|  |                 // Get a line from the bus | ||||||
|  |                 for i in (0..8 * (32 - n as usize)).step_by(8).rev() { | ||||||
|  |                     let line: u64 = screen.read(i); | ||||||
|  |                     screen.write(i, 0u64); | ||||||
|  |                     screen.write(i + (n as usize * 8), line); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// # |`00fb`| | ||||||
|  |     /// Scroll the screen right | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn scroll_right(&mut self, screen: &mut impl ReadWrite<u128>) { | ||||||
|  |         // Get a line from the bus | ||||||
|  |         for i in (0..16 * 64_usize).step_by(16) { | ||||||
|  |             //let line: u128 = bus.read(self.screen + i) >> 4; | ||||||
|  |             screen.write(i, screen.read(i) >> 4); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// # |`00fc`| | ||||||
|  |     ///  Scroll the screen left | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn scroll_left(&mut self, screen: &mut impl ReadWrite<u128>) { | ||||||
|  |         // Get a line from the bus | ||||||
|  |         for i in (0..16 * 64_usize).step_by(16) { | ||||||
|  |             let line: u128 = u128::wrapping_shl(screen.read(i), 4); | ||||||
|  |             screen.write(i, line); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// # |`00fe`| | ||||||
|  |     /// Initialize lores mode | ||||||
|  |     pub(super) fn init_lores(&mut self, screen: &mut Bus) { | ||||||
|  |         self.flags.draw_mode = false; | ||||||
|  |         screen.set_region(Region::Screen, 0..256); | ||||||
|  |         self.clear_screen(screen); | ||||||
|  |     } | ||||||
|  |     /// # |`00ff`| | ||||||
|  |     /// Initialize hires mode | ||||||
|  |     pub(super) fn init_hires(&mut self, screen: &mut Bus) { | ||||||
|  |         self.flags.draw_mode = true; | ||||||
|  |         screen.set_region(Region::Screen, 0..1024); | ||||||
|  |         self.clear_screen(screen); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`1aaa`| Sets pc to an absolute address | ||||||
|  | impl CPU { | ||||||
|  |     /// |`1aaa`| Sets the program counter to an absolute address | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn jump(&mut self, a: Adr) { | ||||||
|  |         // jump to self == halt | ||||||
|  |         if a.wrapping_add(2) == self.pc { | ||||||
|  |             self.flags.pause = true; | ||||||
|  |         } | ||||||
|  |         self.pc = a; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`2aaa`| Pushes pc onto the stack, then jumps to a | ||||||
|  | impl CPU { | ||||||
|  |     /// |`2aaa`| Pushes pc onto the stack, then jumps to a | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn call(&mut self, a: Adr) { | ||||||
|  |         self.stack.push(self.pc); | ||||||
|  |         self.pc = a; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`3xbb`| Skips next instruction if register X == b | ||||||
|  | impl CPU { | ||||||
|  |     /// |`3xbb`| Skips the next instruction if register X == b | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn skip_equals_immediate(&mut self, x: Reg, b: u8) { | ||||||
|  |         if self.v[x] == b { | ||||||
|  |             self.pc = self.pc.wrapping_add(2); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`4xbb`| Skips next instruction if register X != b | ||||||
|  | impl CPU { | ||||||
|  |     /// |`4xbb`| Skips the next instruction if register X != b | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn skip_not_equals_immediate(&mut self, x: Reg, b: u8) { | ||||||
|  |         if self.v[x] != b { | ||||||
|  |             self.pc = self.pc.wrapping_add(2); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`5xyn`| Performs a register-register comparison | ||||||
|  | /// | ||||||
|  | /// |opcode| effect                             | | ||||||
|  | /// |------|------------------------------------| | ||||||
|  | /// |`5XY0`| Skip next instruction if vX == vY  | | ||||||
|  | impl CPU { | ||||||
|  |     /// |`5xy0`| Skips the next instruction if register X != register Y | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn skip_equals(&mut self, x: Reg, y: Reg) { | ||||||
|  |         if self.v[x] == self.v[y] { | ||||||
|  |             self.pc = self.pc.wrapping_add(2); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`6xbb`| Loads immediate byte b into register vX | ||||||
|  | impl CPU { | ||||||
|  |     /// |`6xbb`| Loads immediate byte b into register vX | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn load_immediate(&mut self, x: Reg, b: u8) { | ||||||
|  |         self.v[x] = b; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`7xbb`| Adds immediate byte b to register vX | ||||||
|  | impl CPU { | ||||||
|  |     /// |`7xbb`| Adds immediate byte b to register vX | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn add_immediate(&mut self, x: Reg, b: u8) { | ||||||
|  |         self.v[x] = self.v[x].wrapping_add(b); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`8xyn`| Performs ALU operation | ||||||
|  | /// | ||||||
|  | /// |opcode| effect                             | | ||||||
|  | /// |------|------------------------------------| | ||||||
|  | /// |`8xy0`| Y = X                              | | ||||||
|  | /// |`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                         | | ||||||
|  | impl CPU { | ||||||
|  |     /// |`8xy0`| Loads the value of y into x | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn load(&mut self, x: Reg, y: Reg) { | ||||||
|  |         self.v[x] = self.v[y]; | ||||||
|  |     } | ||||||
|  |     /// |`8xy1`| Performs bitwise or of vX and vY, and stores the result in vX | ||||||
|  |     /// | ||||||
|  |     /// # Quirk | ||||||
|  |     /// The original chip-8 interpreter will clobber vF for any 8-series instruction | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn or(&mut self, x: Reg, y: Reg) { | ||||||
|  |         self.v[x] |= self.v[y]; | ||||||
|  |         if !self.flags.quirks.bin_ops { | ||||||
|  |             self.v[0xf] = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// |`8xy2`| Performs bitwise and of vX and vY, and stores the result in vX | ||||||
|  |     /// | ||||||
|  |     /// # Quirk | ||||||
|  |     /// The original chip-8 interpreter will clobber vF for any 8-series instruction | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn and(&mut self, x: Reg, y: Reg) { | ||||||
|  |         self.v[x] &= self.v[y]; | ||||||
|  |         if !self.flags.quirks.bin_ops { | ||||||
|  |             self.v[0xf] = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// |`8xy3`| Performs bitwise xor of vX and vY, and stores the result in vX | ||||||
|  |     /// | ||||||
|  |     /// # Quirk | ||||||
|  |     /// The original chip-8 interpreter will clobber vF for any 8-series instruction | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn xor(&mut self, x: Reg, y: Reg) { | ||||||
|  |         self.v[x] ^= self.v[y]; | ||||||
|  |         if !self.flags.quirks.bin_ops { | ||||||
|  |             self.v[0xf] = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// |`8xy4`| Performs addition of vX and vY, and stores the result in vX | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn add(&mut self, x: Reg, y: Reg) { | ||||||
|  |         let carry; | ||||||
|  |         (self.v[x], carry) = self.v[x].overflowing_add(self.v[y]); | ||||||
|  |         self.v[0xf] = carry.into(); | ||||||
|  |     } | ||||||
|  |     /// |`8xy5`| Performs subtraction of vX and vY, and stores the result in vX | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn sub(&mut self, x: Reg, y: Reg) { | ||||||
|  |         let carry; | ||||||
|  |         (self.v[x], carry) = self.v[x].overflowing_sub(self.v[y]); | ||||||
|  |         self.v[0xf] = (!carry).into(); | ||||||
|  |     } | ||||||
|  |     /// |`8xy6`| Performs bitwise right shift of vX | ||||||
|  |     /// | ||||||
|  |     /// # Quirk | ||||||
|  |     /// On the original chip-8 interpreter, this shifts vY and stores the result in vX | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn shift_right(&mut self, x: Reg, y: Reg) { | ||||||
|  |         let src: Reg = if self.flags.quirks.shift { x } else { y }; | ||||||
|  |         let shift_out = self.v[src] & 1; | ||||||
|  |         self.v[x] = self.v[src] >> 1; | ||||||
|  |         self.v[0xf] = shift_out; | ||||||
|  |     } | ||||||
|  |     /// |`8xy7`| Performs subtraction of vY and vX, and stores the result in vX | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn backwards_sub(&mut self, x: Reg, y: Reg) { | ||||||
|  |         let carry; | ||||||
|  |         (self.v[x], carry) = self.v[y].overflowing_sub(self.v[x]); | ||||||
|  |         self.v[0xf] = (!carry).into(); | ||||||
|  |     } | ||||||
|  |     /// 8X_E: Performs bitwise left shift of vX | ||||||
|  |     /// | ||||||
|  |     /// # Quirk | ||||||
|  |     /// On the original chip-8 interpreter, this would perform the operation on vY | ||||||
|  |     /// and store the result in vX. This behavior was left out, for now. | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn shift_left(&mut self, x: Reg, y: Reg) { | ||||||
|  |         let src: Reg = if self.flags.quirks.shift { x } else { y }; | ||||||
|  |         let shift_out: u8 = self.v[src] >> 7; | ||||||
|  |         self.v[x] = self.v[src] << 1; | ||||||
|  |         self.v[0xf] = shift_out; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`9xyn`| Performs a register-register comparison | ||||||
|  | /// | ||||||
|  | /// |opcode| effect                             | | ||||||
|  | /// |------|------------------------------------| | ||||||
|  | /// |`9XY0`| Skip next instruction if vX != vY  | | ||||||
|  | impl CPU { | ||||||
|  |     /// |`9xy0`| Skip next instruction if X != y | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn skip_not_equals(&mut self, x: Reg, y: Reg) { | ||||||
|  |         if self.v[x] != self.v[y] { | ||||||
|  |             self.pc = self.pc.wrapping_add(2); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`Aaaa`| Load address #a into register I | ||||||
|  | impl CPU { | ||||||
|  |     /// |`Aadr`| Load address #adr into register I | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn load_i_immediate(&mut self, a: Adr) { | ||||||
|  |         self.i = a; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`Baaa`| Jump to &adr + v0 | ||||||
|  | impl CPU { | ||||||
|  |     /// |`Badr`| Jump to &adr + v0 | ||||||
|  |     /// | ||||||
|  |     /// Quirk: | ||||||
|  |     /// On the Super-Chip, this does stupid shit | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn jump_indexed(&mut self, a: Adr) { | ||||||
|  |         let reg = if self.flags.quirks.stupid_jumps { | ||||||
|  |             a as usize >> 8 | ||||||
|  |         } else { | ||||||
|  |             0 | ||||||
|  |         }; | ||||||
|  |         self.pc = a.wrapping_add(self.v[reg] as Adr); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`Cxbb`| Stores a random number & the provided byte into vX | ||||||
|  | impl CPU { | ||||||
|  |     /// |`Cxbb`| Stores a random number & the provided byte into vX | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn rand(&mut self, x: Reg, b: u8) { | ||||||
|  |         self.v[x] = random::<u8>() & b; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`Dxyn`| Draws n-byte sprite to the screen at coordinates (vX, vY) | ||||||
|  | impl CPU { | ||||||
|  |     /// |`Dxyn`| Draws n-byte sprite to the screen at coordinates (vX, vY) | ||||||
|  |     /// | ||||||
|  |     /// # Quirk | ||||||
|  |     /// On the original chip-8 interpreter, this will wait for a VBI | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn draw(&mut self, x: Reg, y: Reg, n: Nib, screen: &mut Bus) { | ||||||
|  |         if !self.flags.quirks.draw_wait { | ||||||
|  |             self.flags.draw_wait = true; | ||||||
|  |         } | ||||||
|  |         // self.draw_hires handles both hi-res mode and drawing 16x16 sprites | ||||||
|  |         if self.flags.draw_mode || n == 0 { | ||||||
|  |             self.draw_hires(x, y, n, screen); | ||||||
|  |         } else { | ||||||
|  |             self.draw_lores(x, y, n, screen); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// |`Dxyn`| Chip-8: Draws n-byte sprite to the screen at coordinates (vX, vY) | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn draw_lores(&mut self, x: Reg, y: Reg, n: Nib, scr: &mut Bus) { | ||||||
|  |         self.draw_sprite(self.v[x] as u16 % 64, self.v[y] as u16 % 32, n, 64, 32, scr); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn draw_sprite(&mut self, x: u16, y: u16, n: Nib, w: u16, h: u16, screen: &mut Bus) { | ||||||
|  |         let w_bytes = w / 8; | ||||||
|  |         self.v[0xf] = 0; | ||||||
|  |         if let Some(sprite) = self | ||||||
|  |             .screen | ||||||
|  |             .get(self.i as usize..(self.i + n as u16) as usize) | ||||||
|  |         { | ||||||
|  |             for (line, &sprite) in sprite.iter().enumerate() { | ||||||
|  |                 let line = line as u16; | ||||||
|  |                 let sprite = ((sprite as u16) << (8 - (x % 8))).to_be_bytes(); | ||||||
|  |                 for (addr, &byte) in sprite.iter().enumerate().filter_map(|(idx, byte)| { | ||||||
|  |                     let x = (x / 8) + idx as u16; | ||||||
|  |                     Some(( | ||||||
|  |                         if self.flags.quirks.screen_wrap { | ||||||
|  |                             ((y + line) % h * w_bytes + (x % w_bytes)) % (w_bytes * h) | ||||||
|  |                         } else if x < w_bytes { | ||||||
|  |                             (y + line) * w_bytes + x | ||||||
|  |                         } else { | ||||||
|  |                             return None; | ||||||
|  |                         }, | ||||||
|  |                         byte, | ||||||
|  |                     )) | ||||||
|  |                 }) { | ||||||
|  |                     let display: u8 = screen.read(addr); | ||||||
|  |                     screen.write(addr, byte ^ display); | ||||||
|  |                     if byte & display != 0 { | ||||||
|  |                         self.v[0xf] = 1; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | /// |`Dxyn`| Super-Chip extension high-resolution graphics mode | ||||||
|  | impl CPU { | ||||||
|  |     /// |`Dxyn`| Super-Chip extension high-resolution graphics mode | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn draw_hires(&mut self, x: Reg, y: Reg, n: Nib, screen: &mut Bus) { | ||||||
|  |         if !self.flags.quirks.draw_wait { | ||||||
|  |             self.flags.draw_wait = true; | ||||||
|  |         } | ||||||
|  |         let (w, h) = match self.flags.draw_mode { | ||||||
|  |             true => (128, 64), | ||||||
|  |             false => (64, 32), | ||||||
|  |         }; | ||||||
|  |         let (x, y) = (self.v[x] as u16 % w, self.v[y] as u16 % h); | ||||||
|  |         match n { | ||||||
|  |             0 => self.draw_schip_sprite(x, y, w, screen), | ||||||
|  |             _ => self.draw_sprite(x, y, n, w, h, screen), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// Draws a 16x16 Super Chip sprite | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn draw_schip_sprite(&mut self, x: u16, y: u16, w: u16, screen: &mut Bus) { | ||||||
|  |         self.v[0xf] = 0; | ||||||
|  |         let w_bytes = w / 8; | ||||||
|  |         if let Some(sprite) = self.screen.get(self.i as usize..(self.i + 32) as usize) { | ||||||
|  |             let sprite = sprite.to_owned(); | ||||||
|  |             for (line, sprite) in sprite.chunks_exact(2).enumerate() { | ||||||
|  |                 let sprite = u16::from_be_bytes( | ||||||
|  |                     sprite | ||||||
|  |                         .try_into() | ||||||
|  |                         .expect("Chunks should only return 2 bytes"), | ||||||
|  |                 ); | ||||||
|  |                 let addr = (y + line as u16) * w_bytes + x / 8; | ||||||
|  |                 let sprite = (sprite as u32) << (16 - (x % 8)); | ||||||
|  |                 let display: u32 = screen.read(addr); | ||||||
|  |                 screen.write(addr, display ^ sprite); | ||||||
|  |                 if display & sprite != 0 { | ||||||
|  |                     self.v[0xf] += 1; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`Exbb`| Skips instruction on value of keypress | ||||||
|  | /// | ||||||
|  | /// |opcode| effect                             | | ||||||
|  | /// |------|------------------------------------| | ||||||
|  | /// |`eX9e`| Skip next instruction if key == vX | | ||||||
|  | /// |`eXa1`| Skip next instruction if key != vX | | ||||||
|  | impl CPU { | ||||||
|  |     /// |`Ex9E`| Skip next instruction if key == vX | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn skip_key_equals(&mut self, x: Reg) { | ||||||
|  |         if self.keys[self.v[x] as usize & 0xf] { | ||||||
|  |             self.pc += 2; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// |`ExaE`| Skip next instruction if key != vX | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn skip_key_not_equals(&mut self, x: Reg) { | ||||||
|  |         if !self.keys[self.v[x] as usize & 0xf] { | ||||||
|  |             self.pc += 2; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`Fxbb`| Performs IO | ||||||
|  | /// | ||||||
|  | /// |opcode| effect                             | | ||||||
|  | /// |------|------------------------------------| | ||||||
|  | /// |`fX07`| Set vX to value in delay timer     | | ||||||
|  | /// |`fX0a`| Wait for input, store key in vX    | | ||||||
|  | /// |`fX15`| Set sound timer to the value in vX | | ||||||
|  | /// |`fX18`| set delay timer to the value in vX | | ||||||
|  | /// |`fX1e`| Add vX 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 | | ||||||
|  | impl CPU { | ||||||
|  |     /// |`Fx07`| Get the current DT, and put it in vX | ||||||
|  |     /// ```py | ||||||
|  |     /// vX = DT | ||||||
|  |     /// ``` | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn load_delay_timer(&mut self, x: Reg) { | ||||||
|  |         self.v[x] = self.delay as u8; | ||||||
|  |     } | ||||||
|  |     /// |`Fx0A`| Wait for key, then vX = K | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn wait_for_key(&mut self, x: Reg) { | ||||||
|  |         if let Some(key) = self.flags.lastkey { | ||||||
|  |             self.v[x] = key as u8; | ||||||
|  |             self.flags.lastkey = None; | ||||||
|  |         } else { | ||||||
|  |             self.pc = self.pc.wrapping_sub(2); | ||||||
|  |             self.flags.keypause = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// |`Fx15`| Load vX into DT | ||||||
|  |     /// ```py | ||||||
|  |     /// DT = vX | ||||||
|  |     /// ``` | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn store_delay_timer(&mut self, x: Reg) { | ||||||
|  |         self.delay = self.v[x] as f64; | ||||||
|  |     } | ||||||
|  |     /// |`Fx18`| Load vX into ST | ||||||
|  |     /// ```py | ||||||
|  |     /// ST = vX; | ||||||
|  |     /// ``` | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn store_sound_timer(&mut self, x: Reg) { | ||||||
|  |         self.sound = self.v[x] as f64; | ||||||
|  |     } | ||||||
|  |     /// |`Fx1e`| Add vX to I, | ||||||
|  |     /// ```py | ||||||
|  |     /// I += vX; | ||||||
|  |     /// ``` | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn add_i(&mut self, x: Reg) { | ||||||
|  |         self.i += self.v[x] as u16; | ||||||
|  |     } | ||||||
|  |     /// |`Fx29`| Load sprite for character x into I | ||||||
|  |     /// ```py | ||||||
|  |     /// I = sprite(X); | ||||||
|  |     /// ``` | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn load_sprite(&mut self, x: Reg) { | ||||||
|  |         self.i = self.font + (5 * (self.v[x] as Adr % 0x10)); | ||||||
|  |     } | ||||||
|  |     /// |`Fx33`| BCD convert X into I`[0..3]` | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn bcd_convert(&mut self, x: Reg) { | ||||||
|  |         let x = self.v[x]; | ||||||
|  |         self.screen.write(self.i.wrapping_add(2), x % 10); | ||||||
|  |         self.screen.write(self.i.wrapping_add(1), x / 10 % 10); | ||||||
|  |         self.screen.write(self.i, x / 100 % 10); | ||||||
|  |     } | ||||||
|  |     /// |`Fx55`| DMA Stor from I to registers 0..=X | ||||||
|  |     /// | ||||||
|  |     /// # Quirk | ||||||
|  |     /// The original chip-8 interpreter uses I to directly index memory, | ||||||
|  |     /// with the side effect of leaving I as I+X+1 after the transfer is done. | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn store_dma(&mut self, x: Reg) { | ||||||
|  |         let i = self.i as usize; | ||||||
|  |         for (reg, value) in self | ||||||
|  |             .screen | ||||||
|  |             .get_mut(i..=i + x) | ||||||
|  |             .unwrap_or_default() | ||||||
|  |             .iter_mut() | ||||||
|  |             .enumerate() | ||||||
|  |         { | ||||||
|  |             *value = self.v[reg] | ||||||
|  |         } | ||||||
|  |         if !self.flags.quirks.dma_inc { | ||||||
|  |             self.i += x as Adr + 1; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     /// |`Fx65`| DMA Load from I to registers 0..=X | ||||||
|  |     /// | ||||||
|  |     /// # Quirk | ||||||
|  |     /// The original chip-8 interpreter uses I to directly index memory, | ||||||
|  |     /// with the side effect of leaving I as I+X+1 after the transfer is done. | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn load_dma(&mut self, x: Reg) { | ||||||
|  |         let i = self.i as usize; | ||||||
|  |         for (reg, value) in self | ||||||
|  |             .screen | ||||||
|  |             .get(i..=i + x) | ||||||
|  |             .unwrap_or_default() | ||||||
|  |             .iter() | ||||||
|  |             .enumerate() | ||||||
|  |         { | ||||||
|  |             self.v[reg] = *value; | ||||||
|  |         } | ||||||
|  |         if !self.flags.quirks.dma_inc { | ||||||
|  |             self.i += x as Adr + 1; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// |`Fxbb`| Super Chip: Performs IO | ||||||
|  | /// | ||||||
|  | /// |opcode| effect                             | | ||||||
|  | /// |------|------------------------------------| | ||||||
|  | /// |`Fx30`| 16x16 equivalent of load_sprite    | | ||||||
|  | /// |`Fx75`| Save to "flag registers"           | | ||||||
|  | /// |`Fx85`| Load from "flag registers"         | | ||||||
|  | impl CPU { | ||||||
|  |     /// |`Fx30`| (Super-Chip) 16x16 equivalent of [CPU::load_sprite] | ||||||
|  |     /// | ||||||
|  |     /// TODO: Actually make and import the 16x font | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn load_big_sprite(&mut self, x: Reg) { | ||||||
|  |         self.i = self.font + (5 * 8) + (16 * (self.v[x] as Adr % 0x10)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// |`Fx75`| (Super-Chip) Save to "flag registers" | ||||||
|  |     /// I just chuck it in 0x0..0xf. Screw it. | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn store_flags(&mut self, x: Reg) { | ||||||
|  |         // TODO: Save these, maybe | ||||||
|  |         for (reg, value) in self | ||||||
|  |             .screen | ||||||
|  |             .get_mut(0..=x) | ||||||
|  |             .unwrap_or_default() | ||||||
|  |             .iter_mut() | ||||||
|  |             .enumerate() | ||||||
|  |         { | ||||||
|  |             *value = self.v[reg] | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// |`Fx85`| (Super-Chip) Load from "flag registers" | ||||||
|  |     /// I just chuck it in 0x0..0xf. Screw it. | ||||||
|  |     #[inline(always)] | ||||||
|  |     pub(super) fn load_flags(&mut self, x: Reg) { | ||||||
|  |         for (reg, value) in self | ||||||
|  |             .screen | ||||||
|  |             .get(0..=x) | ||||||
|  |             .unwrap_or_default() | ||||||
|  |             .iter() | ||||||
|  |             .enumerate() | ||||||
|  |         { | ||||||
|  |             self.v[reg] = *value; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,230 +0,0 @@ | |||||||
| //! A disassembler for Chip-8 opcodes |  | ||||||
| #![allow(clippy::bad_bit_mask)] |  | ||||||
| use imperative_rs::InstructionSet; |  | ||||||
| use owo_colors::{OwoColorize, Style}; |  | ||||||
| use std::fmt::Display; |  | ||||||
|  |  | ||||||
| /// Disassembles Chip-8 instructions |  | ||||||
| pub trait Disassembler { |  | ||||||
|     /// Disassemble a single instruction |  | ||||||
|     fn once(&self, insn: u16) -> String; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[allow(non_camel_case_types, non_snake_case, missing_docs)] |  | ||||||
| #[derive(Clone, Copy, Debug, InstructionSet, PartialEq, Eq)] |  | ||||||
| /// Implements a Disassembler using imperative_rs |  | ||||||
| pub enum Insn { |  | ||||||
|     // Base instruction set |  | ||||||
|     /// | 00e0 | Clear screen memory to 0s |  | ||||||
|     #[opcode = "0x00e0"] |  | ||||||
|     cls, |  | ||||||
|     /// | 00ee | Return from subroutine |  | ||||||
|     #[opcode = "0x00ee"] |  | ||||||
|     ret, |  | ||||||
|     /// | 1aaa | Jumps to an absolute address |  | ||||||
|     #[opcode = "0x1AAA"] |  | ||||||
|     jmp { A: u16 }, |  | ||||||
|     /// | 2aaa | Pushes pc onto the stack, then jumps to a |  | ||||||
|     #[opcode = "0x2AAA"] |  | ||||||
|     call { A: u16 }, |  | ||||||
|     /// | 3xbb | Skips next instruction if register X == b |  | ||||||
|     #[opcode = "0x3xBB"] |  | ||||||
|     seb { B: u8, x: usize }, |  | ||||||
|     /// | 4xbb | Skips next instruction if register X != b |  | ||||||
|     #[opcode = "0x4xBB"] |  | ||||||
|     sneb { B: u8, x: usize }, |  | ||||||
|     /// | 9XY0 | Skip next instruction if vX == vY  | |  | ||||||
|     #[opcode = "0x5xy0"] |  | ||||||
|     se { y: usize, x: usize }, |  | ||||||
|     /// | 6xbb | Loads immediate byte b into register vX |  | ||||||
|     #[opcode = "0x6xBB"] |  | ||||||
|     movb { B: u8, x: usize }, |  | ||||||
|     /// | 7xbb | Adds immediate byte b to register vX |  | ||||||
|     #[opcode = "0x7xBB"] |  | ||||||
|     addb { B: u8, x: usize }, |  | ||||||
|     /// | 8xy0 | Loads the value of y into x |  | ||||||
|     #[opcode = "0x8xy0"] |  | ||||||
|     mov { x: usize, y: usize }, |  | ||||||
|     /// | 8xy1 | Performs bitwise or of vX and vY, and stores the result in vX |  | ||||||
|     #[opcode = "0x8xy1"] |  | ||||||
|     or { y: usize, x: usize }, |  | ||||||
|     /// | 8xy2 | Performs bitwise and of vX and vY, and stores the result in vX |  | ||||||
|     #[opcode = "0x8xy2"] |  | ||||||
|     and { y: usize, x: usize }, |  | ||||||
|     /// | 8xy3 | Performs bitwise xor of vX and vY, and stores the result in vX |  | ||||||
|     #[opcode = "0x8xy3"] |  | ||||||
|     xor { y: usize, x: usize }, |  | ||||||
|     /// | 8xy4 | Performs addition of vX and vY, and stores the result in vX |  | ||||||
|     #[opcode = "0x8xy4"] |  | ||||||
|     add { y: usize, x: usize }, |  | ||||||
|     /// | 8xy5 | Performs subtraction of vX and vY, and stores the result in vX |  | ||||||
|     #[opcode = "0x8xy5"] |  | ||||||
|     sub { y: usize, x: usize }, |  | ||||||
|     /// | 8xy6 | Performs bitwise right shift of vX (or vY) |  | ||||||
|     #[opcode = "0x8xy6"] |  | ||||||
|     shr { y: usize, x: usize }, |  | ||||||
|     /// | 8xy7 | Performs subtraction of vY and vX, and stores the result in vX |  | ||||||
|     #[opcode = "0x8xy7"] |  | ||||||
|     bsub { y: usize, x: usize }, |  | ||||||
|     /// | 8xyE | Performs bitwise left shift of vX |  | ||||||
|     #[opcode = "0x8xye"] |  | ||||||
|     shl { y: usize, x: usize }, |  | ||||||
|     /// | 9XY0 | Skip next instruction if vX != vY |  | ||||||
|     #[opcode = "0x9xy0"] |  | ||||||
|     sne { y: usize, x: usize }, |  | ||||||
|     /// | Aaaa | Load address #a into register I |  | ||||||
|     #[opcode = "0xaAAA"] |  | ||||||
|     movI { A: u16 }, |  | ||||||
|     /// | Baaa | Jump to &adr + v0 |  | ||||||
|     #[opcode = "0xbAAA"] |  | ||||||
|     jmpr { A: u16 }, |  | ||||||
|     /// | Cxbb | Stores a random number & the provided byte into vX |  | ||||||
|     #[opcode = "0xcxBB"] |  | ||||||
|     rand { B: u8, x: usize }, |  | ||||||
|     /// | Dxyn | Draws n-byte sprite to the screen at coordinates (vX, vY) |  | ||||||
|     #[opcode = "0xdxyn"] |  | ||||||
|     draw { y: usize, x: usize, n: u8 }, |  | ||||||
|     /// | eX9e | Skip next instruction if key == vX |  | ||||||
|     #[opcode = "0xex9e"] |  | ||||||
|     sek { x: usize }, |  | ||||||
|     /// | eXa1 | Skip next instruction if key != vX |  | ||||||
|     #[opcode = "0xexa1"] |  | ||||||
|     snek { x: usize }, |  | ||||||
|     // | fX07 | Set vX to value in delay timer |  | ||||||
|     #[opcode = "0xfx07"] |  | ||||||
|     getdt { x: usize }, |  | ||||||
|     // | fX0a | Wait for input, store key in vX |  | ||||||
|     #[opcode = "0xfx0a"] |  | ||||||
|     waitk { x: usize }, |  | ||||||
|     // | fX15 | Set sound timer to the value in vX |  | ||||||
|     #[opcode = "0xfx15"] |  | ||||||
|     setdt { x: usize }, |  | ||||||
|     // | fX18 | set delay timer to the value in vX |  | ||||||
|     #[opcode = "0xfx18"] |  | ||||||
|     movst { x: usize }, |  | ||||||
|     // | fX1e | Add vX to I |  | ||||||
|     #[opcode = "0xfx1e"] |  | ||||||
|     addI { x: usize }, |  | ||||||
|     // | fX29 | Load sprite for character x into I |  | ||||||
|     #[opcode = "0xfx29"] |  | ||||||
|     font { x: usize }, |  | ||||||
|     // | fX33 | BCD convert X into I[0..3] |  | ||||||
|     #[opcode = "0xfx33"] |  | ||||||
|     bcd { x: usize }, |  | ||||||
|     // | fX55 | DMA Stor from I to registers 0..X |  | ||||||
|     #[opcode = "0xfx55"] |  | ||||||
|     dmao { x: usize }, |  | ||||||
|     // | fX65 | DMA Load from I to registers 0..X |  | ||||||
|     #[opcode = "0xfx65"] |  | ||||||
|     dmai { x: usize }, |  | ||||||
|  |  | ||||||
|     // Super Chip extensions |  | ||||||
|     /// | 00cN | Scroll the screen down |  | ||||||
|     #[opcode = "0x00cn"] |  | ||||||
|     scd { n: u8 }, |  | ||||||
|     /// | 00fb | Scroll the screen right |  | ||||||
|     #[opcode = "0x00fb"] |  | ||||||
|     scr, |  | ||||||
|     /// | 00fc | Scroll the screen left |  | ||||||
|     #[opcode = "0x00fc"] |  | ||||||
|     scl, |  | ||||||
|     /// | 00fd | Exit (halt and catch fire) |  | ||||||
|     #[opcode = "0x00fd"] |  | ||||||
|     halt, |  | ||||||
|     /// | 00fe | Return to low-resolution mode |  | ||||||
|     #[opcode = "0x00fe"] |  | ||||||
|     lores, |  | ||||||
|     /// | 00ff | Enter high-resolution mode |  | ||||||
|     #[opcode = "0x00ff"] |  | ||||||
|     hires, |  | ||||||
|     /// | fx30 | Enter high-resolution mode |  | ||||||
|     #[opcode = "0xfx30"] |  | ||||||
|     hfont { x: usize }, |  | ||||||
|     /// | fx75 | Save to "flag registers" |  | ||||||
|     #[opcode = "0xfx75"] |  | ||||||
|     flgo { x: usize }, |  | ||||||
|     /// | fx85 | Load from "flag registers" |  | ||||||
|     #[opcode = "0xfx85"] |  | ||||||
|     flgi { x: usize }, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Display for Insn { |  | ||||||
|     #[rustfmt::skip] |  | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |  | ||||||
|         match self { |  | ||||||
|             // Base instruction set |  | ||||||
|             Insn::cls              => write!(f, "cls    "), |  | ||||||
|             Insn::ret              => write!(f, "ret    "), |  | ||||||
|             Insn::jmp { A }        => write!(f, "jmp    {A:03x}"), |  | ||||||
|             Insn::call { A }       => write!(f, "call   {A:03x}"), |  | ||||||
|             Insn::seb { B, x }     => write!(f, "se     #{B:02x}, v{x:X}"), |  | ||||||
|             Insn::sneb { B, x }    => write!(f, "sne    #{B:02x}, v{x:X}"), |  | ||||||
|             Insn::se { y, x }      => write!(f, "se     v{y:X}, v{x:X}"), |  | ||||||
|             Insn::movb { B, x }    => write!(f, "mov    #{B:02x}, v{x:X}"), |  | ||||||
|             Insn::addb { B, x }    => write!(f, "add    #{B:02x}, v{x:X}"), |  | ||||||
|             Insn::mov { x, y }     => write!(f, "mov    v{y:X}, v{x:X}"), |  | ||||||
|             Insn::or { y, x }      => write!(f, "or     v{y:X}, v{x:X}"), |  | ||||||
|             Insn::and { y, x }     => write!(f, "and    v{y:X}, v{x:X}"), |  | ||||||
|             Insn::xor { y, x }     => write!(f, "xor    v{y:X}, v{x:X}"), |  | ||||||
|             Insn::add { y, x }     => write!(f, "add    v{y:X}, v{x:X}"), |  | ||||||
|             Insn::sub { y, x }     => write!(f, "sub    v{y:X}, v{x:X}"), |  | ||||||
|             Insn::shr { y, x }     => write!(f, "shr    v{y:X}, v{x:X}"), |  | ||||||
|             Insn::bsub { y, x }    => write!(f, "bsub   v{y:X}, v{x:X}"), |  | ||||||
|             Insn::shl { y, x }     => write!(f, "shl    v{y:X}, v{x:X}"), |  | ||||||
|             Insn::sne { y, x }     => write!(f, "sne    v{y:X}, v{x:X}"), |  | ||||||
|             Insn::movI { A }       => write!(f, "mov    ${A:03x}, I"), |  | ||||||
|             Insn::jmpr { A }       => write!(f, "jmp    ${A:03x}+v0"), |  | ||||||
|             Insn::rand { B, x }    => write!(f, "rand   #{B:02x}, v{x:X}"), |  | ||||||
|             Insn::draw { y, x, n } => write!(f, "draw   #{n:x}, v{x:X}, v{y:X}"), |  | ||||||
|             Insn::sek { x }        => write!(f, "sek    v{x:X}"), |  | ||||||
|             Insn::snek { x }       => write!(f, "snek   v{x:X}"), |  | ||||||
|             Insn::getdt { x }      => write!(f, "mov    DT, v{x:X}"), |  | ||||||
|             Insn::waitk { x }      => write!(f, "waitk  v{x:X}"), |  | ||||||
|             Insn::setdt { x }      => write!(f, "mov    v{x:X}, DT"), |  | ||||||
|             Insn::movst { x }      => write!(f, "mov    v{x:X}, ST"), |  | ||||||
|             Insn::addI { x }       => write!(f, "add    v{x:X}, I"), |  | ||||||
|             Insn::font { x }       => write!(f, "font   v{x:X}, I"), |  | ||||||
|             Insn::bcd { x }        => write!(f, "bcd    v{x:X}, &I"), |  | ||||||
|             Insn::dmao { x }       => write!(f, "dmao   v{x:X}"), |  | ||||||
|             Insn::dmai { x }       => write!(f, "dmai   v{x:X}"), |  | ||||||
|             // Super Chip extensions |  | ||||||
|             Insn::scd { n }        => write!(f, "scd    #{n:x}"), |  | ||||||
|             Insn::scr              => write!(f, "scr    "), |  | ||||||
|             Insn::scl              => write!(f, "scl    "), |  | ||||||
|             Insn::halt             => write!(f, "halt   "), |  | ||||||
|             Insn::lores            => write!(f, "lores  "), |  | ||||||
|             Insn::hires            => write!(f, "hires  "), |  | ||||||
|             Insn::hfont { x }      => write!(f, "hfont  v{x:X}"), |  | ||||||
|             Insn::flgo { x }       => write!(f, "flgo   v{x:X}"), |  | ||||||
|             Insn::flgi { x }       => write!(f, "flgi   v{x:X}"), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Disassembles Chip-8 instructions, printing them in the provided [owo_colors::Style]s |  | ||||||
| #[derive(Clone, Copy, Debug, PartialEq)] |  | ||||||
| pub struct Dis { |  | ||||||
|     /// Styles invalid instructions |  | ||||||
|     pub invalid: Style, |  | ||||||
|     /// Styles valid instruction |  | ||||||
|     pub normal: Style, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Default for Dis { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self { |  | ||||||
|             invalid: Style::new().bold().red(), |  | ||||||
|             normal: Style::new().green(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Disassembler for Dis { |  | ||||||
|     fn once(&self, insn: u16) -> String { |  | ||||||
|         if let Ok((_, insn)) = Insn::decode(&insn.to_be_bytes()) { |  | ||||||
|             format!("{}", insn.style(self.normal)) |  | ||||||
|         } else { |  | ||||||
|             format!("{}", format_args!("inval  {insn:04x}").style(self.invalid)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,632 +1,221 @@ | |||||||
| // (c) 2023 John A. Breaux | // (c) 2023 John A. Breaux | ||||||
| // This code is licensed under MIT license (see LICENSE.txt for details) | // This code is licensed under MIT license (see LICENSE for details) | ||||||
|  | #![allow(clippy::bad_bit_mask)] | ||||||
|  | //! Contains the definition of a Chip-8 [Insn] | ||||||
|  |  | ||||||
| //! Contains implementations for each [Insn] as private member functions of [CPU] | pub mod disassembler; | ||||||
|  |  | ||||||
| use super::*; | use imperative_rs::InstructionSet; | ||||||
|  | use std::fmt::Display; | ||||||
|  |  | ||||||
| impl CPU { |  | ||||||
|     /// Executes a single [Insn] | #[allow(non_camel_case_types, non_snake_case, missing_docs)] | ||||||
|     #[inline(always)] | #[derive(Clone, Copy, Debug, InstructionSet, PartialEq, Eq)] | ||||||
|  | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] | ||||||
|  | /// Implements a Disassembler using imperative_rs | ||||||
|  | pub enum Insn { | ||||||
|  |     // Base instruction set | ||||||
|  |     /// | 00e0 | Clear screen memory to 0s | ||||||
|  |     #[opcode = "0x00e0"] | ||||||
|  |     cls, | ||||||
|  |     /// | 00ee | Return from subroutine | ||||||
|  |     #[opcode = "0x00ee"] | ||||||
|  |     ret, | ||||||
|  |     /// | 1aaa | Jumps to an absolute address | ||||||
|  |     #[opcode = "0x1AAA"] | ||||||
|  |     jmp { A: u16 }, | ||||||
|  |     /// | 2aaa | Pushes pc onto the stack, then jumps to a | ||||||
|  |     #[opcode = "0x2AAA"] | ||||||
|  |     call { A: u16 }, | ||||||
|  |     /// | 3xbb | Skips next instruction if register X == b | ||||||
|  |     #[opcode = "0x3xBB"] | ||||||
|  |     seb { B: u8, x: usize }, | ||||||
|  |     /// | 4xbb | Skips next instruction if register X != b | ||||||
|  |     #[opcode = "0x4xBB"] | ||||||
|  |     sneb { B: u8, x: usize }, | ||||||
|  |     /// | 9XY0 | Skip next instruction if vX == vY  | | ||||||
|  |     #[opcode = "0x5xy0"] | ||||||
|  |     se { y: usize, x: usize }, | ||||||
|  |     /// | 6xbb | Loads immediate byte b into register vX | ||||||
|  |     #[opcode = "0x6xBB"] | ||||||
|  |     movb { B: u8, x: usize }, | ||||||
|  |     /// | 7xbb | Adds immediate byte b to register vX | ||||||
|  |     #[opcode = "0x7xBB"] | ||||||
|  |     addb { B: u8, x: usize }, | ||||||
|  |     /// | 8xy0 | Loads the value of y into x | ||||||
|  |     #[opcode = "0x8xy0"] | ||||||
|  |     mov { x: usize, y: usize }, | ||||||
|  |     /// | 8xy1 | Performs bitwise or of vX and vY, and stores the result in vX | ||||||
|  |     #[opcode = "0x8xy1"] | ||||||
|  |     or { y: usize, x: usize }, | ||||||
|  |     /// | 8xy2 | Performs bitwise and of vX and vY, and stores the result in vX | ||||||
|  |     #[opcode = "0x8xy2"] | ||||||
|  |     and { y: usize, x: usize }, | ||||||
|  |     /// | 8xy3 | Performs bitwise xor of vX and vY, and stores the result in vX | ||||||
|  |     #[opcode = "0x8xy3"] | ||||||
|  |     xor { y: usize, x: usize }, | ||||||
|  |     /// | 8xy4 | Performs addition of vX and vY, and stores the result in vX | ||||||
|  |     #[opcode = "0x8xy4"] | ||||||
|  |     add { y: usize, x: usize }, | ||||||
|  |     /// | 8xy5 | Performs subtraction of vX and vY, and stores the result in vX | ||||||
|  |     #[opcode = "0x8xy5"] | ||||||
|  |     sub { y: usize, x: usize }, | ||||||
|  |     /// | 8xy6 | Performs bitwise right shift of vX (or vY) | ||||||
|  |     #[opcode = "0x8xy6"] | ||||||
|  |     shr { y: usize, x: usize }, | ||||||
|  |     /// | 8xy7 | Performs subtraction of vY and vX, and stores the result in vX | ||||||
|  |     #[opcode = "0x8xy7"] | ||||||
|  |     bsub { y: usize, x: usize }, | ||||||
|  |     /// | 8xyE | Performs bitwise left shift of vX | ||||||
|  |     #[opcode = "0x8xye"] | ||||||
|  |     shl { y: usize, x: usize }, | ||||||
|  |     /// | 9XY0 | Skip next instruction if vX != vY | ||||||
|  |     #[opcode = "0x9xy0"] | ||||||
|  |     sne { y: usize, x: usize }, | ||||||
|  |     /// | Aaaa | Load address #a into register I | ||||||
|  |     #[opcode = "0xaAAA"] | ||||||
|  |     movI { A: u16 }, | ||||||
|  |     /// | Baaa | Jump to &adr + v0 | ||||||
|  |     #[opcode = "0xbAAA"] | ||||||
|  |     jmpr { A: u16 }, | ||||||
|  |     /// | Cxbb | Stores a random number & the provided byte into vX | ||||||
|  |     #[opcode = "0xcxBB"] | ||||||
|  |     rand { B: u8, x: usize }, | ||||||
|  |     /// | Dxyn | Draws n-byte sprite to the screen at coordinates (vX, vY) | ||||||
|  |     #[opcode = "0xdxyn"] | ||||||
|  |     draw { y: usize, x: usize, n: u8 }, | ||||||
|  |     /// | eX9e | Skip next instruction if key == vX | ||||||
|  |     #[opcode = "0xex9e"] | ||||||
|  |     sek { x: usize }, | ||||||
|  |     /// | eXa1 | Skip next instruction if key != vX | ||||||
|  |     #[opcode = "0xexa1"] | ||||||
|  |     snek { x: usize }, | ||||||
|  |     /// | fX07 | Set vX to value in delay timer | ||||||
|  |     #[opcode = "0xfx07"] | ||||||
|  |     getdt { x: usize }, | ||||||
|  |     /// | fX0a | Wait for input, store key in vX | ||||||
|  |     #[opcode = "0xfx0a"] | ||||||
|  |     waitk { x: usize }, | ||||||
|  |     /// | fX15 | Set sound timer to the value in vX | ||||||
|  |     #[opcode = "0xfx15"] | ||||||
|  |     setdt { x: usize }, | ||||||
|  |     /// | fX18 | set delay timer to the value in vX | ||||||
|  |     #[opcode = "0xfx18"] | ||||||
|  |     movst { x: usize }, | ||||||
|  |     /// | fX1e | Add vX to I | ||||||
|  |     #[opcode = "0xfx1e"] | ||||||
|  |     addI { x: usize }, | ||||||
|  |     /// | fX29 | Load sprite for character x into I | ||||||
|  |     #[opcode = "0xfx29"] | ||||||
|  |     font { x: usize }, | ||||||
|  |     /// | fX33 | BCD convert X into I[0..3] | ||||||
|  |     #[opcode = "0xfx33"] | ||||||
|  |     bcd { x: usize }, | ||||||
|  |     /// | fX55 | DMA Stor from I to registers 0..X | ||||||
|  |     #[opcode = "0xfx55"] | ||||||
|  |     dmao { x: usize }, | ||||||
|  |     /// | fX65 | DMA Load from I to registers 0..X | ||||||
|  |     #[opcode = "0xfx65"] | ||||||
|  |     dmai { x: usize }, | ||||||
|  |  | ||||||
|  |     // Super Chip extensions | ||||||
|  |     /// | 00cN | Scroll the screen down | ||||||
|  |     #[opcode = "0x00cn"] | ||||||
|  |     scd { n: u8 }, | ||||||
|  |     /// | 00fb | Scroll the screen right | ||||||
|  |     #[opcode = "0x00fb"] | ||||||
|  |     scr, | ||||||
|  |     /// | 00fc | Scroll the screen left | ||||||
|  |     #[opcode = "0x00fc"] | ||||||
|  |     scl, | ||||||
|  |     /// | 00fd | Exit (halt and catch fire) | ||||||
|  |     #[opcode = "0x00fd"] | ||||||
|  |     halt, | ||||||
|  |     /// | 00fe | Return to low-resolution mode | ||||||
|  |     #[opcode = "0x00fe"] | ||||||
|  |     lores, | ||||||
|  |     /// | 00ff | Enter high-resolution mode | ||||||
|  |     #[opcode = "0x00ff"] | ||||||
|  |     hires, | ||||||
|  |     /// | fx30 | Enter high-resolution mode | ||||||
|  |     #[opcode = "0xfx30"] | ||||||
|  |     hfont { x: usize }, | ||||||
|  |     /// | fx75 | Save to "flag registers" | ||||||
|  |     #[opcode = "0xfx75"] | ||||||
|  |     flgo { x: usize }, | ||||||
|  |     /// | fx85 | Load from "flag registers" | ||||||
|  |     #[opcode = "0xfx85"] | ||||||
|  |     flgi { x: usize }, | ||||||
|  |  | ||||||
|  |     // XO-Chip instructions | ||||||
|  |     /// | 00dN | Scroll the screen up | ||||||
|  |     #[opcode = "0x00dn"] | ||||||
|  |     scu { n: u8 }, | ||||||
|  |     /// | 5XY2 | DMA Load from I to vX..vY | ||||||
|  |     #[opcode = "0x5xy2"] | ||||||
|  |     dmaro { y: usize, x: usize }, | ||||||
|  |     /// | 5XY3 | DMA Load from I to vX..vY | ||||||
|  |     #[opcode = "0x5xy3"] | ||||||
|  |     dmari { y: usize, x: usize }, | ||||||
|  |     /// | F000 | Load long address into character I | ||||||
|  |     #[opcode = "0xf000_iiii"] | ||||||
|  |     long { i: usize }, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Display for Insn { | ||||||
|     #[rustfmt::skip] |     #[rustfmt::skip] | ||||||
|     pub(super) fn execute(&mut self, bus: &mut Bus, instruction: Insn) { |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|         match instruction { |         match self { | ||||||
|             // Core Chip-8 instructions |             // Base instruction set | ||||||
|             Insn::cls               => self.clear_screen(bus), |             Insn::cls               => write!(f, "cls    "), | ||||||
|             Insn::ret               => self.ret(), |             Insn::ret               => write!(f, "ret    "), | ||||||
|             Insn::jmp   {       A } => self.jump(A), |             Insn::jmp { A }         => write!(f, "jmp    {A:03x}"), | ||||||
|             Insn::call  {       A } => self.call(A), |             Insn::call { A }        => write!(f, "call   {A:03x}"), | ||||||
|             Insn::seb   {    x, B } => self.skip_equals_immediate(x, B), |             Insn::seb { B, x }      => write!(f, "se     #{B:02x}, v{x:X}"), | ||||||
|             Insn::sneb  {    x, B } => self.skip_not_equals_immediate(x, B), |             Insn::sneb { B, x }     => write!(f, "sne    #{B:02x}, v{x:X}"), | ||||||
|             Insn::se    { y, x    } => self.skip_equals(x, y), |             Insn::se { y, x }       => write!(f, "se     v{y:X}, v{x:X}"), | ||||||
|             Insn::movb  {    x, B } => self.load_immediate(x, B), |             Insn::movb { B, x }     => write!(f, "mov    #{B:02x}, v{x:X}"), | ||||||
|             Insn::addb  {    x, B } => self.add_immediate(x, B), |             Insn::addb { B, x }     => write!(f, "add    #{B:02x}, v{x:X}"), | ||||||
|             Insn::mov   { y, x    } => self.load(x, y), |             Insn::mov { x, y }      => write!(f, "mov    v{y:X}, v{x:X}"), | ||||||
|             Insn::or    { y, x    } => self.or(x, y), |             Insn::or { y, x }       => write!(f, "or     v{y:X}, v{x:X}"), | ||||||
|             Insn::and   { y, x    } => self.and(x, y), |             Insn::and { y, x }      => write!(f, "and    v{y:X}, v{x:X}"), | ||||||
|             Insn::xor   { y, x    } => self.xor(x, y), |             Insn::xor { y, x }      => write!(f, "xor    v{y:X}, v{x:X}"), | ||||||
|             Insn::add   { y, x    } => self.add(x, y), |             Insn::add { y, x }      => write!(f, "add    v{y:X}, v{x:X}"), | ||||||
|             Insn::sub   { y, x    } => self.sub(x, y), |             Insn::sub { y, x }      => write!(f, "sub    v{y:X}, v{x:X}"), | ||||||
|             Insn::shr   { y, x    } => self.shift_right(x, y), |             Insn::shr { y, x }      => write!(f, "shr    v{y:X}, v{x:X}"), | ||||||
|             Insn::bsub  { y, x    } => self.backwards_sub(x, y), |             Insn::bsub { y, x }     => write!(f, "bsub   v{y:X}, v{x:X}"), | ||||||
|             Insn::shl   { y, x    } => self.shift_left(x, y), |             Insn::shl { y, x }      => write!(f, "shl    v{y:X}, v{x:X}"), | ||||||
|             Insn::sne   { y, x    } => self.skip_not_equals(x, y), |             Insn::sne { y, x }      => write!(f, "sne    v{y:X}, v{x:X}"), | ||||||
|             Insn::movI  {       A } => self.load_i_immediate(A), |             Insn::movI { A }        => write!(f, "mov    ${A:03x}, I"), | ||||||
|             Insn::jmpr  {       A } => self.jump_indexed(A), |             Insn::jmpr { A }        => write!(f, "jmp    ${A:03x}+v0"), | ||||||
|             Insn::rand  {    x, B } => self.rand(x, B), |             Insn::rand { B, x }     => write!(f, "rand   #{B:02x}, v{x:X}"), | ||||||
|             Insn::draw  { y, x, n } => self.draw(x, y, n, bus), |             Insn::draw { y, x, n }  => write!(f, "draw   #{n:x}, v{x:X}, v{y:X}"), | ||||||
|             Insn::sek   {    x    } => self.skip_key_equals(x), |             Insn::sek { x }         => write!(f, "sek    v{x:X}"), | ||||||
|             Insn::snek  {    x    } => self.skip_key_not_equals(x), |             Insn::snek { x }        => write!(f, "snek   v{x:X}"), | ||||||
|             Insn::getdt {    x    } => self.load_delay_timer(x), |             Insn::getdt { x }       => write!(f, "mov    DT, v{x:X}"), | ||||||
|             Insn::waitk {    x    } => self.wait_for_key(x), |             Insn::waitk { x }       => write!(f, "waitk  v{x:X}"), | ||||||
|             Insn::setdt {    x    } => self.store_delay_timer(x), |             Insn::setdt { x }       => write!(f, "mov    v{x:X}, DT"), | ||||||
|             Insn::movst {    x    } => self.store_sound_timer(x), |             Insn::movst { x }       => write!(f, "mov    v{x:X}, ST"), | ||||||
|             Insn::addI  {    x    } => self.add_i(x), |             Insn::addI { x }        => write!(f, "add    v{x:X}, I"), | ||||||
|             Insn::font  {    x    } => self.load_sprite(x), |             Insn::font { x }        => write!(f, "font   v{x:X}, I"), | ||||||
|             Insn::bcd   {    x    } => self.bcd_convert(x, bus), |             Insn::bcd { x }         => write!(f, "bcd    v{x:X}, &I"), | ||||||
|             Insn::dmao  {    x    } => self.store_dma(x, bus), |             Insn::dmao { x }        => write!(f, "dmao   v{x:X}"), | ||||||
|             Insn::dmai  {    x    } => self.load_dma(x, bus), |             Insn::dmai { x }        => write!(f, "dmai   v{x:X}"), | ||||||
|             // Super-Chip extensions |             // Super Chip extensions | ||||||
|             Insn::scd   {       n } => self.scroll_down(n, bus), |             Insn::scd { n }         => write!(f, "scd    #{n:x}"), | ||||||
|             Insn::scr               => self.scroll_right(bus), |             Insn::scr               => write!(f, "scr    "), | ||||||
|             Insn::scl               => self.scroll_left(bus), |             Insn::scl               => write!(f, "scl    "), | ||||||
|             Insn::halt              => self.flags.pause(), |             Insn::halt              => write!(f, "halt   "), | ||||||
|             Insn::lores             => self.init_lores(bus), |             Insn::lores             => write!(f, "lores  "), | ||||||
|             Insn::hires             => self.init_hires(bus), |             Insn::hires             => write!(f, "hires  "), | ||||||
|             Insn::hfont {    x    } => self.load_big_sprite(x), |             Insn::hfont { x }       => write!(f, "hfont  v{x:X}"), | ||||||
|             Insn::flgo  {    x    } => self.store_flags(x, bus), |             Insn::flgo { x }        => write!(f, "flgo   v{x:X}"), | ||||||
|             Insn::flgi  {    x    } => self.load_flags(x, bus), |             Insn::flgi { x }        => write!(f, "flgi   v{x:X}"), | ||||||
|         } |             // XO-Chip extensions | ||||||
|     } |             Insn::scu { n }         => write!(f, "scu    #{n:x}"), | ||||||
| } |             Insn::dmaro { y, x }    => write!(f, "dmaro  v{x:X}..v{y:X}"), | ||||||
|  |             Insn::dmari { y, x }    => write!(f, "dmari  v{x:X}..v{y:X}"), | ||||||
| // |`0aaa`| Issues a "System call" (ML routine) |             Insn::long { i }        => write!(f, "long   ${i:04x}"), | ||||||
| // |         }   | ||||||
| // |opcode| effect                             | |  | ||||||
| // |------|------------------------------------| |  | ||||||
| // |`00e0`| Clear screen memory to all 0       | |  | ||||||
| // |`00ee`| Return from subroutine             | |  | ||||||
| impl CPU { |  | ||||||
|     /// |`00e0`| Clears the screen memory to 0 |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn clear_screen(&mut self, bus: &mut Bus) { |  | ||||||
|         bus.clear_region(Region::Screen); |  | ||||||
|     } |  | ||||||
|     /// |`00ee`| Returns from subroutine |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn ret(&mut self) { |  | ||||||
|         self.pc = self.stack.pop().unwrap_or(0x200); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`1aaa`| Sets pc to an absolute address |  | ||||||
| impl CPU { |  | ||||||
|     /// |`1aaa`| Sets the program counter to an absolute address |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn jump(&mut self, a: Adr) { |  | ||||||
|         // jump to self == halt |  | ||||||
|         if a.wrapping_add(2) == self.pc { |  | ||||||
|             self.flags.pause = true; |  | ||||||
|         } |  | ||||||
|         self.pc = a; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`2aaa`| Pushes pc onto the stack, then jumps to a |  | ||||||
| impl CPU { |  | ||||||
|     /// |`2aaa`| Pushes pc onto the stack, then jumps to a |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn call(&mut self, a: Adr) { |  | ||||||
|         self.stack.push(self.pc); |  | ||||||
|         self.pc = a; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`3xbb`| Skips next instruction if register X == b |  | ||||||
| impl CPU { |  | ||||||
|     /// |`3xbb`| Skips the next instruction if register X == b |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn skip_equals_immediate(&mut self, x: Reg, b: u8) { |  | ||||||
|         if self.v[x] == b { |  | ||||||
|             self.pc = self.pc.wrapping_add(2); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`4xbb`| Skips next instruction if register X != b |  | ||||||
| impl CPU { |  | ||||||
|     /// |`4xbb`| Skips the next instruction if register X != b |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn skip_not_equals_immediate(&mut self, x: Reg, b: u8) { |  | ||||||
|         if self.v[x] != b { |  | ||||||
|             self.pc = self.pc.wrapping_add(2); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`5xyn`| Performs a register-register comparison |  | ||||||
| // |  | ||||||
| // |opcode| effect                             | |  | ||||||
| // |------|------------------------------------| |  | ||||||
| // |`5XY0`| Skip next instruction if vX == vY  | |  | ||||||
| impl CPU { |  | ||||||
|     /// |`5xy0`| Skips the next instruction if register X != register Y |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn skip_equals(&mut self, x: Reg, y: Reg) { |  | ||||||
|         if self.v[x] == self.v[y] { |  | ||||||
|             self.pc = self.pc.wrapping_add(2); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`6xbb`| Loads immediate byte b into register vX |  | ||||||
| impl CPU { |  | ||||||
|     /// |`6xbb`| Loads immediate byte b into register vX |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn load_immediate(&mut self, x: Reg, b: u8) { |  | ||||||
|         self.v[x] = b; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`7xbb`| Adds immediate byte b to register vX |  | ||||||
| impl CPU { |  | ||||||
|     /// |`7xbb`| Adds immediate byte b to register vX |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn add_immediate(&mut self, x: Reg, b: u8) { |  | ||||||
|         self.v[x] = self.v[x].wrapping_add(b); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`8xyn`| Performs ALU operation |  | ||||||
| // |  | ||||||
| // |opcode| effect                             | |  | ||||||
| // |------|------------------------------------| |  | ||||||
| // |`8xy0`| Y = X                              | |  | ||||||
| // |`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                         | |  | ||||||
| impl CPU { |  | ||||||
|     /// |`8xy0`| Loads the value of y into x |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn load(&mut self, x: Reg, y: Reg) { |  | ||||||
|         self.v[x] = self.v[y]; |  | ||||||
|     } |  | ||||||
|     /// |`8xy1`| Performs bitwise or of vX and vY, and stores the result in vX |  | ||||||
|     /// |  | ||||||
|     /// # Quirk |  | ||||||
|     /// The original chip-8 interpreter will clobber vF for any 8-series instruction |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn or(&mut self, x: Reg, y: Reg) { |  | ||||||
|         self.v[x] |= self.v[y]; |  | ||||||
|         if !self.flags.quirks.bin_ops { |  | ||||||
|             self.v[0xf] = 0; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     /// |`8xy2`| Performs bitwise and of vX and vY, and stores the result in vX |  | ||||||
|     /// |  | ||||||
|     /// # Quirk |  | ||||||
|     /// The original chip-8 interpreter will clobber vF for any 8-series instruction |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn and(&mut self, x: Reg, y: Reg) { |  | ||||||
|         self.v[x] &= self.v[y]; |  | ||||||
|         if !self.flags.quirks.bin_ops { |  | ||||||
|             self.v[0xf] = 0; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     /// |`8xy3`| Performs bitwise xor of vX and vY, and stores the result in vX |  | ||||||
|     /// |  | ||||||
|     /// # Quirk |  | ||||||
|     /// The original chip-8 interpreter will clobber vF for any 8-series instruction |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn xor(&mut self, x: Reg, y: Reg) { |  | ||||||
|         self.v[x] ^= self.v[y]; |  | ||||||
|         if !self.flags.quirks.bin_ops { |  | ||||||
|             self.v[0xf] = 0; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     /// |`8xy4`| Performs addition of vX and vY, and stores the result in vX |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn add(&mut self, x: Reg, y: Reg) { |  | ||||||
|         let carry; |  | ||||||
|         (self.v[x], carry) = self.v[x].overflowing_add(self.v[y]); |  | ||||||
|         self.v[0xf] = carry.into(); |  | ||||||
|     } |  | ||||||
|     /// |`8xy5`| Performs subtraction of vX and vY, and stores the result in vX |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn sub(&mut self, x: Reg, y: Reg) { |  | ||||||
|         let carry; |  | ||||||
|         (self.v[x], carry) = self.v[x].overflowing_sub(self.v[y]); |  | ||||||
|         self.v[0xf] = (!carry).into(); |  | ||||||
|     } |  | ||||||
|     /// |`8xy6`| Performs bitwise right shift of vX |  | ||||||
|     /// |  | ||||||
|     /// # Quirk |  | ||||||
|     /// On the original chip-8 interpreter, this shifts vY and stores the result in vX |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn shift_right(&mut self, x: Reg, y: Reg) { |  | ||||||
|         let src: Reg = if self.flags.quirks.shift { x } else { y }; |  | ||||||
|         let shift_out = self.v[src] & 1; |  | ||||||
|         self.v[x] = self.v[src] >> 1; |  | ||||||
|         self.v[0xf] = shift_out; |  | ||||||
|     } |  | ||||||
|     /// |`8xy7`| Performs subtraction of vY and vX, and stores the result in vX |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn backwards_sub(&mut self, x: Reg, y: Reg) { |  | ||||||
|         let carry; |  | ||||||
|         (self.v[x], carry) = self.v[y].overflowing_sub(self.v[x]); |  | ||||||
|         self.v[0xf] = (!carry).into(); |  | ||||||
|     } |  | ||||||
|     /// 8X_E: Performs bitwise left shift of vX |  | ||||||
|     /// |  | ||||||
|     /// # Quirk |  | ||||||
|     /// On the original chip-8 interpreter, this would perform the operation on vY |  | ||||||
|     /// and store the result in vX. This behavior was left out, for now. |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn shift_left(&mut self, x: Reg, y: Reg) { |  | ||||||
|         let src: Reg = if self.flags.quirks.shift { x } else { y }; |  | ||||||
|         let shift_out: u8 = self.v[src] >> 7; |  | ||||||
|         self.v[x] = self.v[src] << 1; |  | ||||||
|         self.v[0xf] = shift_out; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`9xyn`| Performs a register-register comparison |  | ||||||
| // |  | ||||||
| // |opcode| effect                             | |  | ||||||
| // |------|------------------------------------| |  | ||||||
| // |`9XY0`| Skip next instruction if vX != vY  | |  | ||||||
| impl CPU { |  | ||||||
|     /// |`9xy0`| Skip next instruction if X != y |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn skip_not_equals(&mut self, x: Reg, y: Reg) { |  | ||||||
|         if self.v[x] != self.v[y] { |  | ||||||
|             self.pc = self.pc.wrapping_add(2); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`Aaaa`| Load address #a into register I |  | ||||||
| impl CPU { |  | ||||||
|     /// |`Aadr`| Load address #adr into register I |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn load_i_immediate(&mut self, a: Adr) { |  | ||||||
|         self.i = a; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`Baaa`| Jump to &adr + v0 |  | ||||||
| impl CPU { |  | ||||||
|     /// |`Badr`| Jump to &adr + v0 |  | ||||||
|     /// |  | ||||||
|     /// Quirk: |  | ||||||
|     /// On the Super-Chip, this does stupid shit |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn jump_indexed(&mut self, a: Adr) { |  | ||||||
|         let reg = if self.flags.quirks.stupid_jumps { |  | ||||||
|             a as usize >> 8 |  | ||||||
|         } else { |  | ||||||
|             0 |  | ||||||
|         }; |  | ||||||
|         self.pc = a.wrapping_add(self.v[reg] as Adr); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`Cxbb`| Stores a random number & the provided byte into vX |  | ||||||
| impl CPU { |  | ||||||
|     /// |`Cxbb`| Stores a random number & the provided byte into vX |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn rand(&mut self, x: Reg, b: u8) { |  | ||||||
|         self.v[x] = random::<u8>() & b; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`Dxyn`| Draws n-byte sprite to the screen at coordinates (vX, vY) |  | ||||||
| impl CPU { |  | ||||||
|     /// |`Dxyn`| Draws n-byte sprite to the screen at coordinates (vX, vY) |  | ||||||
|     /// |  | ||||||
|     /// # Quirk |  | ||||||
|     /// On the original chip-8 interpreter, this will wait for a VBI |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { |  | ||||||
|         if !self.flags.quirks.draw_wait { |  | ||||||
|             self.flags.draw_wait = true; |  | ||||||
|         } |  | ||||||
|         // self.draw_hires handles both hi-res mode and drawing 16x16 sprites |  | ||||||
|         if self.flags.draw_mode || n == 0 { |  | ||||||
|             self.draw_hires(x, y, n, bus); |  | ||||||
|         } else { |  | ||||||
|             self.draw_lores(x, y, n, bus); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn draw_lores(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { |  | ||||||
|         self.draw_sprite(self.v[x] as u16 % 64, self.v[y] as u16 % 32, n, 64, 32, bus); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn draw_sprite(&mut self, x: u16, y: u16, n: Nib, w: u16, h: u16, bus: &mut Bus) { |  | ||||||
|         let w_bytes = w / 8; |  | ||||||
|         self.v[0xf] = 0; |  | ||||||
|         if let Some(sprite) = bus.get(self.i as usize..(self.i + n as u16) as usize) { |  | ||||||
|             let sprite = sprite.to_vec(); |  | ||||||
|             for (line, &sprite) in sprite.iter().enumerate() { |  | ||||||
|                 let line = line as u16; |  | ||||||
|                 let sprite = ((sprite as u16) << (8 - (x % 8))).to_be_bytes(); |  | ||||||
|                 for (addr, &byte) in sprite.iter().enumerate().filter_map(|(idx, byte)| { |  | ||||||
|                     let x = (x / 8) + idx as u16; |  | ||||||
|                     Some(( |  | ||||||
|                         if self.flags.quirks.screen_wrap { |  | ||||||
|                             ((y + line) % h * w_bytes + (x % w_bytes)) % (w_bytes * h) |  | ||||||
|                         } else if x < w_bytes { |  | ||||||
|                             (y + line) * w_bytes + x |  | ||||||
|                         } else { |  | ||||||
|                             return None; |  | ||||||
|                         } + self.screen, |  | ||||||
|                         byte, |  | ||||||
|                     )) |  | ||||||
|                 }) { |  | ||||||
|                     let screen: u8 = bus.read(addr); |  | ||||||
|                     bus.write(addr, byte ^ screen); |  | ||||||
|                     if byte & screen != 0 { |  | ||||||
|                         self.v[0xf] = 1; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`Exbb`| Skips instruction on value of keypress |  | ||||||
| // |  | ||||||
| // |opcode| effect                             | |  | ||||||
| // |------|------------------------------------| |  | ||||||
| // |`eX9e`| Skip next instruction if key == vX | |  | ||||||
| // |`eXa1`| Skip next instruction if key != vX | |  | ||||||
| impl CPU { |  | ||||||
|     /// |`Ex9E`| Skip next instruction if key == vX |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn skip_key_equals(&mut self, x: Reg) { |  | ||||||
|         if self.keys[self.v[x] as usize & 0xf] { |  | ||||||
|             self.pc += 2; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     /// |`ExaE`| Skip next instruction if key != vX |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn skip_key_not_equals(&mut self, x: Reg) { |  | ||||||
|         if !self.keys[self.v[x] as usize & 0xf] { |  | ||||||
|             self.pc += 2; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // |`Fxbb`| Performs IO |  | ||||||
| // |  | ||||||
| // |opcode| effect                             | |  | ||||||
| // |------|------------------------------------| |  | ||||||
| // |`fX07`| Set vX to value in delay timer     | |  | ||||||
| // |`fX0a`| Wait for input, store key in vX    | |  | ||||||
| // |`fX15`| Set sound timer to the value in vX | |  | ||||||
| // |`fX18`| set delay timer to the value in vX | |  | ||||||
| // |`fX1e`| Add vX 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 | |  | ||||||
| impl CPU { |  | ||||||
|     /// |`Fx07`| Get the current DT, and put it in vX |  | ||||||
|     /// ```py |  | ||||||
|     /// vX = DT |  | ||||||
|     /// ``` |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn load_delay_timer(&mut self, x: Reg) { |  | ||||||
|         self.v[x] = self.delay as u8; |  | ||||||
|     } |  | ||||||
|     /// |`Fx0A`| Wait for key, then vX = K |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn wait_for_key(&mut self, x: Reg) { |  | ||||||
|         if let Some(key) = self.flags.lastkey { |  | ||||||
|             self.v[x] = key as u8; |  | ||||||
|             self.flags.lastkey = None; |  | ||||||
|         } else { |  | ||||||
|             self.pc = self.pc.wrapping_sub(2); |  | ||||||
|             self.flags.keypause = true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     /// |`Fx15`| Load vX into DT |  | ||||||
|     /// ```py |  | ||||||
|     /// DT = vX |  | ||||||
|     /// ``` |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn store_delay_timer(&mut self, x: Reg) { |  | ||||||
|         self.delay = self.v[x] as f64; |  | ||||||
|     } |  | ||||||
|     /// |`Fx18`| Load vX into ST |  | ||||||
|     /// ```py |  | ||||||
|     /// ST = vX; |  | ||||||
|     /// ``` |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn store_sound_timer(&mut self, x: Reg) { |  | ||||||
|         self.sound = self.v[x] as f64; |  | ||||||
|     } |  | ||||||
|     /// |`Fx1e`| Add vX to I, |  | ||||||
|     /// ```py |  | ||||||
|     /// I += vX; |  | ||||||
|     /// ``` |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn add_i(&mut self, x: Reg) { |  | ||||||
|         self.i += self.v[x] as u16; |  | ||||||
|     } |  | ||||||
|     /// |`Fx29`| Load sprite for character x into I |  | ||||||
|     /// ```py |  | ||||||
|     /// I = sprite(X); |  | ||||||
|     /// ``` |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn load_sprite(&mut self, x: Reg) { |  | ||||||
|         self.i = self.font + (5 * (self.v[x] as Adr % 0x10)); |  | ||||||
|     } |  | ||||||
|     /// |`Fx33`| BCD convert X into I`[0..3]` |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn bcd_convert(&mut self, x: Reg, bus: &mut Bus) { |  | ||||||
|         let x = self.v[x]; |  | ||||||
|         bus.write(self.i.wrapping_add(2), x % 10); |  | ||||||
|         bus.write(self.i.wrapping_add(1), x / 10 % 10); |  | ||||||
|         bus.write(self.i, x / 100 % 10); |  | ||||||
|     } |  | ||||||
|     /// |`Fx55`| DMA Stor from I to registers 0..=X |  | ||||||
|     /// |  | ||||||
|     /// # Quirk |  | ||||||
|     /// The original chip-8 interpreter uses I to directly index memory, |  | ||||||
|     /// with the side effect of leaving I as I+X+1 after the transfer is done. |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn store_dma(&mut self, x: Reg, bus: &mut Bus) { |  | ||||||
|         let i = self.i as usize; |  | ||||||
|         for (reg, value) in bus |  | ||||||
|             .get_mut(i..=i + x) |  | ||||||
|             .unwrap_or_default() |  | ||||||
|             .iter_mut() |  | ||||||
|             .enumerate() |  | ||||||
|         { |  | ||||||
|             *value = self.v[reg] |  | ||||||
|         } |  | ||||||
|         if !self.flags.quirks.dma_inc { |  | ||||||
|             self.i += x as Adr + 1; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     /// |`Fx65`| DMA Load from I to registers 0..=X |  | ||||||
|     /// |  | ||||||
|     /// # Quirk |  | ||||||
|     /// The original chip-8 interpreter uses I to directly index memory, |  | ||||||
|     /// with the side effect of leaving I as I+X+1 after the transfer is done. |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn load_dma(&mut self, x: Reg, bus: &mut Bus) { |  | ||||||
|         let i = self.i as usize; |  | ||||||
|         for (reg, value) in bus.get(i..=i + x).unwrap_or_default().iter().enumerate() { |  | ||||||
|             self.v[reg] = *value; |  | ||||||
|         } |  | ||||||
|         if !self.flags.quirks.dma_inc { |  | ||||||
|             self.i += x as Adr + 1; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| //////////////// SUPER CHIP //////////////// |  | ||||||
|  |  | ||||||
| impl CPU { |  | ||||||
|     /// |`00cN`| Scroll the screen down N lines |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn scroll_down(&mut self, n: Nib, bus: &mut Bus) { |  | ||||||
|         match self.flags.draw_mode { |  | ||||||
|             true => { |  | ||||||
|                 // Get a line from the bus |  | ||||||
|                 for i in (0..16 * (64 - n as usize)).step_by(16).rev() { |  | ||||||
|                     let i = i + self.screen as usize; |  | ||||||
|                     let line: u128 = bus.read(i); |  | ||||||
|                     bus.write(i - (n as usize * 16), 0u128); |  | ||||||
|                     bus.write(i, line); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             false => { |  | ||||||
|                 // Get a line from the bus |  | ||||||
|                 for i in (0..8 * (32 - n as usize)).step_by(8).rev() { |  | ||||||
|                     let i = i + self.screen as usize; |  | ||||||
|                     let line: u64 = bus.read(i); |  | ||||||
|                     bus.write(i, 0u64); |  | ||||||
|                     bus.write(i + (n as usize * 8), line); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// |`00fb`| Scroll the screen right |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn scroll_right(&mut self, bus: &mut (impl ReadWrite<u128> + ReadWrite<u128>)) { |  | ||||||
|         // Get a line from the bus |  | ||||||
|         for i in (0..16 * 64).step_by(16) { |  | ||||||
|             //let line: u128 = bus.read(self.screen + i) >> 4; |  | ||||||
|             bus.write(self.screen + i, bus.read(self.screen + i) >> 4); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     /// |`00fc`| Scroll the screen right |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn scroll_left(&mut self, bus: &mut (impl ReadWrite<u128> + ReadWrite<u128>)) { |  | ||||||
|         // Get a line from the bus |  | ||||||
|         for i in (0..16 * 64).step_by(16) { |  | ||||||
|             let line: u128 = (bus.read(self.screen + i) & !(0xf << 124)) << 4; |  | ||||||
|             bus.write(self.screen + i, line); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// |`Dxyn`| |  | ||||||
|     /// Super-Chip extension high-resolution graphics mode |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn draw_hires(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) { |  | ||||||
|         if !self.flags.quirks.draw_wait { |  | ||||||
|             self.flags.draw_wait = true; |  | ||||||
|         } |  | ||||||
|         let (w, h) = match self.flags.draw_mode { |  | ||||||
|             true => (128, 64), |  | ||||||
|             false => (64, 32), |  | ||||||
|         }; |  | ||||||
|         let (x, y) = (self.v[x] as u16 % w, self.v[y] as u16 % h); |  | ||||||
|         match n { |  | ||||||
|             0 => self.draw_schip_sprite(x, y, w, bus), |  | ||||||
|             _ => self.draw_sprite(x, y, n, w, h, bus), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     /// Draws a 16x16 Super Chip sprite |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn draw_schip_sprite(&mut self, x: u16, y: u16, w: u16, bus: &mut Bus) { |  | ||||||
|         self.v[0xf] = 0; |  | ||||||
|         let w_bytes = w / 8; |  | ||||||
|         if let Some(sprite) = bus.get(self.i as usize..(self.i + 32) as usize) { |  | ||||||
|             let sprite = sprite.to_owned(); |  | ||||||
|             for (line, sprite) in sprite.chunks_exact(2).enumerate() { |  | ||||||
|                 let sprite = u16::from_be_bytes( |  | ||||||
|                     sprite |  | ||||||
|                         .try_into() |  | ||||||
|                         .expect("Chunks should only return 2 bytes"), |  | ||||||
|                 ); |  | ||||||
|                 let addr = (y + line as u16) * w_bytes + x / 8 + self.screen; |  | ||||||
|                 let sprite = (sprite as u32) << (16 - (x % 8)); |  | ||||||
|                 let screen: u32 = bus.read(addr); |  | ||||||
|                 bus.write(addr, screen ^ sprite); |  | ||||||
|                 if screen & sprite != 0 { |  | ||||||
|                     self.v[0xf] += 1; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// |`Fx30`| (Super-Chip) 16x16 equivalent of Fx29 |  | ||||||
|     /// |  | ||||||
|     /// TODO: Actually make and import the 16x font |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn load_big_sprite(&mut self, x: Reg) { |  | ||||||
|         self.i = self.font + (5 * 8) + (16 * (self.v[x] as Adr % 0x10)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// |`Fx75`| (Super-Chip) Save to "flag registers" |  | ||||||
|     /// I just chuck it in 0x0..0xf. Screw it. |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn store_flags(&mut self, x: Reg, bus: &mut Bus) { |  | ||||||
|         // TODO: Save these, maybe |  | ||||||
|         for (reg, value) in bus |  | ||||||
|             .get_mut(0..=x) |  | ||||||
|             .unwrap_or_default() |  | ||||||
|             .iter_mut() |  | ||||||
|             .enumerate() |  | ||||||
|         { |  | ||||||
|             *value = self.v[reg] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// |`Fx85`| (Super-Chip) Load from "flag registers" |  | ||||||
|     /// I just chuck it in 0x0..0xf. Screw it. |  | ||||||
|     #[inline(always)] |  | ||||||
|     pub(super) fn load_flags(&mut self, x: Reg, bus: &mut Bus) { |  | ||||||
|         for (reg, value) in bus.get(0..=x).unwrap_or_default().iter().enumerate() { |  | ||||||
|             self.v[reg] = *value; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Initialize lores mode |  | ||||||
|     pub(super) fn init_lores(&mut self, bus: &mut Bus) { |  | ||||||
|         self.flags.draw_mode = false; |  | ||||||
|         let scraddr = self.screen as usize; |  | ||||||
|         bus.set_region(Region::Screen, scraddr..scraddr + 256); |  | ||||||
|         self.clear_screen(bus); |  | ||||||
|     } |  | ||||||
|     /// Initialize hires mode |  | ||||||
|     pub(super) fn init_hires(&mut self, bus: &mut Bus) { |  | ||||||
|         self.flags.draw_mode = true; |  | ||||||
|         let scraddr = self.screen as usize; |  | ||||||
|         bus.set_region(Region::Screen, scraddr..scraddr + 1024); |  | ||||||
|         self.clear_screen(bus); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								src/cpu/instruction/disassembler.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/cpu/instruction/disassembler.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | // (c) 2023 John A. Breaux | ||||||
|  | // This code is licensed under MIT license (see LICENSE for details) | ||||||
|  |  | ||||||
|  | //! A disassembler for Chip-8 opcodes | ||||||
|  | use super::Insn; | ||||||
|  | use imperative_rs::InstructionSet; | ||||||
|  | use owo_colors::{OwoColorize, Style}; | ||||||
|  |  | ||||||
|  | /// Disassembles Chip-8 instructions | ||||||
|  | pub trait Disassembler { | ||||||
|  |     /// Disassemble a single instruction | ||||||
|  |     fn once(&self, insn: u16) -> String; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Disassembles Chip-8 instructions, printing them in the provided [owo_colors::Style]s | ||||||
|  | #[derive(Clone, Copy, Debug, PartialEq)] | ||||||
|  | pub struct Dis { | ||||||
|  |     /// Styles invalid instructions | ||||||
|  |     pub invalid: Style, | ||||||
|  |     /// Styles valid instruction | ||||||
|  |     pub normal: Style, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Default for Dis { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             invalid: Style::new().bold().red(), | ||||||
|  |             normal: Style::new().green(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Disassembler for Dis { | ||||||
|  |     fn once(&self, insn: u16) -> String { | ||||||
|  |         if let Ok((_, insn)) = Insn::decode(&insn.to_be_bytes()) { | ||||||
|  |             format!("{}", insn.style(self.normal)) | ||||||
|  |         } else { | ||||||
|  |             format!("{}", format_args!("inval  {insn:04x}").style(self.invalid)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -17,11 +17,12 @@ use crate::{ | |||||||
|     bus, |     bus, | ||||||
|     cpu::bus::{Bus, Region::*}, |     cpu::bus::{Bus, Region::*}, | ||||||
| }; | }; | ||||||
|  | use rand::random; | ||||||
|  |  | ||||||
| mod decode; | mod decode; | ||||||
|  |  | ||||||
| fn setup_environment() -> (CPU, Bus) { | fn setup_environment() -> (CPU, Bus) { | ||||||
|     ( |     let mut ch8 = ( | ||||||
|         CPU { |         CPU { | ||||||
|             flags: Flags { |             flags: Flags { | ||||||
|                 debug: true, |                 debug: true, | ||||||
| @@ -32,14 +33,14 @@ fn setup_environment() -> (CPU, Bus) { | |||||||
|             ..CPU::default() |             ..CPU::default() | ||||||
|         }, |         }, | ||||||
|         bus! { |         bus! { | ||||||
|             // Load the charset into ROM |  | ||||||
|             Charset [0x0050..0x00A0] = include_bytes!("../mem/charset.bin"), |  | ||||||
|             // Load the ROM file into RAM (dummy binary which contains nothing but `jmp pc+2`) |  | ||||||
|             Program [0x0200..0x1000] = include_bytes!("tests/roms/jumptest.ch8"), |  | ||||||
|             // Create a screen |             // Create a screen | ||||||
|             Screen  [0x0F00..0x1000] = include_bytes!("../../chip8Archive/roms/1dcell.ch8"), |             Screen  [0x0F00..0x1000] = include_bytes!("../../chip8Archive/roms/1dcell.ch8"), | ||||||
|         }, |         }, | ||||||
|     ) |     ); | ||||||
|  |     ch8.0 | ||||||
|  |         .load_program_bytes(include_bytes!("tests/roms/jumptest.ch8")) | ||||||
|  |         .unwrap(); | ||||||
|  |     ch8 | ||||||
| } | } | ||||||
|  |  | ||||||
| fn print_screen(bytes: &[u8]) { | fn print_screen(bytes: &[u8]) { | ||||||
| @@ -54,28 +55,28 @@ mod unimplemented { | |||||||
|     #[test] |     #[test] | ||||||
|     fn ins_5xyn() { |     fn ins_5xyn() { | ||||||
|         let (mut cpu, mut bus) = setup_environment(); |         let (mut cpu, mut bus) = setup_environment(); | ||||||
|         bus.write(0x200u16, 0x500fu16); |         cpu.screen.write(0x200u16, 0x500fu16); | ||||||
|         cpu.tick(&mut bus) |         cpu.tick(&mut bus) | ||||||
|             .expect_err("0x500f is not an instruction"); |             .expect_err("0x500f is not an instruction"); | ||||||
|     } |     } | ||||||
|     #[test] |     #[test] | ||||||
|     fn ins_8xyn() { |     fn ins_8xyn() { | ||||||
|         let (mut cpu, mut bus) = setup_environment(); |         let (mut cpu, mut bus) = setup_environment(); | ||||||
|         bus.write(0x200u16, 0x800fu16); |         cpu.screen.write(0x200u16, 0x800fu16); | ||||||
|         cpu.tick(&mut bus) |         cpu.tick(&mut bus) | ||||||
|             .expect_err("0x800f is not an instruction"); |             .expect_err("0x800f is not an instruction"); | ||||||
|     } |     } | ||||||
|     #[test] |     #[test] | ||||||
|     fn ins_9xyn() { |     fn ins_9xyn() { | ||||||
|         let (mut cpu, mut bus) = setup_environment(); |         let (mut cpu, mut bus) = setup_environment(); | ||||||
|         bus.write(0x200u16, 0x900fu16); |         cpu.screen.write(0x200u16, 0x900fu16); | ||||||
|         cpu.tick(&mut bus) |         cpu.tick(&mut bus) | ||||||
|             .expect_err("0x900f is not an instruction"); |             .expect_err("0x900f is not an instruction"); | ||||||
|     } |     } | ||||||
|     #[test] |     #[test] | ||||||
|     fn ins_exbb() { |     fn ins_exbb() { | ||||||
|         let (mut cpu, mut bus) = setup_environment(); |         let (mut cpu, mut bus) = setup_environment(); | ||||||
|         bus.write(0x200u16, 0xe00fu16); |         cpu.screen.write(0x200u16, 0xe00fu16); | ||||||
|         cpu.tick(&mut bus) |         cpu.tick(&mut bus) | ||||||
|             .expect_err("0xe00f is not an instruction"); |             .expect_err("0xe00f is not an instruction"); | ||||||
|     } |     } | ||||||
| @@ -83,7 +84,7 @@ mod unimplemented { | |||||||
|     #[test] |     #[test] | ||||||
|     fn ins_fxbb() { |     fn ins_fxbb() { | ||||||
|         let (mut cpu, mut bus) = setup_environment(); |         let (mut cpu, mut bus) = setup_environment(); | ||||||
|         bus.write(0x200u16, 0xf00fu16); |         cpu.screen.write(0x200u16, 0xf00fu16); | ||||||
|         cpu.tick(&mut bus) |         cpu.tick(&mut bus) | ||||||
|             .expect_err("0xf00f is not an instruction"); |             .expect_err("0xf00f is not an instruction"); | ||||||
|     } |     } | ||||||
| @@ -755,10 +756,10 @@ mod io { | |||||||
|                 // Debug mode is 5x slower |                 // Debug mode is 5x slower | ||||||
|                 cpu.flags.debug = false; |                 cpu.flags.debug = false; | ||||||
|                 // Load the test program |                 // Load the test program | ||||||
|                 bus = bus.load_region_owned(Program, test.program); |                 cpu.screen.load_region(Program, test.program).unwrap(); | ||||||
|                 // Run the test program for the specified number of steps |                 // Run the test program for the specified number of steps | ||||||
|                 while cpu.cycle() < test.steps { |                 while cpu.cycle() < test.steps { | ||||||
|                     cpu.multistep(&mut bus, test.steps - cpu.cycle()) |                     cpu.multistep(&mut bus, 10.min(test.steps - cpu.cycle())) | ||||||
|                         .expect("Draw tests should not contain undefined instructions"); |                         .expect("Draw tests should not contain undefined instructions"); | ||||||
|                 } |                 } | ||||||
|                 // Compare the screen to the reference screen buffer |                 // Compare the screen to the reference screen buffer | ||||||
| @@ -948,7 +949,7 @@ mod io { | |||||||
|         /// Fx29: Load sprite for character vX into I |         /// Fx29: Load sprite for character vX into I | ||||||
|         #[test] |         #[test] | ||||||
|         fn load_sprite() { |         fn load_sprite() { | ||||||
|             let (mut cpu, bus) = setup_environment(); |             let (mut cpu, _) = setup_environment(); | ||||||
|             for test in TESTS { |             for test in TESTS { | ||||||
|                 let reg = 0xf & random::<usize>(); |                 let reg = 0xf & random::<usize>(); | ||||||
|                 // load number into CPU register |                 // load number into CPU register | ||||||
| @@ -958,7 +959,8 @@ mod io { | |||||||
|  |  | ||||||
|                 let addr = cpu.i as usize; |                 let addr = cpu.i as usize; | ||||||
|                 assert_eq!( |                 assert_eq!( | ||||||
|                     bus.get(addr..addr.wrapping_add(5)) |                     cpu.screen | ||||||
|  |                         .get(addr..addr.wrapping_add(5)) | ||||||
|                         .expect("Region at addr should exist!"), |                         .expect("Region at addr should exist!"), | ||||||
|                     test.output, |                     test.output, | ||||||
|                 ); |                 ); | ||||||
| @@ -995,15 +997,18 @@ mod io { | |||||||
|         #[test] |         #[test] | ||||||
|         fn bcd_convert() { |         fn bcd_convert() { | ||||||
|             for test in BCD_TESTS { |             for test in BCD_TESTS { | ||||||
|                 let (mut cpu, mut bus) = setup_environment(); |                 let (mut cpu, _) = setup_environment(); | ||||||
|                 let addr = 0xff0 & random::<u16>() as usize; |                 let addr = 0xff0 & random::<u16>() as usize; | ||||||
|                 // load CPU registers |                 // load CPU registers | ||||||
|                 cpu.i = addr as u16; |                 cpu.i = addr as u16; | ||||||
|                 cpu.v[5] = test.input; |                 cpu.v[5] = test.input; | ||||||
|  |  | ||||||
|                 cpu.bcd_convert(5, &mut bus); |                 cpu.bcd_convert(5); | ||||||
|  |  | ||||||
|                 assert_eq!(bus.get(addr..addr.saturating_add(3)), Some(test.output)) |                 assert_eq!( | ||||||
|  |                     cpu.screen.get(addr..addr.saturating_add(3)), | ||||||
|  |                     Some(test.output) | ||||||
|  |                 ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -1012,7 +1017,7 @@ mod io { | |||||||
|     // TODO: Test with dma_inc quirk set |     // TODO: Test with dma_inc quirk set | ||||||
|     #[test] |     #[test] | ||||||
|     fn dma_store() { |     fn dma_store() { | ||||||
|         let (mut cpu, mut bus) = setup_environment(); |         let (mut cpu, _) = setup_environment(); | ||||||
|         const DATA: &[u8] = b"ABCDEFGHIJKLMNOP"; |         const DATA: &[u8] = b"ABCDEFGHIJKLMNOP"; | ||||||
|         // Load some test data into memory |         // Load some test data into memory | ||||||
|         let addr = 0x456; |         let addr = 0x456; | ||||||
| @@ -1023,9 +1028,10 @@ mod io { | |||||||
|         for len in 0..16 { |         for len in 0..16 { | ||||||
|             // Perform DMA store |             // Perform DMA store | ||||||
|             cpu.i = addr as u16; |             cpu.i = addr as u16; | ||||||
|             cpu.store_dma(len, &mut bus); |             cpu.store_dma(len); | ||||||
|             // Check that bus grabbed the correct data |             // Check that bus grabbed the correct data | ||||||
|             let bus = bus |             let bus = cpu | ||||||
|  |                 .screen | ||||||
|                 .get_mut(addr..addr + DATA.len()) |                 .get_mut(addr..addr + DATA.len()) | ||||||
|                 .expect("Getting a mutable slice at addr 0x0456 should not fail"); |                 .expect("Getting a mutable slice at addr 0x0456 should not fail"); | ||||||
|             assert_eq!(bus[0..=len], DATA[0..=len]); |             assert_eq!(bus[0..=len], DATA[0..=len]); | ||||||
| @@ -1039,18 +1045,19 @@ mod io { | |||||||
|     // TODO: Test with dma_inc quirk set |     // TODO: Test with dma_inc quirk set | ||||||
|     #[test] |     #[test] | ||||||
|     fn dma_load() { |     fn dma_load() { | ||||||
|         let (mut cpu, mut bus) = setup_environment(); |         let (mut cpu, _) = setup_environment(); | ||||||
|         const DATA: &[u8] = b"ABCDEFGHIJKLMNOP"; |         const DATA: &[u8] = b"ABCDEFGHIJKLMNOP"; | ||||||
|         // Load some test data into memory |         // Load some test data into memory | ||||||
|         let addr = 0x456; |         let addr = 0x456; | ||||||
|         bus.get_mut(addr..addr + DATA.len()) |         cpu.screen | ||||||
|  |             .get_mut(addr..addr + DATA.len()) | ||||||
|             .expect("Getting a mutable slice at addr 0x0456..0x0466 should not fail") |             .expect("Getting a mutable slice at addr 0x0456..0x0466 should not fail") | ||||||
|             .write_all(DATA) |             .write_all(DATA) | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|         for len in 0..16 { |         for len in 0..16 { | ||||||
|             // Perform DMA load |             // Perform DMA load | ||||||
|             cpu.i = addr as u16; |             cpu.i = addr as u16; | ||||||
|             cpu.load_dma(len, &mut bus); |             cpu.load_dma(len); | ||||||
|             // Check that registers grabbed the correct data |             // Check that registers grabbed the correct data | ||||||
|             assert_eq!(cpu.v[0..=len], DATA[0..=len]); |             assert_eq!(cpu.v[0..=len], DATA[0..=len]); | ||||||
|             assert_eq!(cpu.v[len + 1..], [0; 16][len + 1..]); |             assert_eq!(cpu.v[len + 1..], [0; 16][len + 1..]); | ||||||
|   | |||||||
| @@ -1,3 +1,6 @@ | |||||||
|  | // (c) 2023 John A. Breaux | ||||||
|  | // This code is licensed under MIT license (see LICENSE for details) | ||||||
|  |  | ||||||
| //! Exercises the instruction decode logic. | //! Exercises the instruction decode logic. | ||||||
| use super::*; | use super::*; | ||||||
|  |  | ||||||
| @@ -9,9 +12,11 @@ fn run_single_op(op: &[u8]) -> CPU { | |||||||
|     let (mut cpu, mut bus) = ( |     let (mut cpu, mut bus) = ( | ||||||
|         CPU::default(), |         CPU::default(), | ||||||
|         bus! { |         bus! { | ||||||
|             Program[0x200..0x240] = op, |             Screen[0x0..0x1000], | ||||||
|         }, |         }, | ||||||
|     ); |     ); | ||||||
|  |     cpu.screen | ||||||
|  |         .load_region(Program, op).unwrap(); | ||||||
|     cpu.v = *INDX; |     cpu.v = *INDX; | ||||||
|     cpu.flags.quirks = Quirks::from(false); |     cpu.flags.quirks = Quirks::from(false); | ||||||
|     cpu.tick(&mut bus).unwrap(); // will panic if unimplemented |     cpu.tick(&mut bus).unwrap(); // will panic if unimplemented | ||||||
|   | |||||||
| @@ -14,8 +14,8 @@ pub mod error; | |||||||
| // Common imports for Chirp | // Common imports for Chirp | ||||||
| pub use cpu::{ | pub use cpu::{ | ||||||
|     bus::{Bus, Get, ReadWrite, Region::*}, |     bus::{Bus, Get, ReadWrite, Region::*}, | ||||||
|     disassembler::{Dis, Disassembler}, |  | ||||||
|     flags::Flags, |     flags::Flags, | ||||||
|  |     instruction::disassembler::{Dis, Disassembler}, | ||||||
|     mode::Mode, |     mode::Mode, | ||||||
|     quirks::Quirks, |     quirks::Quirks, | ||||||
|     CPU, |     CPU, | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ fn setup_environment() -> (CPU, Bus) { | |||||||
|     cpu.flags = Flags { |     cpu.flags = Flags { | ||||||
|         debug: true, |         debug: true, | ||||||
|         pause: false, |         pause: false, | ||||||
|         monotonic: Some(10), |  | ||||||
|         ..Default::default() |         ..Default::default() | ||||||
|     }; |     }; | ||||||
|     ( |     ( | ||||||
| @@ -36,10 +35,10 @@ struct SuiteTest { | |||||||
| fn run_screentest(test: SuiteTest, mut cpu: CPU, mut bus: Bus) { | fn run_screentest(test: SuiteTest, mut cpu: CPU, mut bus: Bus) { | ||||||
|     // Set the test to run |     // Set the test to run | ||||||
|     bus.write(0x1ffu16, test.test); |     bus.write(0x1ffu16, test.test); | ||||||
|     bus.load_region(Program, test.data).unwrap(); |     cpu.load_program_bytes(test.data).unwrap(); | ||||||
|     // The test suite always initiates a keypause on test completion |     // The test suite always initiates a keypause on test completion | ||||||
|     while !(cpu.flags.keypause || cpu.flags.pause) { |     while !(cpu.flags.is_paused()) { | ||||||
|         cpu.multistep(&mut bus, 100).unwrap(); |         cpu.multistep(&mut bus, 10).unwrap(); | ||||||
|     } |     } | ||||||
|     // Compare the screen to the reference screen buffer |     // Compare the screen to the reference screen buffer | ||||||
|     bus.print_screen().unwrap(); |     bus.print_screen().unwrap(); | ||||||
|   | |||||||
| @@ -192,7 +192,7 @@ mod cpu { | |||||||
| } | } | ||||||
|  |  | ||||||
| mod dis { | mod dis { | ||||||
|     use chirp::cpu::disassembler::Insn; |     use chirp::cpu::instruction::Insn; | ||||||
|     use imperative_rs::InstructionSet; |     use imperative_rs::InstructionSet; | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user