Implement (messy) LEA for stack pointer addresses

jsmoo
Nick Krichevsky 2022-04-09 18:25:41 -04:00
parent a7adfb6f90
commit cfebc1b912
3 changed files with 186 additions and 0 deletions

View File

@ -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"
);
}
}

View File

@ -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 {

View File

@ -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..],
))
}