schip: Add preliminary SuperChip support (no test)
This commit is contained in:
		
							
								
								
									
										11
									
								
								justfile
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								justfile
									
									
									
									
									
								
							@@ -7,11 +7,14 @@ rat:
 | 
			
		||||
test:
 | 
			
		||||
    cargo nextest run
 | 
			
		||||
 | 
			
		||||
chirp:
 | 
			
		||||
    cargo run --bin chirp-minifb -- chip8-test-suite/bin/chip8-test-suite.ch8
 | 
			
		||||
run rom:
 | 
			
		||||
    cargo run --bin chirp-minifb -- '{{rom}}'
 | 
			
		||||
 | 
			
		||||
debug rom:
 | 
			
		||||
    cargo run --bin chirp-minifb -- -d '{{rom}}'
 | 
			
		||||
# Run at 2100000 instructions per frame, and output per-frame runtime statistics
 | 
			
		||||
bench:
 | 
			
		||||
    cargo run --bin chirp-minifb --release -- chip8Archive/roms/1dcell.ch8 -xP -s10 -S2100000
 | 
			
		||||
    cargo run --bin chirp-minifb --release -- chip8Archive/roms/1dcell.ch8 -Ps10 -S2100000 -m xochip
 | 
			
		||||
 | 
			
		||||
flame rom:
 | 
			
		||||
    CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -F 15300 --open --bin chirp-minifb -- '{{rom}}' -s10
 | 
			
		||||
@@ -23,5 +26,5 @@ cover:
 | 
			
		||||
    cargo llvm-cov --open --doctests
 | 
			
		||||
 | 
			
		||||
tokei:
 | 
			
		||||
    tokei --exclude tests/chip8-test-suite
 | 
			
		||||
    tokei --exclude chip8-test-suite --exclude chip8Archive
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,33 +36,39 @@ struct Arguments {
 | 
			
		||||
    pub step: Option<usize>,
 | 
			
		||||
    #[options(help = "Enable performance benchmarking on stderr (requires -S)")]
 | 
			
		||||
    pub perf: bool,
 | 
			
		||||
    #[options(
 | 
			
		||||
        short = "z",
 | 
			
		||||
        help = "Disable setting vF to 0 after a bitwise operation."
 | 
			
		||||
    )]
 | 
			
		||||
    pub vfreset: bool,
 | 
			
		||||
    #[options(
 | 
			
		||||
        short = "x",
 | 
			
		||||
        help = "Disable waiting for vblank after issuing a draw call."
 | 
			
		||||
    )]
 | 
			
		||||
    pub drawsync: bool,
 | 
			
		||||
 | 
			
		||||
    #[options(
 | 
			
		||||
        short = "c",
 | 
			
		||||
        help = "Use CHIP-48 style DMA instructions, which don't touch I."
 | 
			
		||||
        help = "Run in (Chip8, SChip, XOChip) mode.",
 | 
			
		||||
        //parse(from_str = "parse_mode")
 | 
			
		||||
    )]
 | 
			
		||||
    pub memory: bool,
 | 
			
		||||
    #[options(
 | 
			
		||||
        short = "v",
 | 
			
		||||
        help = "Use CHIP-48 style bit-shifts, which don't touch vY."
 | 
			
		||||
    )]
 | 
			
		||||
    pub shift: bool,
 | 
			
		||||
    #[options(
 | 
			
		||||
        short = "b",
 | 
			
		||||
        help = "Use SUPER-CHIP style indexed jump, which is indexed relative to v[adr]."
 | 
			
		||||
    )]
 | 
			
		||||
    pub jumping: bool,
 | 
			
		||||
    pub mode: Option<Mode>,
 | 
			
		||||
 | 
			
		||||
    // #[options(
 | 
			
		||||
    //     short = "z",
 | 
			
		||||
    //     help = "Disable setting vF to 0 after a bitwise operation."
 | 
			
		||||
    // )]
 | 
			
		||||
    // pub vfreset: bool,
 | 
			
		||||
    // #[options(
 | 
			
		||||
    //     short = "x",
 | 
			
		||||
    //     help = "Disable waiting for vblank after issuing a draw call."
 | 
			
		||||
    // )]
 | 
			
		||||
    // pub drawsync: bool,
 | 
			
		||||
 | 
			
		||||
    // #[options(
 | 
			
		||||
    //     short = "c",
 | 
			
		||||
    //     help = "Use CHIP-48 style DMA instructions, which don't touch I."
 | 
			
		||||
    // )]
 | 
			
		||||
    // pub memory: bool,
 | 
			
		||||
    // #[options(
 | 
			
		||||
    //     short = "v",
 | 
			
		||||
    //     help = "Use CHIP-48 style bit-shifts, which don't touch vY."
 | 
			
		||||
    // )]
 | 
			
		||||
    // pub shift: bool,
 | 
			
		||||
    // #[options(
 | 
			
		||||
    //     short = "b",
 | 
			
		||||
    //     help = "Use SUPER-CHIP style indexed jump, which is indexed relative to v[adr]."
 | 
			
		||||
    // )]
 | 
			
		||||
    // pub jumping: bool,
 | 
			
		||||
    #[options(
 | 
			
		||||
        long = "break",
 | 
			
		||||
        help = "Set breakpoints for the emulator to stop at.",
 | 
			
		||||
@@ -105,7 +111,7 @@ impl State {
 | 
			
		||||
                    // Load the ROM file into RAM
 | 
			
		||||
                    Program [0x0200..0x1000] = &read(&options.file)?,
 | 
			
		||||
                    // Create a screen
 | 
			
		||||
                    Screen  [0x1000..0x1100],
 | 
			
		||||
                    Screen  [0x1000..0x1400],
 | 
			
		||||
                    // Create a stack
 | 
			
		||||
                    Stack   [0x0EA0..0x0F00],
 | 
			
		||||
                },
 | 
			
		||||
@@ -117,13 +123,7 @@ impl State {
 | 
			
		||||
                    Dis::default(),
 | 
			
		||||
                    options.breakpoints,
 | 
			
		||||
                    ControlFlags {
 | 
			
		||||
                        quirks: chirp::cpu::Quirks {
 | 
			
		||||
                            bin_ops: options.vfreset,
 | 
			
		||||
                            shift: options.shift,
 | 
			
		||||
                            draw_wait: options.drawsync,
 | 
			
		||||
                            dma_inc: options.memory,
 | 
			
		||||
                            stupid_jumps: options.jumping,
 | 
			
		||||
                        },
 | 
			
		||||
                        quirks: options.mode.unwrap_or_default().into(),
 | 
			
		||||
                        debug: options.debug,
 | 
			
		||||
                        pause: options.pause,
 | 
			
		||||
                        monotonic: options.speed,
 | 
			
		||||
@@ -131,7 +131,7 @@ impl State {
 | 
			
		||||
                    },
 | 
			
		||||
                ),
 | 
			
		||||
            },
 | 
			
		||||
            ui: UIBuilder::new(64, 32, &options.file).build()?,
 | 
			
		||||
            ui: UIBuilder::new(128, 64, &options.file).build()?,
 | 
			
		||||
            ft: Instant::now(),
 | 
			
		||||
        };
 | 
			
		||||
        state.ch8.bus.write(0x1feu16, options.data);
 | 
			
		||||
