Implement (messy) LEA for stack pointer addresses
parent
a7adfb6f90
commit
cfebc1b912
150
src/run.rs
150
src/run.rs
|
@ -1,3 +1,5 @@
|
|||
use std::ops::Add;
|
||||
|
||||
use instructions::{Instruction, RunnableInstruction};
|
||||
|
||||
use crate::{
|
||||
|
@ -36,6 +38,7 @@ macro_rules! assert_ok {
|
|||
}
|
||||
|
||||
impl Processor {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn run(&mut self, instruction: &RunnableInstruction) {
|
||||
match instruction.instruction {
|
||||
Instruction::LD8BitImmediateToRegister { value, register } => {
|
||||
|
@ -147,6 +150,54 @@ impl Processor {
|
|||
let value = self.registers.get_16bit_register(src);
|
||||
self.registers.set_16bit_register(dst, value);
|
||||
}
|
||||
|
||||
Instruction::LDEffectiveAddress { dst, offset } => {
|
||||
let current_sp = self
|
||||
.registers
|
||||
.get_16bit_register(register::SixteenBit::Single(
|
||||
register::SingleSixteenBit::StackPointer,
|
||||
));
|
||||
|
||||
// TODO: This is gross. I'll clean this up when I do more ALU instructions
|
||||
// because there's bound to be reuse
|
||||
let (sixteen_bit_offset, new_sp, carry) = if offset >= 0 {
|
||||
let sixteen_bit_offset = u16::try_from(offset)
|
||||
.expect("failed to put positive (or zero) 8 bit value into unsigned sixteen bit, which should always work...");
|
||||
let new_sp = current_sp + sixteen_bit_offset;
|
||||
// If the lower eight bits are lower than where we started (with a known positive number)
|
||||
// we must have carried.
|
||||
let carry =
|
||||
(((current_sp & 0xff) + (sixteen_bit_offset & 0xff)) & 0x100) == 0x100;
|
||||
|
||||
(sixteen_bit_offset, new_sp, carry)
|
||||
} else {
|
||||
let sixteen_bit_offset = u16::try_from(i16::from(offset).abs())
|
||||
.expect("failed to convert an abs'd 16 bit value to a u16");
|
||||
let (new_sp, underflow) = current_sp.overflowing_sub(sixteen_bit_offset);
|
||||
|
||||
(sixteen_bit_offset, new_sp, underflow)
|
||||
};
|
||||
|
||||
let carry_bit = if carry { 1 } else { 0 };
|
||||
let half_carry_bit =
|
||||
// https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
|
||||
if ((current_sp & 0xf) + (sixteen_bit_offset & 0xf)) & 0x10 == 0x10 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
self.registers
|
||||
.set_flag_bit(register::Flag::Carry, carry_bit);
|
||||
self.registers
|
||||
.set_flag_bit(register::Flag::HalfCarry, half_carry_bit);
|
||||
|
||||
// Manual says we reset these here.
|
||||
self.registers.set_flag_bit(register::Flag::Subtract, 0);
|
||||
self.registers.set_flag_bit(register::Flag::Zero, 0);
|
||||
|
||||
self.registers.set_combined_register(dst, new_sp);
|
||||
}
|
||||
}
|
||||
|
||||
self.num_cycles += u64::from(instruction.cycles);
|
||||
|
@ -621,4 +672,103 @@ mod tests {
|
|||
|
||||
assert_eq!(0x1234, processor.registers.stack_pointer);
|
||||
}
|
||||
|
||||
#[test_case(0, 0xFF00)]
|
||||
#[test_case(16, 0xFF10)]
|
||||
#[test_case(-5, 0xFEFB)]
|
||||
#[test_case(127, 0xFF7F)]
|
||||
#[test_case(-127, 0xFE81)]
|
||||
#[test_case(-128, 0xFE80)]
|
||||
fn test_load_effective_address(value: i8, expected_sp: u16) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.set_16bit_register(
|
||||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
|
||||
0xFF00,
|
||||
);
|
||||
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
// The truncation will only happen if |value| > 128 in the branch where we check it's equal to -128
|
||||
// so we're fine.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let unsigned_value = if value >= 0 {
|
||||
value as u8
|
||||
} else if value == i8::MIN {
|
||||
i16::from(i8::MIN).abs() as u8
|
||||
} else {
|
||||
!(value.abs() as u8) + 1
|
||||
};
|
||||
let data = [0xF8, unsigned_value, 0x00];
|
||||
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(
|
||||
expected_sp,
|
||||
processor
|
||||
.registers
|
||||
.get_combined_register(register::Combined::HL)
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x0000, 0x00, 0, 0; "no addition")]
|
||||
#[test_case(0x00FF, 0x20, 0, 1; "overflow")]
|
||||
#[test_case(0x00C0, 0x7f, 0, 1; "overflow 2")]
|
||||
#[test_case(0x00FF, 0x01, 1, 1; "overflow with half carry")]
|
||||
#[test_case(0x000A, 0x0C, 1, 0; "half carry")]
|
||||
#[test_case(0x0018, -0xC, 1, 0; "negative half carry")]
|
||||
#[test_case(0x000E, -0x0F, 1, 1; "underflow with half carry")]
|
||||
fn test_load_effective_address_flags(
|
||||
starting_sp: u16,
|
||||
add_value: i8,
|
||||
half_carry: u8,
|
||||
carry: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.stack_pointer = starting_sp;
|
||||
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
// The truncation will only happen if |value| > 128 in the branch where we check it's equal to -128
|
||||
// so we're fine.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let unsigned_value = if add_value >= 0 {
|
||||
add_value as u8
|
||||
} else if add_value == i8::MIN {
|
||||
i16::from(i8::MIN).abs() as u8
|
||||
} else {
|
||||
!(add_value.abs() as u8) + 1
|
||||
};
|
||||
|
||||
let data = [0xF8, unsigned_value, 0x00];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
|
||||
assert_eq!(extra_data, &[0x00]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(
|
||||
half_carry,
|
||||
processor.registers.get_flag_bit(register::Flag::HalfCarry),
|
||||
"incorrect half carry bit"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
carry,
|
||||
processor.registers.get_flag_bit(register::Flag::Carry),
|
||||
"incorrect carry bit"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
0,
|
||||
processor.registers.get_flag_bit(register::Flag::Zero),
|
||||
"incorrect zero bit"
|
||||
);
|
||||
assert_eq!(
|
||||
0,
|
||||
processor.registers.get_flag_bit(register::Flag::Subtract),
|
||||
"incorrect subtract bit"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,11 @@ pub enum Instruction {
|
|||
dst: register::SixteenBit,
|
||||
src: register::SixteenBit,
|
||||
},
|
||||
// 3.3.3.4
|
||||
LDEffectiveAddress {
|
||||
dst: register::Combined,
|
||||
offset: i8,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct RunnableInstruction {
|
||||
|
|
|
@ -28,6 +28,7 @@ impl OpcodeParser for Immediate16BitLoadParser {
|
|||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
|
||||
data,
|
||||
),
|
||||
0xF8 => make_load_effective_address(register::Combined::HL, data),
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
|
@ -49,3 +50,33 @@ fn make_load_immediate_data(dst: register::SixteenBit, data: &[u8]) -> ParseResu
|
|||
&data[3..],
|
||||
))
|
||||
}
|
||||
|
||||
fn make_load_effective_address(dst: register::Combined, data: &[u8]) -> ParseResult {
|
||||
let opcode = parse::get_opcode_from_data(data)?;
|
||||
let unsigned_value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
|
||||
let signed_value = {
|
||||
match i8::try_from(*unsigned_value) {
|
||||
Ok(value) => value,
|
||||
// Convert this from a two's complement unsigned to a signed
|
||||
Err(_err) => {
|
||||
let binary_negated = i8::try_from(!unsigned_value).expect(
|
||||
"a presumably negative value couldn't be converted to a signed 8 bit value",
|
||||
);
|
||||
// Normally we'd do twos complement plus one, then negative, but the edge case of -128
|
||||
// means we must distribute the negative, so this becomes a subtraction
|
||||
-binary_negated - 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok((
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::LDEffectiveAddress {
|
||||
dst,
|
||||
offset: signed_value,
|
||||
},
|
||||
cycles: 12,
|
||||
},
|
||||
&data[2..],
|
||||
))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue