Skip to content

Commit

Permalink
major refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
pcasaretto committed Apr 12, 2024
1 parent 821f437 commit 586435d
Show file tree
Hide file tree
Showing 27 changed files with 1,178 additions and 905 deletions.
66 changes: 66 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ edition = "2021"
[dependencies]
env_logger = "0.11.3"
log = "0.4.21"
rand = "0.8.5"
serde = {version = "1.0.197", features = ["derive"]}
serde_json = "1.0.115"
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
channel = "1.77.0"
channel = "nightly"
components = [ "rustfmt", "clippy", "rust-analyzer", "rust-src" ]
70 changes: 6 additions & 64 deletions src/cpu.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::instructions;



#[derive(Debug, Copy, Clone)]
pub enum RegisterTarget {
Expand All @@ -18,6 +19,7 @@ pub enum Register16bTarget {
DE,
HL,
SP,
PC,
}

#[derive(Default)]
Expand All @@ -31,6 +33,7 @@ pub struct Registers {
pub l: u8,
pub f: FlagsRegister,
pub sp: u16,
pub pc: u16,
}

impl Registers {
Expand Down Expand Up @@ -65,6 +68,7 @@ impl Registers {
Register16bTarget::DE => u16::from_be_bytes([self.d, self.e]),
Register16bTarget::HL => u16::from_be_bytes([self.h, self.l]),
Register16bTarget::SP => self.sp,
Register16bTarget::PC => self.pc,
}
}

Expand All @@ -84,6 +88,7 @@ impl Registers {
self.l = low;
}
Register16bTarget::SP => self.sp = value,
Register16bTarget::PC => self.pc = value,
Register16bTarget::AF => {
self.a = high;
self.f = FlagsRegister::from(low);
Expand Down Expand Up @@ -130,77 +135,14 @@ impl std::convert::From<u8> for FlagsRegister {
}
}

/// CPU
pub struct CPU {
pub registers: Registers,
pub pc: u16,
pub bus: MemoryBus,
pub stepHook: Box<dyn FnMut(u8)>,
}

pub struct MemoryBus {
pub memory: [u8; 0x10000],
}

impl MemoryBus {
pub fn read_byte(&self, address: u16) -> u8 {
self.memory[address as usize]
}

pub fn write_byte(&mut self, address: u16, value: u8) {
self.memory[address as usize] = value;
}
}

impl Default for CPU {
fn default() -> Self {
CPU {
registers: Registers::default(),
pc: 0,
bus: MemoryBus {
memory: [0; 0x10000],
},
stepHook: Box::new(|_| {}),
}
}
}

impl CPU {
pub fn step(&mut self) {
let instruction_byte = self.read_next_byte();
let instruction = instructions::from_byte(instruction_byte);
(self.stepHook)(instruction_byte);
instruction(self);
}

pub fn read_next_byte(&mut self) -> u8 {
let byte = self.bus.read_byte(self.pc);
self.pc = self.pc.wrapping_add(1);
byte
}
}

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

#[test]
fn test_add() {
let mut cpu = CPU {
registers: Registers {
a: 3,
c: 4,
f: FlagsRegister::from(0),
..Default::default()
},
bus: MemoryBus {
memory: [0x81; 0x10000],
},
pc: 1245,
..Default::default()
};
cpu.step();
assert_eq!(cpu.registers.a, 7);
assert_eq!(cpu.pc, 1246);
}
}
6 changes: 6 additions & 0 deletions src/emulator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use crate::gameboy;

pub fn run(cartridge: &[u8; 0x200000]) {
let mut gameboy = gameboy::Gameboy::default();
gameboy.run(cartridge);
}
108 changes: 108 additions & 0 deletions src/gameboy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use crate::cpu::{FlagsRegister, Register16bTarget, CPU};
use crate::instructions;
use crate::memory::MemoryBus;
use crate::opcode_info::OpcodeInfo;
use serde_json;

pub struct Gameboy<'a> {
pub cpu: CPU,
pub bus: MemoryBus,
pub cartridge: &'a [u8; 0x200000],
pub opcode_info: OpcodeInfo,
}

impl Default for Gameboy<'_> {
fn default() -> Self {
// load opcode info
let json = include_bytes!("Opcodes.json");
let opcode_info: OpcodeInfo = serde_json::from_slice(json).unwrap();

let cpu = CPU::default();

Self {
cpu,
opcode_info,
bus: MemoryBus::default(),
cartridge: &[0; 0x200000],
}
}
}

// DMG initialization sequence
pub fn initialize(gameboy: &mut Gameboy) {
gameboy.cpu.registers.a = 0x01;
gameboy.cpu.registers.f.zero = true;
//TODO: set carry and half carry based on header checksum
gameboy.cpu.registers.c = 0x13;
gameboy.cpu.registers.e = 0xD8;
gameboy.cpu.registers.h = 0x01;
gameboy.cpu.registers.l = 0x4D;
gameboy.cpu.registers.pc = 0x100;
}

impl<'a> Gameboy<'a> {
pub fn run(self: &'a mut Self, cartridge: &'a [u8; 0x200000]) {
self.cartridge = cartridge;

// load first 0x8000 bytes of cartridge into memory
self.bus.memory[..0x8000].copy_from_slice(&self.cartridge[..0x8000]);

loop {
self.step();
}
}

pub fn step(&mut self) {
let instruction_byte = self.read_next_byte();
let opcode_info = self
.opcode_info
.unprefixed
.get(&format!("0x{:02X}", instruction_byte))
.unwrap();
log::debug!(
"instruction_byte: 0x{:02X}, program counter: 0x{:04X}",
instruction_byte,
self.cpu.registers.get_u16(Register16bTarget::PC)
);
let instruction = instructions::from_byte(instruction_byte);
instruction(self);
}

pub fn read_next_byte(&mut self) -> u8 {
let address = self.cpu.registers.get_u16(Register16bTarget::PC);
let byte = self.bus.read_byte(address);
self.cpu
.registers
.set_u16(Register16bTarget::PC, address.wrapping_add(1));
byte
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::cpu::{FlagsRegister, Register16bTarget, RegisterTarget, Registers};

#[test]
fn test_add() {
let mut gameboy = Gameboy {
cpu: CPU {
registers: Registers {
a: 3,
c: 4,
f: FlagsRegister::from(0),
pc: 1245,
..Default::default()
},
..Default::default()
},
bus: MemoryBus {
memory: [0x81; 0x10000],
},
..Default::default()
};
gameboy.step();
assert_eq!(gameboy.cpu.registers.get_u8(RegisterTarget::A), 7);
assert_eq!(gameboy.cpu.registers.get_u16(Register16bTarget::PC), 1246);
}
}
Loading

0 comments on commit 586435d

Please sign in to comment.