Fixed carry flag on addition and subtraction ops, made rom selection a command line arg

This commit is contained in:
Butter 2026-04-07 10:04:37 -04:00
commit f141b66f09
13 changed files with 1312 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

BIN
1-chip8-logo.ch8 Normal file

Binary file not shown.

BIN
3-corax+.ch8 Normal file

Binary file not shown.

BIN
4-flags.ch8 Normal file

Binary file not shown.

BIN
5-quirks.ch8 Normal file

Binary file not shown.

BIN
6-keypad.ch8 Normal file

Binary file not shown.

BIN
7-beep.ch8 Normal file

Binary file not shown.

BIN
8-scrolling.ch8 Normal file

Binary file not shown.

1006
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "buttery_chip8"
version = "0.1.0"
edition = "2024"
[dependencies]
minifb = "0.27"
rand = "0.10"

BIN
IBM.ch8 Normal file

Binary file not shown.

238
src/cpu.rs Normal file
View 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
View 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(())
}