mirror of https://github.com/Ivsucram/ivsemu.git
Chip-8 emulator
Complete Chip-8 instructions. However, the user still needs to modify the code to select the ROM. Next-step will be to insert SUPER-CHIP and OX-CHIP instructions, as well as to give menu options to the user.
This commit is contained in:
parent
9202042093
commit
26a1b85841
|
@ -2,3 +2,4 @@
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
|
||||||
/src/SDL2.dll
|
/src/SDL2.dll
|
||||||
|
/src/chip_8/roms
|
|
@ -1,28 +1,42 @@
|
||||||
// CHIP-8 means Compact Hexadecimal Interpretive Programming - 8-bit
|
// CHIP-8 means Compact Hexadecimal Interpretive Programming - 8-bit
|
||||||
use ::sdl2;
|
use ::sdl2;
|
||||||
|
|
||||||
use crate::chip_8::display;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
const DISPLAY_WIDTH: usize = 64;
|
const DISPLAY_WIDTH: usize = 64;
|
||||||
const DISPLAY_HEIGHT: usize = 32;
|
const DISPLAY_HEIGHT: usize = 32;
|
||||||
const DISPLAY_SCALE: u32 = 10;
|
const DISPLAY_SCALE: u32 = 10;
|
||||||
|
|
||||||
pub struct ProgramCounter(usize);
|
pub struct ProgramCounter(usize);
|
||||||
pub struct Registers([u8; 16]);
|
pub struct Registers([u8; 0x10]);
|
||||||
pub struct Ram([u8; 4 * 1024]);
|
pub struct Ram([u8; 4 * 1024]);
|
||||||
pub struct Timer(u8);
|
|
||||||
pub struct Timing(u16);
|
|
||||||
pub struct DisplayBuffer([[bool; DISPLAY_HEIGHT]; DISPLAY_WIDTH]);
|
pub struct DisplayBuffer([[bool; DISPLAY_HEIGHT]; DISPLAY_WIDTH]);
|
||||||
|
|
||||||
|
pub struct Timer{
|
||||||
|
tick: u8,
|
||||||
|
clock_hz: u128,
|
||||||
|
elapsed: std::time::SystemTime
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Keypad {
|
pub struct Keypad {
|
||||||
keys: [bool; 0xF],
|
keys: [bool; 0x10],
|
||||||
layout: String
|
// layout: String
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OpCodes {
|
||||||
|
opcode: u16,
|
||||||
|
nible_1: u8,
|
||||||
|
nible_2: u8,
|
||||||
|
nible_3: u8,
|
||||||
|
nible_4: u8
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProgramCounter {
|
impl ProgramCounter {
|
||||||
fn init() -> ProgramCounter {
|
fn init() -> ProgramCounter {
|
||||||
ProgramCounter {
|
ProgramCounter {
|
||||||
0: 0
|
0: 0x200
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +44,10 @@ impl ProgramCounter {
|
||||||
self.0 += 2;
|
self.0 += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decrement(&mut self) {
|
||||||
|
self.0 -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
fn set(&mut self, value: usize) {
|
fn set(&mut self, value: usize) {
|
||||||
self.0 = value;
|
self.0 = value;
|
||||||
}
|
}
|
||||||
|
@ -47,18 +65,12 @@ impl Registers {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, register: usize) -> u8 {
|
fn get(&self, register: usize) -> u8 {
|
||||||
if register <= 0xF {
|
|
||||||
self.0[register]
|
self.0[register]
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(&mut self, register: usize, value: u8) {
|
fn set(&mut self, register: usize, value: u8) {
|
||||||
if register <= 0xF {
|
|
||||||
self.0[register] = value;
|
self.0[register] = value;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ram {
|
impl Ram {
|
||||||
|
@ -69,7 +81,8 @@ impl Ram {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_fonts(&mut self) {
|
fn init_fonts(&mut self) {
|
||||||
const FONTS: [u8; 80] = [
|
let font_address = 0x50;
|
||||||
|
let fonts: [u8; 80] = [
|
||||||
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
|
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
|
||||||
0x20, 0x60, 0x20, 0x20, 0x70, // 1
|
0x20, 0x60, 0x20, 0x20, 0x70, // 1
|
||||||
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
|
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
|
||||||
|
@ -86,8 +99,8 @@ impl Ram {
|
||||||
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
|
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
|
||||||
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
|
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
|
||||||
0xF0, 0x80, 0xF0, 0x80, 0x80]; // F
|
0xF0, 0x80, 0xF0, 0x80, 0x80]; // F
|
||||||
for i in 0x50..0x80 {
|
for i in 0..fonts.len() {
|
||||||
self.0[i] = FONTS[i];
|
self.0[i + font_address] = fonts[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,70 +112,114 @@ impl Ram {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read8(&self, addr: usize) -> u8 {
|
fn read8(&self, addr: usize) -> u8 {
|
||||||
assert!(addr <= self.0.len());
|
assert!(addr <= self.0.len(), "addr = {}, self.0.len() = {}", addr, self.0.len());
|
||||||
self.0[addr]
|
self.0[addr]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read16(&self, addr: usize) -> u16 {
|
fn read16(&self, addr: usize) -> u16 {
|
||||||
assert!(addr < self.0.len());
|
assert!(addr < self.0.len(), "addr = {}, self.0.len() = {}", addr, self.0.len());
|
||||||
// byteorder::LittleEndiar::read_u16(&self.ram[addr])
|
// byteorder::LittleEndian::read_u16(&self.0[addr]);
|
||||||
|
|
||||||
// u16::from_le_bytes(self.ram[addr..addr+2])
|
// u16::from_le_bytes(self.ram[addr..addr+2])
|
||||||
// self.ram[addr..addr+2]
|
// self.ram[addr..addr+2]
|
||||||
(self.0[addr] as u16) << 8 | self.0[addr+1] as u16
|
(self.0[addr] as u16) << 8 | self.0[addr+1] as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write8(&mut self, addr: usize, value: u8) {
|
||||||
|
assert!(addr < self.0.len());
|
||||||
|
self.0[addr] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timer {
|
impl Timer {
|
||||||
fn init() -> Timer {
|
fn init() -> Timer {
|
||||||
Timer {
|
Timer {
|
||||||
0: 0
|
tick: 255,
|
||||||
|
clock_hz: 60,
|
||||||
|
elapsed: std::time::SystemTime::now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self) -> bool {
|
fn tick(&mut self) -> bool {
|
||||||
self.0 -= 1;
|
let mut res: bool = false;
|
||||||
self.0 == 0
|
match self.elapsed.elapsed() {
|
||||||
|
Ok(elapsed) => {
|
||||||
|
if elapsed.as_secs_f32() >= 1./(self.clock_hz as f32) {
|
||||||
|
if self.tick > 0 { self.tick -= 1; }
|
||||||
|
self.reset_elapsed();
|
||||||
|
res = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_elapsed(&mut self) {
|
||||||
|
self.elapsed = std::time::SystemTime::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keypad {
|
impl Keypad {
|
||||||
fn init() -> Keypad {
|
fn init() -> Keypad {
|
||||||
Keypad {
|
Keypad {
|
||||||
keys: [false; 0xF],
|
keys: [false; 0x10],
|
||||||
layout: String::from("123C456D789EA0BF")
|
// layout: String::from("123C456D789EA0BF")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_layout(&mut self, layout: &str) {
|
fn compute_keycode(&self, keycode: sdl2::keyboard::Keycode) -> Option<usize> {
|
||||||
assert_eq!(layout.len(), self.layout.len());
|
match keycode {
|
||||||
self.layout = layout.to_string();
|
sdl2::keyboard::Keycode::Num1 => Some(0x1),
|
||||||
}
|
sdl2::keyboard::Keycode::Num2 => Some(0x2),
|
||||||
}
|
sdl2::keyboard::Keycode::Num3 => Some(0x3),
|
||||||
|
sdl2::keyboard::Keycode::Num4 => Some(0xC),
|
||||||
|
|
||||||
impl Timing {
|
sdl2::keyboard::Keycode::Q => Some(0x4),
|
||||||
fn init() -> Timing {
|
sdl2::keyboard::Keycode::W => Some(0x5),
|
||||||
Timing {
|
sdl2::keyboard::Keycode::E => Some(0x6),
|
||||||
0: 1000 // 1 MHz
|
sdl2::keyboard::Keycode::R => Some(0xD),
|
||||||
|
|
||||||
|
sdl2::keyboard::Keycode::A => Some(0x7),
|
||||||
|
sdl2::keyboard::Keycode::S => Some(0x8),
|
||||||
|
sdl2::keyboard::Keycode::D => Some(0x9),
|
||||||
|
sdl2::keyboard::Keycode::F => Some(0xE),
|
||||||
|
|
||||||
|
sdl2::keyboard::Keycode::Z => Some(0xA),
|
||||||
|
sdl2::keyboard::Keycode::X => Some(0x0),
|
||||||
|
sdl2::keyboard::Keycode::C => Some(0xB),
|
||||||
|
sdl2::keyboard::Keycode::V => Some(0xF),
|
||||||
|
|
||||||
|
_ => Option::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn increase(&mut self) {
|
// fn set_layout(&mut self, layout: &str) {
|
||||||
self.0 += 10;
|
// assert_eq!(layout.len(), self.layout.len());
|
||||||
|
// self.layout = layout.to_string();
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn get(&mut self, pos: usize) -> bool {
|
||||||
|
self.keys[pos]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrease(&mut self) {
|
fn being_pressed(&self) -> u8 {
|
||||||
self.0 -= 10;
|
for key in 0x0..0x10 {
|
||||||
|
if self.keys[key] {
|
||||||
|
return key as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0x10
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format(&self) -> String {
|
fn press(&mut self, key: usize) {
|
||||||
let mut res = String::new();
|
self.keys[key] = true;
|
||||||
if self.0 >= 1000 {
|
|
||||||
res = format!("{:.2}{}", self.0/1000, "MHz");
|
|
||||||
} else {
|
|
||||||
res = format!("{}{}", self.0, "Hz");
|
|
||||||
}
|
}
|
||||||
res
|
|
||||||
|
fn release(&mut self, key: usize) {
|
||||||
|
self.keys[key] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,57 +233,455 @@ impl DisplayBuffer {
|
||||||
fn clear(&mut self) {
|
fn clear(&mut self) {
|
||||||
self.0 = [[false; DISPLAY_HEIGHT]; DISPLAY_WIDTH];
|
self.0 = [[false; DISPLAY_HEIGHT]; DISPLAY_WIDTH];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn random(&mut self) {
|
impl OpCodes {
|
||||||
for (x, col) in self.0.iter_mut().enumerate() {
|
fn init(opcode: u16) -> OpCodes {
|
||||||
for (y, pixel) in col.iter_mut().enumerate() {
|
OpCodes {
|
||||||
let mut rng = rand::thread_rng();
|
opcode: opcode,
|
||||||
*pixel = rng.gen_range(0..2) == 0;
|
nible_1: ((opcode & 0xF000) >> 12) as u8,
|
||||||
|
nible_2: ((opcode & 0x0F00) >> 8) as u8,
|
||||||
|
nible_3: ((opcode & 0x00F0) >> 4) as u8,
|
||||||
|
nible_4: (opcode & 0x000F) as u8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_x(&self) -> usize {
|
||||||
|
self.nible_2 as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_y(&self) -> usize {
|
||||||
|
self.nible_3 as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_nnn(&self) -> u16 {
|
||||||
|
self.opcode & 0xFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_nn(&self) -> u8 {
|
||||||
|
(self.opcode & 0xFF) as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_n(&self) -> u8 {
|
||||||
|
(self.opcode & 0xF) as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CPU {
|
||||||
|
pc: ProgramCounter,
|
||||||
|
index_register: usize,
|
||||||
|
stack: Vec<usize>,
|
||||||
|
delay_timer: Timer,
|
||||||
|
sound_timer: Timer,
|
||||||
|
clock: Timer,
|
||||||
|
registers: Registers,
|
||||||
|
ram: Ram,
|
||||||
|
keypad: Keypad,
|
||||||
|
display_buffer: DisplayBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CPU {
|
||||||
|
fn init() -> CPU {
|
||||||
|
CPU {
|
||||||
|
pc: ProgramCounter::init(),
|
||||||
|
index_register: 0,
|
||||||
|
stack: vec![],
|
||||||
|
delay_timer: Timer::init(),
|
||||||
|
sound_timer: Timer::init(),
|
||||||
|
clock: Timer::init(),
|
||||||
|
registers: Registers::init(),
|
||||||
|
ram: Ram::init(),
|
||||||
|
keypad: Keypad::init(),
|
||||||
|
display_buffer: DisplayBuffer::init()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let mut pc = ProgramCounter::init();
|
let mut cpu = CPU::init();
|
||||||
let mut index_register: u16 = 0;
|
cpu.ram.init_fonts();
|
||||||
let mut stack: Vec<u16>;
|
// load_rom("src/chip_8/roms/ibm_logo.ch8", &mut cpu);
|
||||||
let mut delay_timer = Timer::init();
|
load_rom("src/chip_8/roms/BC_test.ch8", &mut cpu);
|
||||||
let mut sound_timer = Timer::init();
|
// load_rom("src/chip_8/roms/test_opcode.ch8", &mut cpu);
|
||||||
let mut registers = Registers::init();
|
// load_rom("src/chip_8/roms/HIDDEN.ch8", &mut cpu); // Good to test keyboard
|
||||||
let mut ram = Ram::init();
|
// load_rom("src/chip_8/roms/CAVE.ch8", &mut cpu); // Good to test keyboard
|
||||||
let mut keypad = Keypad::init();
|
// load_rom("src/chip_8/roms/TRON.ch8", &mut cpu); // Good to test keyboard
|
||||||
let mut timing = Timing::init();
|
// load_rom("src/chip_8/roms/PUZZLE.ch8", &mut cpu); // Good to test keyboard?
|
||||||
let mut display_buffer = DisplayBuffer::init();
|
// load_rom("src/chip_8/roms/TETRIS.ch8", &mut cpu); // Good to test keyboard?
|
||||||
|
// load_rom("src/chip_8/roms/delay_timer_test.ch8", &mut cpu);
|
||||||
|
// load_rom("src/chip_8/roms/random_number_test.ch8", &mut cpu);
|
||||||
|
|
||||||
let sdl_context = sdl2::init().unwrap();
|
let sdl_context = sdl2::init().unwrap();
|
||||||
|
|
||||||
let mut display = crate::chip_8::display::Display::init(&sdl_context, DISPLAY_SCALE);
|
let mut display = crate::chip_8::display::Display::init(&sdl_context, DISPLAY_SCALE);
|
||||||
|
|
||||||
let mut event_listener = sdl_context.event_pump().unwrap();
|
let mut event_listener = sdl_context.event_pump().unwrap();
|
||||||
let mut is_running = true;
|
cpu.clock.clock_hz = 60;
|
||||||
|
|
||||||
'runner: loop {
|
'runner: loop {
|
||||||
for event in event_listener.poll_iter() {
|
for event in event_listener.poll_iter() {
|
||||||
match event {
|
match event {
|
||||||
sdl2::event::Event::Quit {..} | sdl2::event::Event::KeyDown {
|
sdl2::event::Event::Quit {..} => break 'runner,
|
||||||
|
sdl2::event::Event::KeyDown {
|
||||||
keycode: Some(sdl2::keyboard::Keycode::Escape), .. } => {break 'runner},
|
keycode: Some(sdl2::keyboard::Keycode::Escape), .. } => {break 'runner},
|
||||||
|
sdl2::event::Event::KeyDown {
|
||||||
|
keycode: Some(sdl2::keyboard::Keycode::RightBracket), .. } => {
|
||||||
|
println!("Increasing cpu clock from {} Hz to {} Hz", cpu.clock.clock_hz, cpu.clock.clock_hz + 10);
|
||||||
|
cpu.clock.clock_hz += 10;
|
||||||
|
},
|
||||||
|
sdl2::event::Event::KeyDown {
|
||||||
|
keycode: Some(sdl2::keyboard::Keycode::LeftBracket), .. } => {
|
||||||
|
println!("Decreasing cpu clock from {} Hz to {} Hz", cpu.clock.clock_hz, cpu.clock.clock_hz - 10);
|
||||||
|
cpu.clock.clock_hz -= 10;
|
||||||
|
},
|
||||||
|
sdl2::event::Event::KeyDown { keycode: Some(keycode), ..} => {
|
||||||
|
if let Some(key_index) = cpu.keypad.compute_keycode(keycode) {
|
||||||
|
cpu.keypad.press(key_index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sdl2::event::Event::KeyUp { keycode: Some(keycode), ..} => {
|
||||||
|
if let Some(key_index) = cpu.keypad.compute_keycode(keycode) {
|
||||||
|
cpu.keypad.release(key_index);
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
display_buffer.random();
|
cpu.delay_timer.tick();
|
||||||
display.draw(&display_buffer.0)
|
cpu.sound_timer.tick();
|
||||||
|
if cpu.clock.tick() {
|
||||||
|
let opcode = OpCodes::init(fetch(&mut cpu));
|
||||||
|
decode(&opcode, &mut cpu);
|
||||||
|
if opcode.nible_1 == 0xD {
|
||||||
|
display.draw(&cpu.display_buffer.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch(ram: Ram, pc: &mut ProgramCounter) -> u16{
|
fn load_rom(filename: &str, cpu: &mut CPU) {
|
||||||
let opcode = ram.read16(pc.get());
|
let mut f = std::fs::File::open(&filename).expect("no file found");
|
||||||
pc.increase();
|
let metadata = std::fs::metadata(&filename).expect("unable to read metadata");
|
||||||
|
let mut buffer = vec![0; metadata.len() as usize];
|
||||||
|
f.read(&mut buffer).expect("buffer overflow");
|
||||||
|
cpu.ram.load_rom(&buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch(cpu: &mut CPU) -> u16 {
|
||||||
|
let opcode = cpu.ram.read16(cpu.pc.get());
|
||||||
|
cpu.pc.increase();
|
||||||
opcode
|
opcode
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode() {}
|
fn decode(opcode: &OpCodes, cpu: &mut CPU) {
|
||||||
|
if opcode.opcode == 0x00E0 {
|
||||||
|
op_00e0(cpu);
|
||||||
|
} else if opcode.nible_1 == 0x1 {
|
||||||
|
op1nnn(cpu, opcode.get_nnn() as usize);
|
||||||
|
} else if opcode.opcode == 0x00EE {
|
||||||
|
op00ee(cpu);
|
||||||
|
} else if opcode.nible_1 == 0x2 {
|
||||||
|
op2nnn(cpu, opcode.get_nnn() as usize);
|
||||||
|
} else if opcode.nible_1 == 0x3 {
|
||||||
|
op3xnn(cpu, opcode.get_x(), opcode.get_nn());
|
||||||
|
} else if opcode.nible_1 == 0x4 {
|
||||||
|
op4xnn(cpu, opcode.get_x(), opcode.get_nn());
|
||||||
|
} else if opcode.nible_1 == 0x5 {
|
||||||
|
op5xy0(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0x9 {
|
||||||
|
op9xy0(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0x6 {
|
||||||
|
op6xnn(cpu, opcode.get_x(), opcode.get_nn());
|
||||||
|
} else if opcode.nible_1 == 0x7 {
|
||||||
|
op7xnn(cpu, opcode.get_x(), opcode.get_nn());
|
||||||
|
} else if opcode.nible_1 == 0x8 && opcode.nible_4 == 0x0 {
|
||||||
|
op8xy0(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0x8 && opcode.nible_4 == 0x1 {
|
||||||
|
op8xy1(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0x8 && opcode.nible_4 == 0x2 {
|
||||||
|
op8xy2(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0x8 && opcode.nible_4 == 0x3 {
|
||||||
|
op8xy3(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0x8 && opcode.nible_4 == 0x4 {
|
||||||
|
op8xy4(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0x8 && opcode.nible_4 == 0x5 {
|
||||||
|
op8xy5(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0x8 && opcode.nible_4 == 0x6 {
|
||||||
|
op8xy6(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0x8 && opcode.nible_4 == 0x7 {
|
||||||
|
op8xy7(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0x8 && opcode.nible_4 == 0xE {
|
||||||
|
op8xye(cpu, opcode.get_x(), opcode.get_y());
|
||||||
|
} else if opcode.nible_1 == 0xA {
|
||||||
|
opannn(cpu, opcode.get_nnn() as usize);
|
||||||
|
} else if opcode.nible_1 == 0xB {
|
||||||
|
opbnnn(cpu, opcode.get_nnn() as usize);
|
||||||
|
} else if opcode.nible_1 == 0xC {
|
||||||
|
opcxnn(cpu, opcode.get_x(), opcode.get_nn());
|
||||||
|
} else if opcode.nible_1 == 0xD {
|
||||||
|
opdxyn(cpu, opcode.get_x(), opcode.get_y(), opcode.get_n());
|
||||||
|
} else if opcode.nible_1 == 0xE && opcode.nible_3 == 0x9 && opcode.nible_4 == 0xE {
|
||||||
|
opex9e(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xE && opcode.nible_3 == 0x9 && opcode.nible_4 == 0xE {
|
||||||
|
opex9e(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xE && opcode.nible_3 == 0xA && opcode.nible_4 == 0x1 {
|
||||||
|
opexa1(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xF && opcode.nible_3 == 0x0 && opcode.nible_4 == 0x7 {
|
||||||
|
opfx07(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xF && opcode.nible_3 == 0x1 && opcode.nible_4 == 0x5 {
|
||||||
|
opfx15(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xF && opcode.nible_3 == 0x1 && opcode.nible_4 == 0x8 {
|
||||||
|
opfx18(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xF && opcode.nible_3 == 0x1 && opcode.nible_4 == 0xE {
|
||||||
|
opfx1e(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xF && opcode.nible_3 == 0x0 && opcode.nible_4 == 0xA {
|
||||||
|
opfx0a(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xF && opcode.nible_3 == 0x2 && opcode.nible_4 == 0x9 {
|
||||||
|
opfx29(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xF && opcode.nible_3 == 0x3 && opcode.nible_4 == 0x3 {
|
||||||
|
opfx33(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xF && opcode.nible_3 == 0x5 && opcode.nible_4 == 0x5 {
|
||||||
|
opfx55(cpu, opcode.get_x());
|
||||||
|
} else if opcode.nible_1 == 0xF && opcode.nible_3 == 0x6 && opcode.nible_4 == 0x5 {
|
||||||
|
opfx65(cpu, opcode.get_x());
|
||||||
|
} else {
|
||||||
|
println!{"Unknown instruction: {:04x}", opcode.opcode};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fn execute() {}
|
||||||
|
|
||||||
pub fn execute() {}
|
// fn op_0NNN() {}//ignore
|
||||||
|
|
||||||
|
// CLS
|
||||||
|
// Clear the display, turning all pixels off
|
||||||
|
fn op_00e0(cpu: &mut CPU) {
|
||||||
|
cpu.display_buffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// JMP
|
||||||
|
// Set PC to NNN, causing the program to jump to that memory location
|
||||||
|
fn op1nnn(cpu: &mut CPU, value: usize) {
|
||||||
|
cpu.pc.set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op00ee(cpu: &mut CPU) {
|
||||||
|
let value = cpu.stack.pop();
|
||||||
|
match value {
|
||||||
|
Some(value) => {
|
||||||
|
cpu.pc.set(value);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op2nnn(cpu: &mut CPU, value: usize) {
|
||||||
|
cpu.stack.push(cpu.pc.get());
|
||||||
|
cpu.pc.set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op3xnn(cpu: &mut CPU, register_x: usize, value: u8) {
|
||||||
|
if cpu.registers.get(register_x) == value {
|
||||||
|
cpu.pc.increase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op4xnn(cpu: &mut CPU, register_x: usize, value: u8) {
|
||||||
|
if cpu.registers.get(register_x) != value {
|
||||||
|
cpu.pc.increase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op5xy0(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
if cpu.registers.get(register_x) == cpu.registers.get(register_y) {
|
||||||
|
cpu.pc.increase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op9xy0(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
if cpu.registers.get(register_x) != cpu.registers.get(register_y) {
|
||||||
|
cpu.pc.increase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SET
|
||||||
|
// Set the register VX to the value NN
|
||||||
|
fn op6xnn(cpu: &mut CPU, register: usize, value: u8) {
|
||||||
|
cpu.registers.set(register, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD
|
||||||
|
// Add the value NN to VX. VF is ignored
|
||||||
|
fn op7xnn(cpu: &mut CPU, register: usize, value: u8) {
|
||||||
|
let nn = value as u16;
|
||||||
|
let vx = cpu.registers.get(register) as u16;
|
||||||
|
let value = (nn + vx) & 0xFF;
|
||||||
|
let value = value as u8;
|
||||||
|
cpu.registers.set(register, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op8xy0(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
cpu.registers.set(register_x, cpu.registers.get(register_y));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op8xy1(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
let vx = cpu.registers.get(register_x);
|
||||||
|
let vy = cpu.registers.get(register_y);
|
||||||
|
cpu.registers.set(register_x, vx | vy);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op8xy2(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
let vx = cpu.registers.get(register_x);
|
||||||
|
let vy = cpu.registers.get(register_y);
|
||||||
|
cpu.registers.set(register_x, vx & vy);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op8xy3(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
let vx = cpu.registers.get(register_x);
|
||||||
|
let vy = cpu.registers.get(register_y);
|
||||||
|
cpu.registers.set(register_x, vx ^ vy);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op8xy4(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
let vx = cpu.registers.get(register_x) as u16;
|
||||||
|
let vy = cpu.registers.get(register_y) as u16;
|
||||||
|
let value = (vx + vy) & 0xFF;
|
||||||
|
cpu.registers.set(register_x, value as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op8xy5(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
let vx = cpu.registers.get(register_x);
|
||||||
|
let vy = cpu.registers.get(register_y);
|
||||||
|
cpu.registers.set(register_x, vx.wrapping_sub(vy));
|
||||||
|
cpu.registers.set(0xF, if vx > vy {1} else {0});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op8xy6(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
let vy = cpu.registers.get(register_y);
|
||||||
|
let vx = cpu.registers.get(register_x);
|
||||||
|
cpu.registers.set(register_x, vy >> 1);
|
||||||
|
cpu.registers.set(0xF, vx & 0x1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op8xy7(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
let vx = cpu.registers.get(register_x);
|
||||||
|
let vy = cpu.registers.get(register_y);
|
||||||
|
cpu.registers.set(register_x, vy.wrapping_sub(vx));
|
||||||
|
cpu.registers.set(0xF, if vy > vx {1} else {0});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op8xye(cpu: &mut CPU, register_x: usize, register_y: usize) {
|
||||||
|
let vy = cpu.registers.get(register_y);
|
||||||
|
let vx = cpu.registers.get(register_x);
|
||||||
|
cpu.registers.set(register_x, vy << 1);
|
||||||
|
cpu.registers.set(0xF, (vx & 0x80) >> 7);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// IND
|
||||||
|
// Set index register I to the value NNN
|
||||||
|
fn opannn(cpu: &mut CPU, value: usize) {
|
||||||
|
cpu.index_register = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opbnnn(cpu: &mut CPU, value: usize) {
|
||||||
|
let v0 = cpu.registers.get(0x0) as usize;
|
||||||
|
cpu.pc.set(value + v0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opcxnn(cpu: &mut CPU, register_x: usize, value: u8) {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
cpu.registers.set(register_x,rng.gen_range(0x0..0xFF) & value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DIS
|
||||||
|
// Display
|
||||||
|
fn opdxyn(cpu: &mut CPU, register_x: usize, register_y: usize, value: u8) {
|
||||||
|
let mut vf: bool = false;
|
||||||
|
let value = value as usize;
|
||||||
|
let ori_x = cpu.registers.get(register_x) as usize % DISPLAY_WIDTH;
|
||||||
|
let ori_y = cpu.registers.get(register_y) as usize % DISPLAY_HEIGHT;
|
||||||
|
|
||||||
|
for row in 0..value {
|
||||||
|
let y = ori_y + row;
|
||||||
|
if y >= DISPLAY_HEIGHT {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sprite = cpu.ram.read8(cpu.index_register + row);
|
||||||
|
for pixel_position in 0..8 {
|
||||||
|
let x = ori_x + pixel_position;
|
||||||
|
if x >= DISPLAY_WIDTH {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let memory_pixel: bool = (sprite & (1 << (7 - pixel_position))) > 0;
|
||||||
|
let display_pixel: bool = cpu.display_buffer.0[x][y];
|
||||||
|
cpu.display_buffer.0[x][y] = memory_pixel ^ display_pixel;
|
||||||
|
vf = (memory_pixel && display_pixel) || vf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cpu.registers.set(0xF, if vf {1} else {0});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opex9e(cpu: &mut CPU, register_x: usize) {
|
||||||
|
if cpu.keypad.get(cpu.registers.get(register_x) as usize) {
|
||||||
|
cpu.pc.increase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opexa1(cpu: &mut CPU, register_x: usize) {
|
||||||
|
if !cpu.keypad.get(cpu.registers.get(register_x) as usize) {
|
||||||
|
cpu.pc.increase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opfx07(cpu: &mut CPU, register_x: usize) {
|
||||||
|
cpu.registers.set(register_x, cpu.delay_timer.tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opfx15(cpu: &mut CPU, register_x: usize) {
|
||||||
|
cpu.delay_timer.tick = cpu.registers.get(register_x);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opfx18(cpu: &mut CPU, register_x: usize) {
|
||||||
|
cpu.sound_timer.tick = cpu.registers.get(register_x);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opfx1e(cpu: &mut CPU, register_x: usize) {
|
||||||
|
cpu.index_register += cpu.registers.get(register_x) as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opfx0a(cpu: &mut CPU, register_x: usize) {
|
||||||
|
if cpu.keypad.being_pressed() == 0x10 {
|
||||||
|
cpu.pc.decrement();
|
||||||
|
} else {
|
||||||
|
cpu.registers.set(register_x, cpu.keypad.being_pressed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opfx29(cpu: &mut CPU, register_x: usize) {
|
||||||
|
let char = (cpu.registers.get(register_x) & 0xF) as usize;
|
||||||
|
cpu.index_register = 0x50 + char * 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opfx33(cpu: &mut CPU, register_x: usize) {
|
||||||
|
let vx = cpu.registers.get(register_x);
|
||||||
|
cpu.ram.write8(cpu.index_register, vx / 100);
|
||||||
|
cpu.ram.write8(cpu.index_register + 1, vx / 10 % 10);
|
||||||
|
cpu.ram.write8(cpu.index_register + 2, vx % 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opfx55(cpu: &mut CPU, register_x: usize) {
|
||||||
|
let i = cpu.index_register;
|
||||||
|
for regs in 0x0..(register_x + 1) {
|
||||||
|
cpu.ram.write8(i + regs, cpu.registers.get(regs));
|
||||||
|
}
|
||||||
|
// cpu.index_register += register_x + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opfx65(cpu: &mut CPU, register_x: usize) {
|
||||||
|
let i = cpu.index_register;
|
||||||
|
for regs in 0x0..(register_x + 1) {
|
||||||
|
cpu.registers.set(regs, cpu.ram.read8(i + regs));
|
||||||
|
}
|
||||||
|
// cpu.index_register += register_x + 1;
|
||||||
|
}
|
|
@ -1,21 +1,15 @@
|
||||||
use sdl2::pixels::Color;
|
|
||||||
use sdl2::rect::Rect;
|
|
||||||
use sdl2::render::Canvas;
|
|
||||||
use sdl2::Sdl;
|
|
||||||
use sdl2::video::Window;
|
|
||||||
|
|
||||||
const WIDTH: usize = 64;
|
const WIDTH: usize = 64;
|
||||||
const HEIGHT: usize = 32;
|
const HEIGHT: usize = 32;
|
||||||
|
|
||||||
pub struct Display {
|
pub struct Display {
|
||||||
canvas: Canvas<Window>,
|
canvas: sdl2::render::Canvas<sdl2::video::Window>,
|
||||||
scale: u32,
|
scale: u32,
|
||||||
off_color: Color,
|
off_color: sdl2::pixels::Color,
|
||||||
on_color: Color
|
on_color: sdl2::pixels::Color
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display {
|
impl Display {
|
||||||
pub fn init(sdl_context: &Sdl, scale: u32) -> Display {
|
pub fn init(sdl_context: &sdl2::Sdl, scale: u32) -> Display {
|
||||||
let video_subsystem = sdl_context.video().unwrap();
|
let video_subsystem = sdl_context.video().unwrap();
|
||||||
|
|
||||||
let window = video_subsystem.window("Chip-8", WIDTH as u32 * scale,
|
let window = video_subsystem.window("Chip-8", WIDTH as u32 * scale,
|
||||||
|
@ -29,8 +23,8 @@ impl Display {
|
||||||
Display {
|
Display {
|
||||||
canvas: canvas,
|
canvas: canvas,
|
||||||
scale: scale,
|
scale: scale,
|
||||||
off_color: Color::RGB(0, 0, 0),
|
off_color: sdl2::pixels::Color::RGB(0, 0, 0),
|
||||||
on_color: Color::RGB(255, 255, 255)
|
on_color: sdl2::pixels::Color::RGB(255, 255, 255)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +40,7 @@ impl Display {
|
||||||
let y = (y as u32 * self.scale) as i32;
|
let y = (y as u32 * self.scale) as i32;
|
||||||
let width = self.scale;
|
let width = self.scale;
|
||||||
let height = self.scale;
|
let height = self.scale;
|
||||||
self.canvas.fill_rect(Rect::new(x, y, width, height))
|
self.canvas.fill_rect(sdl2::rect::Rect::new(x, y, width, height))
|
||||||
.expect("Failed to draw pixel");
|
.expect("Failed to draw pixel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue