Skip to content

Commit

Permalink
Implemented timers. Implemented primitive loop.
Browse files Browse the repository at this point in the history
  • Loading branch information
ablakey committed May 7, 2020
1 parent d46d1e3 commit 5c5b388
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 21 deletions.
Binary file added roms/particle_demo.c8
Binary file not shown.
90 changes: 78 additions & 12 deletions src/chip8.rs
Expand Up @@ -55,8 +55,8 @@ pub struct Chip8 {
index_register: u16,
program_counter: u16,
pub graphics_buffer: [bool; 64 * 32], // 64 rows, 32 cols, row-major.
// delay_timer: u8,
// sound_timer: u8,
delay_timer: u8,
sound_timer: u8,
stack: [u16; 16],
stack_pointer: usize,
// keys: [u8; 16],
Expand All @@ -78,8 +78,8 @@ impl Chip8 {
index_register: 0,
program_counter: Chip8::ADDRESS_ROM,
graphics_buffer: [false; 64 * 32],
// delay_timer: 0,
// sound_timer: 0,
delay_timer: 0,
sound_timer: 0,
stack: [0; 16],
stack_pointer: 0,
// keys: [0; 16],
Expand Down Expand Up @@ -111,8 +111,18 @@ impl Chip8 {
// Increment PC unless opcode is JUMP or CALL.
if ![0xB, 0x2, 0x1].contains(&opcode_symbols.a) {
self.program_counter += Chip8::OPCODE_SIZE;
} else {
println!("Skip PC increment.");
}
}

/// Decrement both sound and delay timers.
/// This should be getting called at 60hz by the emulator's controller.
pub fn decrement_timers(&mut self) {
if self.delay_timer > 0 {
self.delay_timer -= 1;
}

if self.sound_timer > 0 {
self.sound_timer -= 1;
}
}

Expand Down Expand Up @@ -184,13 +194,11 @@ impl Chip8 {

/// Jump PC to NNN.
fn JUMP(&mut self, nnn: u12) {
println!("{:x?}", self.registers);
self.program_counter = nnn;
}

/// Call subroutine at NNN.
fn CALL(&mut self, nnn: u12) {
println!("{:x}", nnn);
// Maintain current PC in the stack to be able to return from subroutine.
self.stack[self.stack_pointer] = self.program_counter;
self.stack_pointer += 1;
Expand All @@ -199,10 +207,7 @@ impl Chip8 {

/// Skip next instruction if VX == NN.
fn SKE(&mut self, x: u4, nn: u8) {
println!("{:x}", self.registers[x as usize]);
println!("{:x}", nn);
if self.registers[x as usize] == nn {
println!("SKIP");
self.program_counter += Chip8::OPCODE_SIZE;
}
}
Expand Down Expand Up @@ -328,9 +333,22 @@ impl Chip8 {
fn LOADS(&mut self, x: u4) {
self.not_implemented();
}

/// Add VX to I. VF set to 1 if there is an overflow, else 0.
fn ADDI(&mut self, x: u4) {
self.not_implemented();
let vx = self.registers[x as usize] as u16;

// Range overflow?
if self.index_register as usize + vx as usize > 0xFFF {
self.registers[0xF] = 1;
} else {
self.registers[0xF] = 0;
}

// Add with possible overflow.
self.index_register += vx;
}

fn LDSPR(&mut self, x: u4) {
self.not_implemented();
}
Expand Down Expand Up @@ -403,4 +421,52 @@ mod tests {
assert!(machine.graphics_buffer.iter().all(|&n| !n));
assert!(machine.has_graphics_update);
}

/// Timers should decrement by 1 each time `decrement_timers` is called, but never fall below 0.
#[test]
fn test_decrement_timers() {
let mut machine = Chip8::init();
machine.delay_timer = 5;
machine.sound_timer = 0;

machine.decrement_timers();

assert_eq!(machine.sound_timer, 0);
assert_eq!(machine.delay_timer, 4);
}

/// The Draw opcode should XOR black and white bits to the graphics buffer with overflow to next
// line. It is byte-encoded sprite-based. See specifications online for more details.
#[test]
fn test_draw() {
let mut machine = Chip8::init();
machine.index_register = 0x204; // Where to look for the sprite data.
machine.memory[0x204] = 0xCC; // 8 bits to draw: 11001100
machine.memory[0x205] = 0xFF; // 8 bits to draw: 11111111
machine.registers[0] = 0; // x coordinate
machine.registers[1] = 0; // y-coordinate

machine.DRAW(0, 1, 1); // Get x,y from 0,1 and draw a single byte of data.

// The segment of the graphics buffer is as expected. We drew four pixels at x= 0, 1, 4, 5.
assert_eq!(
machine.graphics_buffer[0..8],
[true, true, false, false, true, true, false, false]
);

// In this case, we draw two lines, not one.
machine.DRAW(0, 1, 2);

// XORing has turned off the pixels that were on.
assert_eq!(
machine.graphics_buffer[0..8],
[false, false, false, false, false, false, false, false]
);

// But the second line is all on now.
assert_eq!(
machine.graphics_buffer[64..72],
[true, true, true, true, true, true, true, true]
)
}
}
4 changes: 2 additions & 2 deletions src/graphics.rs
Expand Up @@ -37,7 +37,7 @@ impl Screen {
/// Iterate through all pixels in buffer and draw only those that are set active.
/// The screen is first blanked, then all pixels in buffer are evaluated for being active.
/// The remaining pixels are drawn as filled rects, scaled by scale_factor.
pub fn blit(&mut self, &buffer: &[bool; 64 * 32]) {
pub fn draw(&mut self, &buffer: &[bool; 64 * 32]) {
let rects: Vec<sdl2::rect::Rect> = buffer
.iter()
.enumerate()
Expand All @@ -46,7 +46,7 @@ impl Screen {
// Row-major, so we divide and modulo by width to get row and column number.
let row = n / Self::CHIP8_WIDTH as usize;
let col = n % Self::CHIP8_WIDTH as usize;
println!("{} {}", row, col);

return sdl2::rect::Rect::new(
(col * self.scale_factor as usize) as i32,
(row * self.scale_factor as usize) as i32,
Expand Down
23 changes: 16 additions & 7 deletions src/main.rs
Expand Up @@ -4,7 +4,7 @@ use chip8::Chip8;
use graphics::Screen;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
// use std::{thread, time};
use std::{thread, time};

fn main() -> Result<(), String> {
// Init SDL components (the CHIP8 I/O)
Expand All @@ -14,17 +14,24 @@ fn main() -> Result<(), String> {

// Load a program.
let mut machine = Chip8::init();
machine.load_rom(String::from("roms/maze.c8")).unwrap();
machine
.load_rom(String::from("roms/particle_demo.c8"))
.unwrap();

machine.print_mem();

'running: loop {
// Advance the program a tick.
machine.tick();
// Advance the program at about 500hz (8.33 ticks per cycle.)
// Note: we are rounding to 8 for simplicity, meaning the emulator runs a bit slow.
for _ in 0..8 {
machine.tick();
}

machine.decrement_timers();

// Draw to screen?
if machine.has_graphics_update {
screen.blit(&machine.graphics_buffer);
screen.draw(&machine.graphics_buffer);
}

// Handle keyboard/event input.
Expand All @@ -41,8 +48,10 @@ fn main() -> Result<(), String> {
}
}

//Throttle the loop rate.
// thread::sleep(time::Duration::from_millis(100));
// Loop at ~ 60hz.
// Note: this speeds the emulator up (16ms vs. 16.66ms). It also means we sleep for at
// least 16ms but could be more. We should implement something more robust.
thread::sleep(time::Duration::from_millis(16));
}

Ok(())
Expand Down

0 comments on commit 5c5b388

Please sign in to comment.