Fixed carry flag on addition and subtraction ops, made rom selection a command line arg
This commit is contained in:
commit
f141b66f09
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
BIN
1-chip8-logo.ch8
Normal file
BIN
1-chip8-logo.ch8
Normal file
Binary file not shown.
BIN
3-corax+.ch8
Normal file
BIN
3-corax+.ch8
Normal file
Binary file not shown.
BIN
4-flags.ch8
Normal file
BIN
4-flags.ch8
Normal file
Binary file not shown.
BIN
5-quirks.ch8
Normal file
BIN
5-quirks.ch8
Normal file
Binary file not shown.
BIN
6-keypad.ch8
Normal file
BIN
6-keypad.ch8
Normal file
Binary file not shown.
BIN
7-beep.ch8
Normal file
BIN
7-beep.ch8
Normal file
Binary file not shown.
BIN
8-scrolling.ch8
Normal file
BIN
8-scrolling.ch8
Normal file
Binary file not shown.
1006
Cargo.lock
generated
Normal file
1006
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "buttery_chip8"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
minifb = "0.27"
|
||||||
|
rand = "0.10"
|
||||||
238
src/cpu.rs
Normal file
238
src/cpu.rs
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
use crate::cpu;
|
||||||
|
use rand::{self, RngExt};
|
||||||
|
|
||||||
|
const FONT: [u8; 80] = [
|
||||||
|
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
|
||||||
|
0x20, 0x60, 0x20, 0x20, 0x70, // 1
|
||||||
|
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
|
||||||
|
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
|
||||||
|
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
|
||||||
|
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
|
||||||
|
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
|
||||||
|
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
|
||||||
|
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
|
||||||
|
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
|
||||||
|
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
|
||||||
|
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
|
||||||
|
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
|
||||||
|
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
|
||||||
|
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
|
||||||
|
0xF0, 0x80, 0xF0, 0x80, 0x80, // F
|
||||||
|
];
|
||||||
|
pub struct Cpu {
|
||||||
|
reg: [u8; 16],
|
||||||
|
pub ram: [u8; 4096],
|
||||||
|
program_counter: u16,
|
||||||
|
stack: [u16; 16],
|
||||||
|
stack_pointer: u8,
|
||||||
|
index: u16,
|
||||||
|
pub display: [bool; 64 * 32],
|
||||||
|
delay_timer: u8,
|
||||||
|
sound_timer: u8,
|
||||||
|
pub keys: [bool; 16],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cpu {
|
||||||
|
pub fn new(rom: &[u8]) -> Cpu {
|
||||||
|
let mut cpu = Cpu {
|
||||||
|
reg: [0; 16],
|
||||||
|
ram: [0; 4096],
|
||||||
|
program_counter: 0x200,
|
||||||
|
stack: [0; 16],
|
||||||
|
stack_pointer: 0,
|
||||||
|
index: 0,
|
||||||
|
display: [false; 64 * 32],
|
||||||
|
delay_timer: 0,
|
||||||
|
sound_timer: 0,
|
||||||
|
keys: [false; 16],
|
||||||
|
};
|
||||||
|
cpu.load_font();
|
||||||
|
cpu.load_rom(rom);
|
||||||
|
cpu
|
||||||
|
}
|
||||||
|
pub fn load_font(&mut self) {
|
||||||
|
self.load_into_ram(&FONT, 0x050);
|
||||||
|
}
|
||||||
|
pub fn load_rom(&mut self, cart: &[u8]) {
|
||||||
|
self.load_into_ram(cart, 0x200);
|
||||||
|
}
|
||||||
|
fn stack_push(&mut self, data: u16) {
|
||||||
|
self.stack[self.stack_pointer as usize] = data;
|
||||||
|
self.stack_pointer += 1;
|
||||||
|
}
|
||||||
|
fn stack_pop(&mut self) -> u16 {
|
||||||
|
self.stack_pointer -= 1;
|
||||||
|
self.stack[self.stack_pointer as usize]
|
||||||
|
}
|
||||||
|
pub fn tick(&mut self) {
|
||||||
|
// Fetch - read two bytes from RAM at program_counter
|
||||||
|
let hi = self.ram[self.program_counter as usize] as u16;
|
||||||
|
let lo = self.ram[(self.program_counter + 1) as usize] as u16;
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
// Increment - increment the program counter by 2
|
||||||
|
self.program_counter += 2;
|
||||||
|
// Decode - figure out what those bytes mean
|
||||||
|
let opcode = (hi << 8) | lo;
|
||||||
|
let nibble = (opcode >> 12) & 0xF;
|
||||||
|
let x = ((opcode >> 8) & 0xF) as usize; // as usize for indexing into registers
|
||||||
|
let y = ((opcode >> 4) & 0xF) as usize;
|
||||||
|
let n = (opcode & 0xF) as usize;
|
||||||
|
let nn = (opcode & 0xFF) as u8;
|
||||||
|
let nnn = opcode & 0xFFF;
|
||||||
|
// Execute - do the thing
|
||||||
|
match nibble {
|
||||||
|
0x0 => {
|
||||||
|
match opcode {
|
||||||
|
0x00E0 => self.display = [false; 64 * 32], //clear the screen
|
||||||
|
0x00EE => self.program_counter = self.stack_pop(), // return from subroutine
|
||||||
|
_ => println!("opcode {:#06X} has not been implemented", opcode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x1 => self.program_counter = nnn, //0x1NNN is jump to NNN
|
||||||
|
0x2 => {
|
||||||
|
// call subroutine at nnn
|
||||||
|
self.stack_push(self.program_counter);
|
||||||
|
self.program_counter = nnn
|
||||||
|
}
|
||||||
|
0x3 => {
|
||||||
|
if self.reg[x] == nn {
|
||||||
|
self.program_counter += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x4 => {
|
||||||
|
if self.reg[x] != nn {
|
||||||
|
self.program_counter += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x5 => {
|
||||||
|
if self.reg[x] == self.reg[y] {
|
||||||
|
self.program_counter += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x6 => self.reg[x] = nn,
|
||||||
|
0x7 => self.reg[x] = self.reg[x].wrapping_add(nn),
|
||||||
|
0x8 => match n {
|
||||||
|
0x0 => self.reg[x] = self.reg[y],
|
||||||
|
0x1 => self.reg[x] |= self.reg[y],
|
||||||
|
0x2 => self.reg[x] &= self.reg[y],
|
||||||
|
0x3 => self.reg[x] ^= self.reg[y],
|
||||||
|
0x4 => {
|
||||||
|
let (result, carry) = self.reg[x].overflowing_add(self.reg[y]);
|
||||||
|
self.reg[x] = result;
|
||||||
|
self.reg[0xF] = if carry { 1 } else { 0 };
|
||||||
|
}
|
||||||
|
0x5 => {
|
||||||
|
let result = self.reg[x].wrapping_sub(self.reg[y]);
|
||||||
|
let not_borrow = (self.reg[x] >= self.reg[y]) as u8;
|
||||||
|
self.reg[x] = result;
|
||||||
|
self.reg[0xF] = not_borrow;
|
||||||
|
}
|
||||||
|
0x6 => {
|
||||||
|
self.reg[x] = self.reg[y];
|
||||||
|
let lsb = self.reg[x] & 1;
|
||||||
|
self.reg[x] >>= 1;
|
||||||
|
self.reg[0xF] = lsb;
|
||||||
|
}
|
||||||
|
0x7 => {
|
||||||
|
let result = self.reg[y].wrapping_sub(self.reg[x]);
|
||||||
|
let not_borrow = (self.reg[y] >= self.reg[x]) as u8;
|
||||||
|
self.reg[x] = result;
|
||||||
|
self.reg[0xF] = not_borrow;
|
||||||
|
}
|
||||||
|
0xE => {
|
||||||
|
self.reg[x] = self.reg[y];
|
||||||
|
let msb = (self.reg[x] >> 7) & 1;
|
||||||
|
self.reg[x] <<= 1;
|
||||||
|
self.reg[0xF] = msb;
|
||||||
|
}
|
||||||
|
_ => println!("opcode {:#06X} has not been implemented", opcode),
|
||||||
|
},
|
||||||
|
0x9 => {
|
||||||
|
if self.reg[x] != self.reg[y] {
|
||||||
|
self.program_counter += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0xA => self.index = nnn, //0xANNN set index to NNN
|
||||||
|
0xB => self.program_counter = self.reg[0] as u16 + nnn,
|
||||||
|
0xC => {
|
||||||
|
let random_byte: u8 = rng.random();
|
||||||
|
self.reg[x] = nn & random_byte;
|
||||||
|
}
|
||||||
|
0xD => {
|
||||||
|
//0xDXYN is display, and is a doozy.
|
||||||
|
// get X and Y coords from VX and VY
|
||||||
|
let x_coord: usize = self.reg[x].into();
|
||||||
|
let y_coord: usize = self.reg[y].into();
|
||||||
|
let mut flipped = false;
|
||||||
|
for row in 0..n {
|
||||||
|
let sprite_byte = self.ram[self.index as usize + row];
|
||||||
|
for col in 0..8 {
|
||||||
|
let bit = (sprite_byte >> (7 - col)) & 1;
|
||||||
|
let y = y_coord + row;
|
||||||
|
let x = x_coord + col;
|
||||||
|
let idx = y * 64 + x;
|
||||||
|
if bit == 1 {
|
||||||
|
self.display[idx] ^= true;
|
||||||
|
flipped |= !self.display[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if flipped {
|
||||||
|
self.reg[0xF] = 1;
|
||||||
|
} else {
|
||||||
|
self.reg[0xF] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0xE => match nn {
|
||||||
|
0x9E => {
|
||||||
|
if self.keys[self.reg[x] as usize] {
|
||||||
|
self.program_counter += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0xA1 => {
|
||||||
|
if !self.keys[self.reg[x] as usize] {
|
||||||
|
self.program_counter += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => println!("opcode {:#06X} has not been implemented", opcode), // unknown opcode
|
||||||
|
},
|
||||||
|
0xF => match nn {
|
||||||
|
0x07 => self.reg[x] = self.delay_timer,
|
||||||
|
0x0A => {}
|
||||||
|
0x15 => self.delay_timer = self.reg[x],
|
||||||
|
0x18 => self.sound_timer = self.reg[x],
|
||||||
|
0x1E => self.index = self.index.wrapping_add(self.reg[x] as u16),
|
||||||
|
0x29 => {
|
||||||
|
self.index = ((self.reg[x] * 5) + 0x050) as u16;
|
||||||
|
}
|
||||||
|
0x33 => {
|
||||||
|
let vx = self.reg[x];
|
||||||
|
let hundreds = vx / 100;
|
||||||
|
let tens = (vx / 10) % 10;
|
||||||
|
let ones = vx % 10;
|
||||||
|
self.ram[self.index as usize] = hundreds;
|
||||||
|
self.ram[(self.index + 1) as usize] = tens;
|
||||||
|
self.ram[(self.index + 2) as usize] = ones;
|
||||||
|
}
|
||||||
|
0x55 => {
|
||||||
|
for reg in 0..=x {
|
||||||
|
self.ram[self.index as usize + reg] = self.reg[reg];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x65 => {
|
||||||
|
for reg in 0..=x {
|
||||||
|
self.reg[reg] = self.ram[self.index as usize + reg];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => println!("opcode {:#06X} has not been implemented", opcode),
|
||||||
|
},
|
||||||
|
_ => println!("opcode {:#06X} has not been implemented", opcode), // unknown opcode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn load_into_ram(&mut self, data: &[u8], offset: usize) {
|
||||||
|
data.iter().enumerate().for_each(|(i, byte)| {
|
||||||
|
//write byte to ram starting at 0x200
|
||||||
|
self.ram[offset + i] = *byte;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/main.rs
Normal file
59
src/main.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
mod cpu;
|
||||||
|
|
||||||
|
use cpu::Cpu;
|
||||||
|
use minifb::{Key, Scale, Window, WindowOptions};
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
|
const WIDTH: usize = 64;
|
||||||
|
const HEIGHT: usize = 32;
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
if args.len() < 2 {
|
||||||
|
eprintln!("Usage: {} <rom.ch8>", args[0]);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let rom = fs::read(&args[1])?;
|
||||||
|
let mut chip8 = Cpu::new(&rom);
|
||||||
|
|
||||||
|
let mut window = Window::new(
|
||||||
|
"Buttery Chip8",
|
||||||
|
WIDTH,
|
||||||
|
HEIGHT,
|
||||||
|
WindowOptions {
|
||||||
|
scale: Scale::X8,
|
||||||
|
..WindowOptions::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let keys = [
|
||||||
|
window.is_key_down(Key::X), // 0
|
||||||
|
window.is_key_down(Key::Key1), // 1
|
||||||
|
window.is_key_down(Key::Key2), // 2
|
||||||
|
window.is_key_down(Key::Key3), // 3
|
||||||
|
window.is_key_down(Key::Q), // 4
|
||||||
|
window.is_key_down(Key::W), // 5
|
||||||
|
window.is_key_down(Key::E), // 6
|
||||||
|
window.is_key_down(Key::A), // 7
|
||||||
|
window.is_key_down(Key::S), // 8
|
||||||
|
window.is_key_down(Key::D), // 9
|
||||||
|
window.is_key_down(Key::Z), // A
|
||||||
|
window.is_key_down(Key::C), // B
|
||||||
|
window.is_key_down(Key::Key4), // C
|
||||||
|
window.is_key_down(Key::R), // D
|
||||||
|
window.is_key_down(Key::F), // E
|
||||||
|
window.is_key_down(Key::V), // F
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT];
|
||||||
|
|
||||||
|
while window.is_open() {
|
||||||
|
chip8.keys = keys;
|
||||||
|
chip8.tick();
|
||||||
|
for (i, pixel) in chip8.display.iter().enumerate() {
|
||||||
|
buffer[i] = if *pixel { 0xFFFFFFFF } else { 0x00000000 };
|
||||||
|
}
|
||||||
|
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user