diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml
index 1f4b5cc..899ed77 100644
--- a/.idea/dictionaries/project.xml
+++ b/.idea/dictionaries/project.xml
@@ -16,6 +16,7 @@
reti
rrca
rrla
+ tama
vram
diff --git a/Cargo.toml b/Cargo.toml
index 87dc666..609a4be 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,4 +7,4 @@ edition = "2021"
[dependencies]
serde_json = "1.0.140"
-glob = "0.3.2"
\ No newline at end of file
+glob = "0.3.2"
diff --git a/src/instructions.rs b/src/instructions.rs
index d0fa99b..5b03883 100644
--- a/src/instructions.rs
+++ b/src/instructions.rs
@@ -4,7 +4,7 @@ pub enum TargetRegister { A, B, C, D, E, H, L, }
#[derive(Clone, Copy, Debug)]
pub enum TargetU16Register {AF, BC, DE, HL, SP, PC}
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Condition {
NZ, // Not Zero
Z, // Zero
@@ -606,4 +606,4 @@ fn match_cb_instruction(opcode: u8) -> Instruction {
0xFF => { Instruction::SET(7, Target::U8Register(TargetRegister::A)) }
_ => { panic!("Invalid u16 opcode: {:02X}", opcode); }
}
-}
\ No newline at end of file
+}
diff --git a/src/main.rs b/src/main.rs
index 3545e96..d83d7f7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -41,7 +41,7 @@ enum CartridgeType{
impl GameRom {
fn load(filename: &str) -> GameRom {
-
+
let data = std::fs::read(filename).unwrap();
let title_end = data[0x134..0x143].iter().position(|b| *b == 0x0).unwrap();
let title = data[0x134..0x134 + title_end].to_vec().escape_ascii().to_string();
@@ -52,7 +52,7 @@ impl GameRom {
let licensee = [data[0x144], data[0x145]];
let sgb_flag = data[0x146];
println!("{:x}", data[0x147]);
-
+
let cartridge_type = match data[0x147] {
0x00 => CartridgeType::RomOnly,
0x01 => CartridgeType::MBC1,
@@ -113,7 +113,7 @@ impl GameRom {
}
_ => {panic!("unsupported Cartridge type")}
}
-
+
}
}
@@ -152,14 +152,14 @@ impl GPU {
// Tiles rows are encoded in two bytes with the first byte always
// on an even address. Bitwise ANDing the address with 0xffe
// gives us the address of the first byte.
- // For example: `12 & 0xFFFE == 12` and `13 & 0xFFFE == 12`
+ // For example, `12 & 0xFFFE == 12` and `13 & 0xFFFE == 12`
let normalized_index = index & 0xFFFE;
- // First we need to get the two bytes that encode the tile row.
+ // First, we need to get the two bytes that encode the tile row.
let byte1 = self.vram[normalized_index];
let byte2 = self.vram[normalized_index + 1];
- // A tiles is 8 rows tall. Since each row is encoded with two bytes a tile
+ // Tiles are 8 rows tall. Since each row is encoded with two bytes, a tile
// is therefore 16 bytes in total.
let tile_index = index / 16;
// Every two bytes is a new row
@@ -167,19 +167,19 @@ impl GPU {
// Now we're going to loop 8 times to get the 8 pixels that make up a given row.
for pixel_index in 0..8 {
- // To determine a pixel's value we must first find the corresponding bit that encodes
- // that pixels value:
+ // To determine a pixel's value, we must first find the corresponding bit that encodes
+ // that pixel value:
// 1111_1111
// 0123 4567
//
- // As you can see the bit that corresponds to the nth pixel is the bit in the nth
+ // As you can see, the bit that corresponds to the nth pixel is the bit in the nth
// position *from the left*. Bits are normally indexed from the right.
//
// To find the first pixel (a.k.a pixel 0) we find the left most bit (a.k.a bit 7). For
// the second pixel (a.k.a pixel 1) we first the second most left bit (a.k.a bit 6) and
// so on.
//
- // We then create a mask with a 1 at that position and 0s everywhere else.
+ // We then create a mask with a 1 at that position and 0 s everywhere else.
//
// Bitwise ANDing this mask with our bytes will leave that particular bit with its
// original value and every other bit with a 0.
@@ -187,10 +187,10 @@ impl GPU {
let lsb = byte1 & mask;
let msb = byte2 & mask;
- // If the masked values are not 0 the masked bit must be 1. If they are 0, the masked
+ // If the masked values are not 0, the masked bit must be 1. If they are 0, the masked
// bit must be 0.
//
- // Finally we can tell which of the four tile values the pixel is. For example, if the least
+ // Finally, we can tell which of the four tile values the pixel is. For example, if the least
// significant byte's bit is 1 and the most significant byte's bit is also 1, then we
// have tile value `Three`.
let value = match (lsb != 0, msb != 0) {
@@ -255,7 +255,7 @@ impl MemoryBus {
CART_BEGIN ..= CART_END => {
self.rom.write_byte(address, value)
}
- _ => self.memory[address as usize] = value
+ _ => self.memory[address] = value
}
}
fn read_u16(&self, address: u16) -> u16 {
@@ -445,7 +445,7 @@ impl CPU {
- fn execute_next_instruction(&mut self) {
+ fn execute_next_instruction(&mut self) -> u8 {
// if self.pc == 0xC {
// println!("clear vram Complete");
// }
@@ -454,9 +454,10 @@ impl CPU {
// }
let inst = parse_instruction(self.bus.read_byte(self.pc), self.bus.read_byte(self.pc.wrapping_add(1)), self.bus.read_byte(self.pc.wrapping_add(2)));
// println!("{:x} {:?} {:?}", self.pc, inst, self.registers.f);
-
+
let result = self.execute(inst);
- self.pc = result.pc
+ self.pc = result.pc;
+ result.cycles
}
fn standard_inc_pc_and_timing(&mut self, target: Target) -> ExecutionReturn {
@@ -468,7 +469,7 @@ impl CPU {
ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 2}
}
Target::Immediate(_) => {
- ExecutionReturn{pc: self.pc.wrapping_add(2), cycles: 3}
+ ExecutionReturn{pc: self.pc.wrapping_add(2), cycles: 2}
},
}
}
@@ -618,13 +619,18 @@ impl CPU {
self.registers.f.subtract = true;
let (_, half_carry) = (value & 0xF).overflowing_sub(1);
self.registers.f.half_carry = half_carry;
- self.standard_inc_pc_and_timing(target)
+ match target {
+ Target::U8Register(_) => {ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 1}}
+ Target::U16Register(TargetU16Register::SP) => {ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 2}}
+ Target::U16Register(_) => {ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 3}}
+ Target::Immediate(_) => {panic!("Does not accept immediate target")}
+ }
}
Instruction::DECU16(register) => {
let value = self.get_u16_reg_value(register);
let new_value = value.wrapping_sub(1);
self.set_u16_reg_value(register, new_value);
- ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 1}
+ ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 2}
}
Instruction::DI => {
self.interrupts_enabled = false;
@@ -635,7 +641,7 @@ impl CPU {
ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 1}
}
Instruction::HALT => {
- ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 1}
+ ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 3}
}
Instruction::INC(target) => {
@@ -645,7 +651,12 @@ impl CPU {
self.registers.f.zero = new_value == 0;
self.registers.f.subtract = false;
self.registers.f.half_carry = (value & 0xF) + 1 > 0xF;
- self.standard_inc_pc_and_timing(target)
+ match target {
+ Target::U8Register(_) => {ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 1}}
+ Target::U16Register(TargetU16Register::SP) => {ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 2}}
+ Target::U16Register(_) => {ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 3}}
+ Target::Immediate(_) => {panic!("Does not accept immediate target")}
+ }
}
Instruction::INCU16(register) => {
let value = self.get_u16_reg_value(register);
@@ -666,7 +677,7 @@ impl CPU {
}
Instruction::JR(condition, offset) => {
if self.check_condition(condition) {
- ExecutionReturn{pc: self.pc.wrapping_add_signed(offset as i16).wrapping_add(2), cycles: 4}
+ ExecutionReturn{pc: self.pc.wrapping_add_signed(offset as i16).wrapping_add(2), cycles: 3}
} else {
ExecutionReturn{pc: self.pc.wrapping_add(2), cycles: 2}
}
@@ -716,13 +727,21 @@ impl CPU {
let offset = self.get_target_value(target);
let address = 0xFF00 | offset as u16;
self.bus.write_byte(address, self.registers.a);
- self.standard_inc_pc_and_timing(target)
+ match target {
+ Target::U8Register(_) => {ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 2}}
+ Target::U16Register(_) => {ExecutionReturn{pc: self.pc.wrapping_add(2), cycles: 3}}
+ Target::Immediate(_) => {ExecutionReturn{pc: self.pc.wrapping_add(2), cycles: 3}}
+ }
}
LoadTarget::CopyAPort(target) => {
let offset = self.get_target_value(target);
let address = 0xFF00 | offset as u16;
self.registers.a = self.bus.read_byte(address);
- self.standard_inc_pc_and_timing(target)
+ match target {
+ Target::U8Register(_) => {ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 2}}
+ Target::U16Register(_) => {ExecutionReturn{pc: self.pc.wrapping_add(2), cycles: 3}}
+ Target::Immediate(_) => {ExecutionReturn{pc: self.pc.wrapping_add(2), cycles: 3}}
+ }
}
LoadTarget::CopyAR16(source_register) => {
let address = self.get_u16_reg_value(source_register);
@@ -966,7 +985,7 @@ impl CPU {
self.rotate_inc_pc_and_timing(target)
}
Instruction::STOP(_) => {
- ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 1}
+ ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 3}
}
Instruction::SUB(target) => {
let value = self.get_target_value(target);
@@ -1012,6 +1031,7 @@ fn main() {
}
fn run_instruction_tests() {
+
for entry in glob("sm83/v1/*.json").unwrap() {
println!("{:?}", entry);
let json_file = std::fs::read(entry.unwrap()).unwrap();
@@ -1019,7 +1039,23 @@ fn run_instruction_tests() {
for test in tests.as_array().unwrap() {
// println!("{}", test["name"]);
let mut gameboy = CPU::load_test(&test["initial"]);
- gameboy.execute_next_instruction();
+
+ // Get the instruction before executing it
+ let pc = gameboy.pc;
+ let inst = parse_instruction(
+ gameboy.bus.read_byte(pc),
+ gameboy.bus.read_byte(pc.wrapping_add(1)),
+ gameboy.bus.read_byte(pc.wrapping_add(2))
+ );
+
+ // Execute the instruction and get the cycle count
+ let cycles = gameboy.execute_next_instruction();
+
+ // Verify the cycle count
+ let expected_cycles = test["cycles"].as_array().unwrap().len();
+ assert_eq!(cycles, expected_cycles as u8);
+
+ // Compare the final state
gameboy.compare_state(&test["final"]);
}
}
@@ -1050,6 +1086,6 @@ fn run_gameboy() {
if count % 1_000_000 == 0 {
println!("PC: {:04X}, {count}", gameboy.pc);
}
- gameboy.execute_next_instruction()
+ gameboy.execute_next_instruction();
}
-}
\ No newline at end of file
+}