@@ -202,7 +202,7 @@ impl Iterator for State {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<()> {
 | 
			
		||||
pub fn main() -> Result<()> {
 | 
			
		||||
    let options = Arguments::parse_args_default_or_exit();
 | 
			
		||||
    let state = State::new(options)?;
 | 
			
		||||
    for result in state {
 | 
			
		||||
 
 | 
			
		||||
@@ -56,14 +56,14 @@ impl UIBuilder {
 | 
			
		||||
impl Default for UIBuilder {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        UIBuilder {
 | 
			
		||||
            width: 64,
 | 
			
		||||
            height: 32,
 | 
			
		||||
            width: 128,
 | 
			
		||||
            height: 64,
 | 
			
		||||
            name: Some("Chip-8 Interpreter"),
 | 
			
		||||
            rom: None,
 | 
			
		||||
            window_options: WindowOptions {
 | 
			
		||||
                title: true,
 | 
			
		||||
                resize: false,
 | 
			
		||||
                scale: Scale::X16,
 | 
			
		||||
                scale: Scale::X8,
 | 
			
		||||
                scale_mode: ScaleMode::AspectRatioStretch,
 | 
			
		||||
                none: true,
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
@@ -80,9 +80,13 @@ pub struct FrameBufferFormat {
 | 
			
		||||
 | 
			
		||||
impl Default for FrameBufferFormat {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        // FrameBufferFormat {
 | 
			
		||||
        //     fg: 0x0011a434,
 | 
			
		||||
        //     bg: 0x001E2431,
 | 
			
		||||
        // }
 | 
			
		||||
        FrameBufferFormat {
 | 
			
		||||
            fg: 0x0011a434,
 | 
			
		||||
            bg: 0x001E2431,
 | 
			
		||||
            fg: 0xc4c4c4,
 | 
			
		||||
            bg: 0x000000,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -112,6 +116,8 @@ impl FrameBuffer {
 | 
			
		||||
                        self.format.fg
 | 
			
		||||
                    } else {
 | 
			
		||||
                        self.format.bg
 | 
			
		||||
                        // .wrapping_add(0x001104 * (idx / self.width) as u32)
 | 
			
		||||
                        // .wrapping_add(0x141000 * (idx & 3) as u32)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -123,7 +129,7 @@ impl FrameBuffer {
 | 
			
		||||
 | 
			
		||||
impl Default for FrameBuffer {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::new(64, 32)
 | 
			
		||||
        Self::new(128, 64)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										76
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								src/bus.rs
									
									
									
									
									
								
							@@ -308,14 +308,14 @@ impl Bus {
 | 
			
		||||
        const REGION: Region = Region::Screen;
 | 
			
		||||
        if let Some(screen) = self.get_region(REGION) {
 | 
			
		||||
            for (index, byte) in screen.iter().enumerate() {
 | 
			
		||||
                if index % 8 == 0 {
 | 
			
		||||
                    print!("|");
 | 
			
		||||
                if index % 16 == 0 {
 | 
			
		||||
                    print!("{index:03x}|");
 | 
			
		||||
                }
 | 
			
		||||
                print!(
 | 
			
		||||
                    "{}",
 | 
			
		||||
                    format!("{byte:08b}").replace('0', "  ").replace('1', "██")
 | 
			
		||||
                    format!("{byte:08b}").replace('0', " ").replace('1', "█")
 | 
			
		||||
                );
 | 
			
		||||
                if index % 8 == 7 {
 | 
			
		||||
                if index % 16 == 15 {
 | 
			
		||||
                    println!("|");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -339,7 +339,43 @@ impl Read<u16> for Bus {
 | 
			
		||||
    fn read(&self, addr: impl Into<usize>) -> u16 {
 | 
			
		||||
        let addr: usize = addr.into();
 | 
			
		||||
        if let Some(bytes) = self.memory.get(addr..addr + 2) {
 | 
			
		||||
            u16::from_be_bytes(bytes.try_into().expect("asked for 2 bytes, got != 2 bytes"))
 | 
			
		||||
            u16::from_be_bytes(bytes.try_into().expect("Should get 2 bytes"))
 | 
			
		||||
        } else {
 | 
			
		||||
            0xc5c5
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Read<u32> for Bus {
 | 
			
		||||
    /// Read a u16 from address `addr`
 | 
			
		||||
    fn read(&self, addr: impl Into<usize>) -> u32 {
 | 
			
		||||
        let addr: usize = addr.into();
 | 
			
		||||
        if let Some(bytes) = self.memory.get(addr..addr + 4) {
 | 
			
		||||
            u32::from_be_bytes(bytes.try_into().expect("Should get 4 bytes"))
 | 
			
		||||
        } else {
 | 
			
		||||
            0xc5c5
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Read<u64> for Bus {
 | 
			
		||||
    /// Read a u16 from address `addr`
 | 
			
		||||
    fn read(&self, addr: impl Into<usize>) -> u64 {
 | 
			
		||||
        let addr: usize = addr.into();
 | 
			
		||||
        if let Some(bytes) = self.memory.get(addr..addr + 8) {
 | 
			
		||||
            u64::from_be_bytes(bytes.try_into().expect("Should get 8 bytes"))
 | 
			
		||||
        } else {
 | 
			
		||||
            0xc5c5
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Read<u128> for Bus {
 | 
			
		||||
    /// Read a u16 from address `addr`
 | 
			
		||||
    fn read(&self, addr: impl Into<usize>) -> u128 {
 | 
			
		||||
        let addr: usize = addr.into();
 | 
			
		||||
        if let Some(bytes) = self.memory.get(addr..addr + 16) {
 | 
			
		||||
            u128::from_be_bytes(bytes.try_into().expect("Should get 16 bytes"))
 | 
			
		||||
        } else {
 | 
			
		||||
            0xc5c5
 | 
			
		||||
        }
 | 
			
		||||
@@ -366,6 +402,36 @@ impl Write<u16> for Bus {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Write<u32> for Bus {
 | 
			
		||||
    /// Write a u16 to address `addr`
 | 
			
		||||
    fn write(&mut self, addr: impl Into<usize>, data: u32) {
 | 
			
		||||
        let addr: usize = addr.into();
 | 
			
		||||
        if let Some(slice) = self.get_mut(addr..addr + 4) {
 | 
			
		||||
            data.to_be_bytes().as_mut().swap_with_slice(slice);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Write<u64> for Bus {
 | 
			
		||||
    /// Write a u16 to address `addr`
 | 
			
		||||
    fn write(&mut self, addr: impl Into<usize>, data: u64) {
 | 
			
		||||
        let addr: usize = addr.into();
 | 
			
		||||
        if let Some(slice) = self.get_mut(addr..addr + 8) {
 | 
			
		||||
            data.to_be_bytes().as_mut().swap_with_slice(slice);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Write<u128> for Bus {
 | 
			
		||||
    /// Write a u16 to address `addr`
 | 
			
		||||
    fn write(&mut self, addr: impl Into<usize>, data: u128) {
 | 
			
		||||
        let addr: usize = addr.into();
 | 
			
		||||
        if let Some(slice) = self.get_mut(addr..addr + 16) {
 | 
			
		||||
            data.to_be_bytes().as_mut().swap_with_slice(slice);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_feature = "rhexdump")]
 | 
			
		||||
impl Display for Bus {
 | 
			
		||||
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										235
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										235
									
								
								src/cpu.rs
									
									
									
									
									
								
							@@ -22,12 +22,39 @@ use crate::{
 | 
			
		||||
use imperative_rs::InstructionSet;
 | 
			
		||||
use owo_colors::OwoColorize;
 | 
			
		||||
use rand::random;
 | 
			
		||||
use std::time::Instant;
 | 
			
		||||
use std::{str::FromStr, time::Instant};
 | 
			
		||||
 | 
			
		||||
type Reg = usize;
 | 
			
		||||
type Adr = u16;
 | 
			
		||||
type Nib = u8;
 | 
			
		||||
 | 
			
		||||
/// Selects the memory behavior of the interpreter
 | 
			
		||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
			
		||||
pub enum Mode {
 | 
			
		||||
    /// VIP emulation mode
 | 
			
		||||
    #[default]
 | 
			
		||||
    Chip8,
 | 
			
		||||
    /// Chip-48 emulation mode
 | 
			
		||||
    SChip,
 | 
			
		||||
    /// XO-Chip emulation mode
 | 
			
		||||
    XOChip,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromStr for Mode {
 | 
			
		||||
    type Err = Error;
 | 
			
		||||
 | 
			
		||||
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
 | 
			
		||||
        match s.to_lowercase().as_str() {
 | 
			
		||||
            "chip8" | "chip-8" => Ok(Mode::Chip8),
 | 
			
		||||
            "schip" | "superchip" => Ok(Mode::SChip),
 | 
			
		||||
            "xo-chip" | "xochip" => Ok(Mode::XOChip),
 | 
			
		||||
            _ => Err(Error::InvalidMode {
 | 
			
		||||
                mode: s.to_string(),
 | 
			
		||||
            }),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Controls the authenticity behavior of the CPU on a granular level.
 | 
			
		||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
			
		||||
pub struct Quirks {
 | 
			
		||||
@@ -51,7 +78,7 @@ impl From<bool> for Quirks {
 | 
			
		||||
                shift: true,
 | 
			
		||||
                draw_wait: true,
 | 
			
		||||
                dma_inc: true,
 | 
			
		||||
                stupid_jumps: false,
 | 
			
		||||
                stupid_jumps: true,
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            Quirks {
 | 
			
		||||
@@ -65,6 +92,22 @@ impl From<bool> for Quirks {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Mode> for Quirks {
 | 
			
		||||
    fn from(value: Mode) -> Self {
 | 
			
		||||
        match value {
 | 
			
		||||
            Mode::Chip8 => false.into(),
 | 
			
		||||
            Mode::SChip => true.into(),
 | 
			
		||||
            Mode::XOChip => Self {
 | 
			
		||||
                bin_ops: true,
 | 
			
		||||
                shift: false,
 | 
			
		||||
                draw_wait: true,
 | 
			
		||||
                dma_inc: false,
 | 
			
		||||
                stupid_jumps: false,
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Quirks {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::from(false)
 | 
			
		||||
@@ -82,9 +125,13 @@ pub struct ControlFlags {
 | 
			
		||||
    pub keypause: bool,
 | 
			
		||||
    /// Set when the emulator is waiting for a frame to be drawn
 | 
			
		||||
    pub draw_wait: bool,
 | 
			
		||||
    /// Set when the emulator is in high-res mode
 | 
			
		||||
    pub draw_mode: bool,
 | 
			
		||||
    /// Set to the last key that's been *released* after a keypause
 | 
			
		||||
    pub lastkey: Option<usize>,
 | 
			
		||||
    /// Represents the set of emulator [Quirks] to enable
 | 
			
		||||
    /// Represents the current emulator [Mode]
 | 
			
		||||
    pub mode: Mode,
 | 
			
		||||
    /// Represents the set of emulator [Quirks] to enable, independent of the [Mode]
 | 
			
		||||
    pub quirks: Quirks,
 | 
			
		||||
    /// Represents the number of instructions to run per tick of the internal timer
 | 
			
		||||
    pub monotonic: Option<usize>,
 | 
			
		||||
@@ -704,6 +751,7 @@ impl CPU {
 | 
			
		||||
    #[rustfmt::skip]
 | 
			
		||||
    fn execute(&mut self, bus: &mut Bus, instruction: Insn) {
 | 
			
		||||
        match instruction {
 | 
			
		||||
            // Core Chip-8 instructions
 | 
			
		||||
            Insn::cls               => self.clear_screen(bus),
 | 
			
		||||
            Insn::ret               => self.ret(bus),
 | 
			
		||||
            Insn::jmp   { A       } => self.jump(A),
 | 
			
		||||
@@ -713,7 +761,7 @@ impl CPU {
 | 
			
		||||
            Insn::se    { y, x    } => self.skip_equals(x, y),
 | 
			
		||||
            Insn::movb  { B, x    } => self.load_immediate(x, B),
 | 
			
		||||
            Insn::addb  { B, x    } => self.add_immediate(x, B),
 | 
			
		||||
            Insn::mov   { x, y    } => self.load(x, y),
 | 
			
		||||
            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),
 | 
			
		||||
@@ -726,7 +774,7 @@ impl CPU {
 | 
			
		||||
            Insn::movI  { A       } => self.load_i_immediate(A),
 | 
			
		||||
            Insn::jmpr  { A       } => self.jump_indexed(A),
 | 
			
		||||
            Insn::rand  { B, x    } => self.rand(x, B),
 | 
			
		||||
            Insn::draw  { x, y, n } => self.draw(x, y, n, bus),
 | 
			
		||||
            Insn::draw  { y, x, n } => self.draw(x, y, n, bus),
 | 
			
		||||
            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),
 | 
			
		||||
@@ -738,6 +786,16 @@ impl CPU {
 | 
			
		||||
            Insn::bcd   { x       } => self.bcd_convert(x, bus),
 | 
			
		||||
            Insn::dmao  { x       } => self.store_dma(x, bus),
 | 
			
		||||
            Insn::dmai  { x       } => self.load_dma(x, bus),
 | 
			
		||||
            // Super-Chip extensions
 | 
			
		||||
            Insn::scd   {       n } => self.scroll_down(n, bus),
 | 
			
		||||
            Insn::scr               => self.scr(bus),
 | 
			
		||||
            Insn::scl               => self.scl(bus),
 | 
			
		||||
            Insn::halt              => self.flags.pause(),
 | 
			
		||||
            Insn::lores             => self.flags.draw_mode = false,
 | 
			
		||||
            Insn::hires             => self.flags.draw_mode = true,
 | 
			
		||||
            Insn::hfont { x       } => self.load_big_sprite(x),
 | 
			
		||||
            Insn::flgo  { x       } => self.store_flags(x, bus),
 | 
			
		||||
            Insn::flgi  { x       } => self.load_flags(x, bus),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -755,9 +813,7 @@ impl CPU {
 | 
			
		||||
    /// |`00e0`| Clears the screen memory to 0
 | 
			
		||||
    #[inline(always)]
 | 
			
		||||
    fn clear_screen(&mut self, bus: &mut Bus) {
 | 
			
		||||
        if let Some(screen) = bus.get_region_mut(Region::Screen) {
 | 
			
		||||
            screen.fill(0);
 | 
			
		||||
        }
 | 
			
		||||
        bus.clear_region(Region::Screen);
 | 
			
		||||
    }
 | 
			
		||||
    /// |`00ee`| Returns from subroutine
 | 
			
		||||
    #[inline(always)]
 | 
			
		||||
@@ -765,6 +821,37 @@ impl CPU {
 | 
			
		||||
        self.sp = self.sp.wrapping_add(2);
 | 
			
		||||
        self.pc = bus.read(self.sp);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// |`00cN`| Scroll the screen down N lines
 | 
			
		||||
    #[inline(always)]
 | 
			
		||||
    fn scroll_down(&mut self, n: Nib, bus: &mut Bus) {
 | 
			
		||||
        // Get a line from the bus
 | 
			
		||||
        for i in (16 * n as usize..16 * 15).step_by(16).rev() {
 | 
			
		||||
            let i = i + self.screen as usize;
 | 
			
		||||
            let line: u128 = bus.read(i);
 | 
			
		||||
            bus.write(i - (n as usize * 16), line);
 | 
			
		||||
            bus.write(i, 0u128);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// |`00fb`| Scroll the screen right
 | 
			
		||||
    #[inline(always)]
 | 
			
		||||
    fn scr(&mut self, bus: &mut (impl Read<u128> + Write<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)]
 | 
			
		||||
    fn scl(&mut self, bus: &mut (impl Read<u128> + Write<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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// |`1aaa`| Sets pc to an absolute address
 | 
			
		||||
@@ -994,6 +1081,15 @@ impl CPU {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// TODO: Do this more idiomatically, using some iterator chain?
 | 
			
		||||
fn doublewide(value: u16) -> u32 {
 | 
			
		||||
    let mut out: u32 = 0;
 | 
			
		||||
    for i in 0..16 {
 | 
			
		||||
        out |= ((value as u32 & 0x1 << i) * 3) << i;
 | 
			
		||||
    }
 | 
			
		||||
    out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// |`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)
 | 
			
		||||
@@ -1002,31 +1098,86 @@ impl CPU {
 | 
			
		||||
    /// On the original chip-8 interpreter, this will wait for a VBI
 | 
			
		||||
    #[inline(always)]
 | 
			
		||||
    fn draw(&mut self, x: Reg, y: Reg, n: Nib, bus: &mut Bus) {
 | 
			
		||||
        let (x, y) = (self.v[x] as u16 % 64, self.v[y] as u16 % 32);
 | 
			
		||||
        // lmaotch
 | 
			
		||||
        match self.flags.draw_mode {
 | 
			
		||||
            true => self.draw_hires(x, y, n, bus),
 | 
			
		||||
            false => self.draw_lores(x, y, n, bus),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    #[inline(always)]
 | 
			
		||||
    fn draw_lores(&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) = (64, 32);
 | 
			
		||||
        let (x, y) = (self.v[x] as u16 % w, self.v[y] as u16 % h);
 | 
			
		||||
        self.v[0xf] = 0;
 | 
			
		||||
        for byte in 0..n as u16 {
 | 
			
		||||
            if y + byte > 32 {
 | 
			
		||||
                return;
 | 
			
		||||
        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, sprite) = (line as u16, *sprite);
 | 
			
		||||
                // clip
 | 
			
		||||
                if y + line >= h {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                // clip
 | 
			
		||||
                let sprite = (sprite as u16) << (8 - (x % 8))
 | 
			
		||||
                    & if (x % w) >= (w - 8) { 0xff00 } else { 0xffff };
 | 
			
		||||
                // scale
 | 
			
		||||
                let addr = |x, y| -> u16 { ((y + (2 * line)) * 8 + (x / 8)) * 2 + self.screen };
 | 
			
		||||
                let y = y << 1;
 | 
			
		||||
                let sprite = doublewide(sprite);
 | 
			
		||||
                for scale in 0..2 {
 | 
			
		||||
                    let screen: u32 = bus.read(addr(x, y + scale));
 | 
			
		||||
                    bus.write(addr(x, y + scale), screen ^ sprite);
 | 
			
		||||
                    if screen & sprite != 0 {
 | 
			
		||||
                        self.v[0xf] = 1;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // Calculate the lower bound address based on the X,Y position on the screen
 | 
			
		||||
            let addr = (y + byte) * 8 + (x & 0x3f) / 8 + self.screen;
 | 
			
		||||
            // Read a byte of sprite data into a u16, and shift it x % 8 bits
 | 
			
		||||
            let sprite: u8 = bus.read(self.i + byte);
 | 
			
		||||
            let sprite =
 | 
			
		||||
                (sprite as u16) << (8 - (x & 7)) & if x % 64 > 56 { 0xff00 } else { 0xffff };
 | 
			
		||||
            // Read a u16 from the bus containing the two bytes which might need to be updated
 | 
			
		||||
            let mut screen: u16 = bus.read(addr);
 | 
			
		||||
            // Save the bits-toggled-off flag if necessary
 | 
			
		||||
            if screen & sprite != 0 {
 | 
			
		||||
                self.v[0xF] = 1
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // Super-Chip extension high-resolution graphics mode
 | 
			
		||||
    #[inline(always)]
 | 
			
		||||
    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) = (128, 64);
 | 
			
		||||
        let (x, y) = (self.v[x] as u16 % w, self.v[y] as u16 % h);
 | 
			
		||||
        let w_bytes = w / 8;
 | 
			
		||||
        self.v[0xf] = 0;
 | 
			
		||||
        if n == 0 {
 | 
			
		||||
            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(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;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            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 addr = (y + line as u16) * w_bytes + x / 8 + self.screen;
 | 
			
		||||
                    let sprite = (*sprite as u16) << (8 - (x % 8));
 | 
			
		||||
                    let screen: u16 = bus.read(addr);
 | 
			
		||||
                    bus.write(addr, screen ^ sprite);
 | 
			
		||||
                    if screen & sprite != 0 {
 | 
			
		||||
                        self.v[0xf] += 1;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // Update the screen word by XORing the sprite byte
 | 
			
		||||
            screen ^= sprite;
 | 
			
		||||
            // Save the result to the screen
 | 
			
		||||
            bus.write(addr, screen);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1164,4 +1315,34 @@ impl CPU {
 | 
			
		||||
            self.i += x as Adr + 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// |`Fx30`| (Super-Chip) 16x16 equivalent of Fx29
 | 
			
		||||
    #[inline(always)]
 | 
			
		||||
    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)]
 | 
			
		||||
    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)]
 | 
			
		||||
    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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ use std::fmt::Display;
 | 
			
		||||
#[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,
 | 
			
		||||
@@ -111,12 +112,42 @@ pub enum Insn {
 | 
			
		||||
    // | 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}"),
 | 
			
		||||
@@ -151,6 +182,16 @@ impl Display for Insn {
 | 
			
		||||
            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}"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,12 @@ pub enum Error {
 | 
			
		||||
        /// The offending register
 | 
			
		||||
        reg: usize,
 | 
			
		||||
    },
 | 
			
		||||
    /// Tried to convert string into mode, but it did not match.
 | 
			
		||||
    #[error("Invalid mode: {mode}")]
 | 
			
		||||
    InvalidMode {
 | 
			
		||||
        /// The string which failed to become a mode
 | 
			
		||||
        mode: String,
 | 
			
		||||
    },
 | 
			
		||||
    /// Error originated in [std::io]
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    IoError(#[from] std::io::Error),
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ pub mod error;
 | 
			
		||||
 | 
			
		||||
// Common imports for Chirp
 | 
			
		||||
pub use bus::{Bus, Read, Region::*, Write};
 | 
			
		||||
pub use cpu::{disassembler::Dis, ControlFlags, CPU};
 | 
			
		||||
pub use cpu::{disassembler::Dis, ControlFlags, Mode, CPU};
 | 
			
		||||
pub use error::Result;
 | 
			
		||||
 | 
			
		||||
/// Holds the state of a Chip-8
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ fn setup_environment() -> (CPU, Bus) {
 | 
			
		||||
            Charset [0x0050..0x00A0] = include_bytes!("../src/mem/charset.bin"),
 | 
			
		||||
            // Load the ROM file into RAM
 | 
			
		||||
            Program [0x0200..0x1000] = include_bytes!("../chip8-test-suite/bin/chip8-test-suite.ch8"),
 | 
			
		||||
            // Create a screen, and fill it with garbage data
 | 
			
		||||
            // Create a screen, and fill it with
 | 
			
		||||
            Screen  [0x0F00..0x1000] = include_bytes!("chip8_test_suite.rs"),
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -132,8 +132,8 @@ mod cpu {
 | 
			
		||||
                keypause: false,
 | 
			
		||||
                draw_wait: false,
 | 
			
		||||
                lastkey: None,
 | 
			
		||||
                quirks: Default::default(),
 | 
			
		||||
                monotonic: None,
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            };
 | 
			
		||||
            let cf2 = cf1.clone();
 | 
			
		||||
            assert_eq!(cf1, cf2)
 | 
			
		||||
@@ -151,9 +151,7 @@ mod cpu {
 | 
			
		||||
                    pause: false,
 | 
			
		||||
                    keypause: false,
 | 
			
		||||
                    draw_wait: false,
 | 
			
		||||
                    lastkey: Default::default(),
 | 
			
		||||
                    quirks: Default::default(),
 | 
			
		||||
                    monotonic: Default::default()
 | 
			
		||||
                    ..Default::default()
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
@@ -165,9 +163,7 @@ mod cpu {
 | 
			
		||||
                pause: true,
 | 
			
		||||
                keypause: true,
 | 
			
		||||
                draw_wait: true,
 | 
			
		||||
                lastkey: Default::default(),
 | 
			
		||||
                quirks: Default::default(),
 | 
			
		||||
                monotonic: Default::default(),
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            };
 | 
			
		||||
            assert_ne!(cf1, cf2);
 | 
			
		||||
        }
 | 
			
		||||
@@ -179,9 +175,7 @@ mod cpu {
 | 
			
		||||
                pause: true,
 | 
			
		||||
                keypause: true,
 | 
			
		||||
                draw_wait: true,
 | 
			
		||||
                lastkey: Default::default(),
 | 
			
		||||
                quirks: Default::default(),
 | 
			
		||||
                monotonic: Default::default(),
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            };
 | 
			
		||||
            assert!(cf1 < cf2);
 | 
			
		||||
            assert_eq!(ControlFlags::default(), cf1.min(cf2));
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user