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 instructions::{Instruction, RunnableInstruction};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -36,6 +38,7 @@ macro_rules! assert_ok {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Processor {
|
impl Processor {
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn run(&mut self, instruction: &RunnableInstruction) {
|
fn run(&mut self, instruction: &RunnableInstruction) {
|
||||||
match instruction.instruction {
|
match instruction.instruction {
|
||||||
Instruction::LD8BitImmediateToRegister { value, register } => {
|
Instruction::LD8BitImmediateToRegister { value, register } => {
|
||||||
|
@ -147,6 +150,54 @@ impl Processor {
|
||||||
let value = self.registers.get_16bit_register(src);
|
let value = self.registers.get_16bit_register(src);
|
||||||
self.registers.set_16bit_register(dst, value);
|
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);
|
self.num_cycles += u64::from(instruction.cycles);
|
||||||
|
@ -621,4 +672,103 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(0x1234, processor.registers.stack_pointer);
|
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,
|
dst: register::SixteenBit,
|
||||||
src: register::SixteenBit,
|
src: register::SixteenBit,
|
||||||
},
|
},
|
||||||
|
// 3.3.3.4
|
||||||
|
LDEffectiveAddress {
|
||||||
|
dst: register::Combined,
|
||||||
|
offset: i8,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RunnableInstruction {
|
pub struct RunnableInstruction {
|
||||||
|
|
|
@ -28,6 +28,7 @@ impl OpcodeParser for Immediate16BitLoadParser {
|
||||||
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
|
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
|
||||||
data,
|
data,
|
||||||
),
|
),
|
||||||
|
0xF8 => make_load_effective_address(register::Combined::HL, data),
|
||||||
_ => Err(Error::UnknownOpcode(opcode)),
|
_ => Err(Error::UnknownOpcode(opcode)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,3 +50,33 @@ fn make_load_immediate_data(dst: register::SixteenBit, data: &[u8]) -> ParseResu
|
||||||
&data[3..],
|
&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