Refactor and improve instruction execution behavior
Standardize instruction cycle handling and update return types for better clarity and accuracy. Add cycle count assertions in tests to ensure proper execution timings. Minor formatting fixes and typo corrections throughout the codebase.
This commit is contained in:
1
.idea/dictionaries/project.xml
generated
1
.idea/dictionaries/project.xml
generated
@@ -16,6 +16,7 @@
|
|||||||
<w>reti</w>
|
<w>reti</w>
|
||||||
<w>rrca</w>
|
<w>rrca</w>
|
||||||
<w>rrla</w>
|
<w>rrla</w>
|
||||||
|
<w>tama</w>
|
||||||
<w>vram</w>
|
<w>vram</w>
|
||||||
</words>
|
</words>
|
||||||
</dictionary>
|
</dictionary>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ pub enum TargetRegister { A, B, C, D, E, H, L, }
|
|||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum TargetU16Register {AF, BC, DE, HL, SP, PC}
|
pub enum TargetU16Register {AF, BC, DE, HL, SP, PC}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Condition {
|
pub enum Condition {
|
||||||
NZ, // Not Zero
|
NZ, // Not Zero
|
||||||
Z, // Zero
|
Z, // Zero
|
||||||
|
|||||||
82
src/main.rs
82
src/main.rs
@@ -152,14 +152,14 @@ impl GPU {
|
|||||||
// Tiles rows are encoded in two bytes with the first byte always
|
// Tiles rows are encoded in two bytes with the first byte always
|
||||||
// on an even address. Bitwise ANDing the address with 0xffe
|
// on an even address. Bitwise ANDing the address with 0xffe
|
||||||
// gives us the address of the first byte.
|
// 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;
|
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 byte1 = self.vram[normalized_index];
|
||||||
let byte2 = self.vram[normalized_index + 1];
|
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.
|
// is therefore 16 bytes in total.
|
||||||
let tile_index = index / 16;
|
let tile_index = index / 16;
|
||||||
// Every two bytes is a new row
|
// 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.
|
// 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 {
|
for pixel_index in 0..8 {
|
||||||
// To determine a pixel's value we must first find the corresponding bit that encodes
|
// To determine a pixel's value, we must first find the corresponding bit that encodes
|
||||||
// that pixels value:
|
// that pixel value:
|
||||||
// 1111_1111
|
// 1111_1111
|
||||||
// 0123 4567
|
// 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.
|
// 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
|
// 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
|
// the second pixel (a.k.a pixel 1) we first the second most left bit (a.k.a bit 6) and
|
||||||
// so on.
|
// 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
|
// Bitwise ANDing this mask with our bytes will leave that particular bit with its
|
||||||
// original value and every other bit with a 0.
|
// original value and every other bit with a 0.
|
||||||
@@ -187,10 +187,10 @@ impl GPU {
|
|||||||
let lsb = byte1 & mask;
|
let lsb = byte1 & mask;
|
||||||
let msb = byte2 & 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.
|
// 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
|
// significant byte's bit is 1 and the most significant byte's bit is also 1, then we
|
||||||
// have tile value `Three`.
|
// have tile value `Three`.
|
||||||
let value = match (lsb != 0, msb != 0) {
|
let value = match (lsb != 0, msb != 0) {
|
||||||
@@ -255,7 +255,7 @@ impl MemoryBus {
|
|||||||
CART_BEGIN ..= CART_END => {
|
CART_BEGIN ..= CART_END => {
|
||||||
self.rom.write_byte(address, value)
|
self.rom.write_byte(address, value)
|
||||||
}
|
}
|
||||||
_ => self.memory[address as usize] = value
|
_ => self.memory[address] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn read_u16(&self, address: u16) -> u16 {
|
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 {
|
// if self.pc == 0xC {
|
||||||
// println!("clear vram Complete");
|
// println!("clear vram Complete");
|
||||||
// }
|
// }
|
||||||
@@ -456,7 +456,8 @@ impl CPU {
|
|||||||
// println!("{:x} {:?} {:?}", self.pc, inst, self.registers.f);
|
// println!("{:x} {:?} {:?}", self.pc, inst, self.registers.f);
|
||||||
|
|
||||||
let result = self.execute(inst);
|
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 {
|
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}
|
ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 2}
|
||||||
}
|
}
|
||||||
Target::Immediate(_) => {
|
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;
|
self.registers.f.subtract = true;
|
||||||
let (_, half_carry) = (value & 0xF).overflowing_sub(1);
|
let (_, half_carry) = (value & 0xF).overflowing_sub(1);
|
||||||
self.registers.f.half_carry = half_carry;
|
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) => {
|
Instruction::DECU16(register) => {
|
||||||
let value = self.get_u16_reg_value(register);
|
let value = self.get_u16_reg_value(register);
|
||||||
let new_value = value.wrapping_sub(1);
|
let new_value = value.wrapping_sub(1);
|
||||||
self.set_u16_reg_value(register, new_value);
|
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 => {
|
Instruction::DI => {
|
||||||
self.interrupts_enabled = false;
|
self.interrupts_enabled = false;
|
||||||
@@ -635,7 +641,7 @@ impl CPU {
|
|||||||
ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 1}
|
ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 1}
|
||||||
}
|
}
|
||||||
Instruction::HALT => {
|
Instruction::HALT => {
|
||||||
ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 1}
|
ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 3}
|
||||||
}
|
}
|
||||||
|
|
||||||
Instruction::INC(target) => {
|
Instruction::INC(target) => {
|
||||||
@@ -645,7 +651,12 @@ impl CPU {
|
|||||||
self.registers.f.zero = new_value == 0;
|
self.registers.f.zero = new_value == 0;
|
||||||
self.registers.f.subtract = false;
|
self.registers.f.subtract = false;
|
||||||
self.registers.f.half_carry = (value & 0xF) + 1 > 0xF;
|
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) => {
|
Instruction::INCU16(register) => {
|
||||||
let value = self.get_u16_reg_value(register);
|
let value = self.get_u16_reg_value(register);
|
||||||
@@ -666,7 +677,7 @@ impl CPU {
|
|||||||
}
|
}
|
||||||
Instruction::JR(condition, offset) => {
|
Instruction::JR(condition, offset) => {
|
||||||
if self.check_condition(condition) {
|
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 {
|
} else {
|
||||||
ExecutionReturn{pc: self.pc.wrapping_add(2), cycles: 2}
|
ExecutionReturn{pc: self.pc.wrapping_add(2), cycles: 2}
|
||||||
}
|
}
|
||||||
@@ -716,13 +727,21 @@ impl CPU {
|
|||||||
let offset = self.get_target_value(target);
|
let offset = self.get_target_value(target);
|
||||||
let address = 0xFF00 | offset as u16;
|
let address = 0xFF00 | offset as u16;
|
||||||
self.bus.write_byte(address, self.registers.a);
|
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) => {
|
LoadTarget::CopyAPort(target) => {
|
||||||
let offset = self.get_target_value(target);
|
let offset = self.get_target_value(target);
|
||||||
let address = 0xFF00 | offset as u16;
|
let address = 0xFF00 | offset as u16;
|
||||||
self.registers.a = self.bus.read_byte(address);
|
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) => {
|
LoadTarget::CopyAR16(source_register) => {
|
||||||
let address = self.get_u16_reg_value(source_register);
|
let address = self.get_u16_reg_value(source_register);
|
||||||
@@ -966,7 +985,7 @@ impl CPU {
|
|||||||
self.rotate_inc_pc_and_timing(target)
|
self.rotate_inc_pc_and_timing(target)
|
||||||
}
|
}
|
||||||
Instruction::STOP(_) => {
|
Instruction::STOP(_) => {
|
||||||
ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 1}
|
ExecutionReturn{pc: self.pc.wrapping_add(1), cycles: 3}
|
||||||
}
|
}
|
||||||
Instruction::SUB(target) => {
|
Instruction::SUB(target) => {
|
||||||
let value = self.get_target_value(target);
|
let value = self.get_target_value(target);
|
||||||
@@ -1012,6 +1031,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_instruction_tests() {
|
fn run_instruction_tests() {
|
||||||
|
|
||||||
for entry in glob("sm83/v1/*.json").unwrap() {
|
for entry in glob("sm83/v1/*.json").unwrap() {
|
||||||
println!("{:?}", entry);
|
println!("{:?}", entry);
|
||||||
let json_file = std::fs::read(entry.unwrap()).unwrap();
|
let json_file = std::fs::read(entry.unwrap()).unwrap();
|
||||||
@@ -1019,7 +1039,23 @@ fn run_instruction_tests() {
|
|||||||
for test in tests.as_array().unwrap() {
|
for test in tests.as_array().unwrap() {
|
||||||
// println!("{}", test["name"]);
|
// println!("{}", test["name"]);
|
||||||
let mut gameboy = CPU::load_test(&test["initial"]);
|
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"]);
|
gameboy.compare_state(&test["final"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1050,6 +1086,6 @@ fn run_gameboy() {
|
|||||||
if count % 1_000_000 == 0 {
|
if count % 1_000_000 == 0 {
|
||||||
println!("PC: {:04X}, {count}", gameboy.pc);
|
println!("PC: {:04X}, {count}", gameboy.pc);
|
||||||
}
|
}
|
||||||
gameboy.execute_next_instruction()
|
gameboy.execute_next_instruction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user