Skip to content

Commit

Permalink
start cleanning up the code
Browse files Browse the repository at this point in the history
  • Loading branch information
gthvn1 committed Apr 29, 2024
1 parent 00e91a0 commit bfacce9
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 123 deletions.
9 changes: 8 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
## What?
- Another *chip8* emulator in rust
- Only instructions used to display Timendus spash screen are implemented
- Next to implement for pong is `FX15`
- To run it: `cargo run --bin emulator <ROMS>`
- See Timendus link below for some testing ROMS
- To debug prepend `RUST_LOG=debug`
- [Changelog](https://github.com/gthvn1/chip8-emulator/blob/master/Changelog.md)

## Todo
- Next to implement for pong is `FX15`
- But before:
- `FX15` is dealing with timer. We don't have any notion of timer yet.
- So we need to introduce it and we need to change our code
- We will use another frontend than minibf that doesn't have any FPS.
- Thus we will redisign a bit our emulator to work with Raylib...

## Links

- [Chip8 Technical Reference](http://devernay.free.fr/hacks/chip8/C8TECH10.HTM)
Expand Down
146 changes: 67 additions & 79 deletions src/chip8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ impl Chip8 {
/// Emulate the instruction at program counter.
/// Currently we are returning false for opcode that are not yet emulated
/// but it is for testing.
pub fn emulate_one_insn(&mut self) -> Result<(), Chip8Error> {
pub fn emulate_insn(&mut self) -> Result<(), Chip8Error> {
let opcode = Opcode::new(u16::from_be_bytes(
self.mem[self.pc..self.pc + OPCODE_SIZE].try_into().unwrap(),
));
Expand All @@ -198,32 +198,31 @@ impl Chip8 {

self.pc += OPCODE_SIZE;

match opcode.upper4() {
0x0 => {
if opcode.value() == 0x00E0 {
// clear screen
self.mem[DISPLAY_OFFSET..(DISPLAY_OFFSET + DISPLAY_SIZE)]
.copy_from_slice(&[0; DISPLAY_SIZE]);
} else if opcode.value() == 0x00EE {
// return from subroutine
if self.sp == STACK_OFFSET {
return Err(Chip8Error::StackUnderflow);
}
match opcode.per_4bits() {
// clear screen
(0x0, 0x0, 0xE, 0x0) => {
self.mem[DISPLAY_OFFSET..(DISPLAY_OFFSET + DISPLAY_SIZE)]
.copy_from_slice(&[0; DISPLAY_SIZE]);
}
// return from subroutine
(0x0, 0x0, 0xE, 0xE) => {
if self.sp == STACK_OFFSET {
return Err(Chip8Error::StackUnderflow);
}

// Read PC from the top of the stack
let pc_hi = self.mem[self.sp] as usize;
let pc_low = self.mem[self.sp + 1] as usize;
// Read PC from the top of the stack
let pc_hi = self.mem[self.sp] as usize;
let pc_low = self.mem[self.sp + 1] as usize;

self.pc = (pc_hi << 8) | pc_low;
self.pc = (pc_hi << 8) | pc_low;

// Decrement the stack pointer
self.sp -= 2;
} else {
return Err(Chip8Error::NotImplemented(opcode));
}
// Decrement the stack pointer
self.sp -= 2;
}
0x1 => self.pc = opcode.nnn() as usize,
0x2 => {
// Jump to addr
(0x1, _, _, _) => self.pc = opcode.nnn() as usize,
// Call addr
(0x2, _, _, _) => {
self.sp += 2; // Increment stack pointer
if self.sp >= STACK_OFFSET + STACK_SIZE {
return Err(Chip8Error::StackOverflow);
Expand All @@ -239,40 +238,32 @@ impl Chip8 {
// Set the new PC
self.pc = opcode.nnn() as usize;
}
0x3 => return Err(Chip8Error::NotImplemented(opcode)),
0x4 => return Err(Chip8Error::NotImplemented(opcode)),
0x5 => return Err(Chip8Error::NotImplemented(opcode)),
0x6 => {
let idx = opcode.x() as usize;
if idx >= VREGS_SIZE {
// LD Vx, byte
(0x6, x, _, _) => {
if x >= VREGS_SIZE {
return Err(Chip8Error::VregsOverflow);
}
self.vregs[idx] = opcode.nn();
self.vregs[x] = opcode.nn();
}
0x7 => {
let idx = opcode.x() as usize;
if idx >= VREGS_SIZE {
// ADD Vx, byte
(0x7, x, _, _) => {
if x >= VREGS_SIZE {
return Err(Chip8Error::VregsOverflow);
}
self.vregs[idx] += opcode.nn();
self.vregs[x] += opcode.nn();
}
0x8 => return Err(Chip8Error::NotImplemented(opcode)),
0x9 => return Err(Chip8Error::NotImplemented(opcode)),
0xA => self.i = opcode.nnn(),
0xB => return Err(Chip8Error::NotImplemented(opcode)),
0xC => return Err(Chip8Error::NotImplemented(opcode)),
0xD => {
// LD I, addr
(0xA, _, _, _) => self.i = opcode.nnn(),
// DRAW Vx, Vy, nibble
(0xD, x, y, n) => {
// Draw a sprite 8xN at coordinate (VX, VY)
// VX and VY are in pixels
let x = opcode.x() as usize;
let y = opcode.y() as usize;
if x >= VREGS_SIZE || y >= VREGS_SIZE {
return Err(Chip8Error::VregsOverflow);
}

let vx = self.vregs[opcode.x() as usize] as usize;
let vy = self.vregs[opcode.y() as usize] as usize;
let n = opcode.n() as usize;
let vx = self.vregs[x] as usize;
let vy = self.vregs[y] as usize;

log::debug!("Draw a 8x{n} sprite at ({vx}, {vy})");

Expand Down Expand Up @@ -305,52 +296,49 @@ impl Chip8 {
// Update the real framebuffer
self.mem[DISPLAY_OFFSET..(DISPLAY_OFFSET + DISPLAY_SIZE)].copy_from_slice(&fb_copy);
}
0xE => return Err(Chip8Error::NotImplemented(opcode)),
0xF => match opcode.nn() {
0x29 => {
// I is set to the location of the hexadecimal sprite corresponding to the
// value of Vx
let x = opcode.x() as usize;
if x >= VREGS_SIZE {
return Err(Chip8Error::VregsOverflow);
}

let vx = self.vregs[x] as u16;
// There are 16 hexadecimal sprites from 0 to F.
if vx >= 16 as u16 {
return Err(Chip8Error::UndefinedHexadecimal(vx as usize));
}

self.i = FONTS_OFFSET as u16 + FONTS_HEIGHT as u16 * vx;
// LD F, Vx
(0xF, x, 0x2, 0x9) => {
// I is set to the location of the hexadecimal sprite corresponding to the
// value of Vx
if x >= VREGS_SIZE {
return Err(Chip8Error::VregsOverflow);
}
0x33 => {
let vx = self.vregs[opcode.x() as usize];
let idx = self.i as usize;
self.mem[idx] = (vx / 100) % 10; // hundreds digit
self.mem[idx + 1] = (vx / 10) % 10; // tens digit
self.mem[idx + 2] = vx % 10; // ones digit

let vx = self.vregs[x] as u16;
// There are 16 hexadecimal sprites from 0 to F.
if vx >= 16_u16 {
return Err(Chip8Error::UndefinedHexadecimal(vx as usize));
}
0x65 => {
// Set V0 to Vx from memory starting at location i
let idx = self.i as usize;

for i in 0..16 {
self.vregs[i] = self.mem[idx + i];
}
self.i = FONTS_OFFSET as u16 + FONTS_HEIGHT as u16 * vx;
}
// LD B, Vx
(0xF, x, 0x3, 0x3) => {
let vx = self.vregs[x];
let idx = self.i as usize;
self.mem[idx] = (vx / 100) % 10; // hundreds digit
self.mem[idx + 1] = (vx / 10) % 10; // tens digit
self.mem[idx + 2] = vx % 10; // ones digit
}
// LD Vx, [I]
(0xF, _, 0x6, 0x5) => {
// Set V0 to Vx from memory starting at location i
// TODO: check the range of i ?
let idx = self.i as usize;

for x in 0..16 {
self.vregs[x] = self.mem[idx + x];
}
_ => return Err(Chip8Error::NotImplemented(opcode)),
},
_ => {
return Err(Chip8Error::UnknownOpcode(opcode));
}
_ => return Err(Chip8Error::NotImplemented(opcode)),
};

Ok(())
}

pub fn run(&mut self) {
loop {
match self.emulate_one_insn() {
match self.emulate_insn() {
Err(e) => {
log::error!("{e}");
break;
Expand Down
59 changes: 16 additions & 43 deletions src/chip8/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@ impl Opcode {
Self { value }
}

/// Returns the value of the upcode as u16
pub fn value(&self) -> u16 {
self.value
}

/// Returns the upper 4 bits of the opcode
pub fn upper4(&self) -> u8 {
let upper = self.value >> 12;
upper.try_into().unwrap()
/// Returns the value of the upcode as tuple of 4 bits
pub fn per_4bits(&self) -> (usize, usize, usize, usize) {
let v = self.value as usize;
(
(v & 0xF000) >> 12,
(v & 0x0F00) >> 8,
(v & 0x00F0) >> 4,
v & 0x000F,
)
}

pub fn nnn(&self) -> u16 {
Expand All @@ -35,37 +41,22 @@ impl Opcode {
let v = self.value & 0xFF;
v.try_into().unwrap()
}

pub fn n(&self) -> u8 {
let v = self.value & 0xF;
v.try_into().unwrap()
}

pub fn x(&self) -> u8 {
let v = (self.value & 0x0F00) >> 8;
v.try_into().unwrap()
}

pub fn y(&self) -> u8 {
let v = (self.value & 0x00F0) >> 4;
v.try_into().unwrap()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_upper4() {
fn test_per_4bits() {
let opcode = Opcode::new(0xF123);
assert_eq!(opcode.upper4(), 0xF);
assert_eq!(opcode.per_4bits(), (0xF, 0x1, 0x2, 0x3));

let opcode = Opcode::new(0x4123);
assert_eq!(opcode.upper4(), 0x4);
let opcode = Opcode::new(0x23);
assert_eq!(opcode.per_4bits(), (0x0, 0x0, 0x2, 0x3));

let opcode = Opcode::new(0x0123);
assert_eq!(opcode.upper4(), 0x0);
let opcode = Opcode::new(0x23F);
assert_eq!(opcode.per_4bits(), (0x0, 0x2, 0x3, 0xF));
}

#[test]
Expand All @@ -79,22 +70,4 @@ mod tests {
let opcode = Opcode::new(0xF123);
assert_eq!(opcode.nn(), 0x23);
}

#[test]
fn test_n() {
let opcode = Opcode::new(0xF123);
assert_eq!(opcode.n(), 0x3);
}

#[test]
fn test_x() {
let opcode = Opcode::new(0xF123);
assert_eq!(opcode.x(), 0x1);
}

#[test]
fn test_y() {
let opcode = Opcode::new(0xF123);
assert_eq!(opcode.y(), 0x2);
}
}

0 comments on commit bfacce9

Please sign in to comment.