use crate::consts::flag;
use crate::mcode::InstructionSet;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub(crate) enum AluOp {
#[default]
Add,
Adc,
Sub,
Sbc,
And,
Xor,
Or,
Cp,
Rotate,
Bit,
Set,
Res,
Daa,
Rld,
Rrd,
None,
}
impl AluOp {
pub(crate) fn from_bits(bits: u8) -> Self {
match bits & 0x0F {
0b0000 => Self::Add,
0b0001 => Self::Adc,
0b0010 => Self::Sub,
0b0011 => Self::Sbc,
0b0100 => Self::And,
0b0101 => Self::Xor,
0b0110 => Self::Or,
0b0111 => Self::Cp,
0b1000 => Self::Rotate,
0b1001 => Self::Bit,
0b1010 => Self::Set,
0b1011 => Self::Res,
0b1100 => Self::Daa,
0b1101 => Self::Rld,
0b1110 => Self::Rrd,
_ => Self::None,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) struct Inputs {
pub op: AluOp,
pub ir: u8,
pub instruction_set: InstructionSet,
pub bus_a: u8,
pub bus_b: u8,
pub flags: u8,
pub preserve_result_flags: bool,
pub combine_zero: bool,
}
pub(crate) fn execute(inputs: Inputs) -> (u8, u8) {
match inputs.op {
AluOp::Add
| AluOp::Adc
| AluOp::Sub
| AluOp::Sbc
| AluOp::And
| AluOp::Xor
| AluOp::Or
| AluOp::Cp => arithmetic(inputs),
AluOp::Rotate => rotate(inputs),
AluOp::Bit => test_bit(inputs),
AluOp::Set => (inputs.bus_b | bit_mask(inputs.ir), inputs.flags),
AluOp::Res => (inputs.bus_b & !bit_mask(inputs.ir), inputs.flags),
AluOp::Daa => decimal_adjust(inputs),
AluOp::Rld | AluOp::Rrd => rotate_nibble(inputs),
AluOp::None => (0, inputs.flags),
}
}
struct AddSub {
result: u8,
half_carry: bool,
carry: bool,
overflow: bool,
}
fn add_sub(a: u8, b: u8, subtract: bool, carry: bool) -> AddSub {
let operand = if subtract { !b } else { b };
let injected = u8::from(subtract ^ carry);
let nibble = (a & 0x0F) + (operand & 0x0F) + injected;
let below_sign = u16::from(a & 0x7F) + u16::from(operand & 0x7F) + u16::from(injected);
let whole = u16::from(a) + u16::from(operand) + u16::from(injected);
AddSub {
result: a.wrapping_add(operand).wrapping_add(injected),
half_carry: nibble & 0x10 != 0,
carry: whole & 0x100 != 0,
overflow: (whole & 0x100 != 0) ^ (below_sign & 0x80 != 0),
}
}
fn arithmetic(inputs: Inputs) -> (u8, u8) {
let mut flags = inputs.flags & !(flag::N_MASK | flag::C_MASK);
let result = match inputs.op {
AluOp::And => {
flags |= flag::H_MASK;
inputs.bus_a & inputs.bus_b
}
AluOp::Xor => {
flags &= !flag::H_MASK;
inputs.bus_a ^ inputs.bus_b
}
AluOp::Or => {
flags &= !flag::H_MASK;
inputs.bus_a | inputs.bus_b
}
_ => {
let subtract = matches!(inputs.op, AluOp::Sub | AluOp::Sbc | AluOp::Cp);
let carry =
matches!(inputs.op, AluOp::Adc | AluOp::Sbc) && is_set(inputs.flags, flag::C_MASK);
let sum = add_sub(inputs.bus_a, inputs.bus_b, subtract, carry);
if subtract {
flags |= flag::N_MASK;
}
flags = write(flags, flag::C_MASK, sum.carry ^ subtract);
flags = write(flags, flag::H_MASK, sum.half_carry ^ subtract);
flags = write(flags, flag::P_MASK, sum.overflow);
sum.result
}
};
let undocumented_source = if matches!(inputs.op, AluOp::Cp) {
inputs.bus_b
} else {
result
};
flags = copy_undocumented(flags, undocumented_source);
flags = write(flags, flag::S_MASK, result & 0x80 != 0);
flags = write(
flags,
flag::Z_MASK,
result == 0 && (!inputs.combine_zero || is_set(inputs.flags, flag::Z_MASK)),
);
if matches!(inputs.op, AluOp::And | AluOp::Xor | AluOp::Or) {
flags = write(flags, flag::P_MASK, is_even_parity(result));
}
if inputs.preserve_result_flags {
flags = hold_result_flags(flags, inputs.flags);
}
(result, flags)
}
fn rotate(inputs: Inputs) -> (u8, u8) {
let value = inputs.bus_a;
let carry_in = u8::from(is_set(inputs.flags, flag::C_MASK));
let (result, carry) = match (inputs.ir >> 3) & 0x07 {
0b000 => (value.rotate_left(1), value & 0x80 != 0),
0b001 => (value.rotate_right(1), value & 0x01 != 0),
0b010 => ((value << 1) | carry_in, value & 0x80 != 0),
0b011 => ((value >> 1) | (carry_in << 7), value & 0x01 != 0),
0b100 => (value << 1, value & 0x80 != 0),
0b101 => ((value >> 1) | (value & 0x80), value & 0x01 != 0),
0b110 => ((value << 1) | 0x01, value & 0x80 != 0),
_ => (value >> 1, value & 0x01 != 0),
};
let mut flags = inputs.flags & !(flag::H_MASK | flag::N_MASK);
flags = write(flags, flag::C_MASK, carry);
flags = copy_undocumented(flags, result);
flags = describe_result(flags, result);
if matches!(inputs.instruction_set, InstructionSet::Base) {
flags = hold_result_flags(flags, inputs.flags);
}
(result, flags)
}
fn test_bit(inputs: Inputs) -> (u8, u8) {
let result = inputs.bus_b & bit_mask(inputs.ir);
let mut flags = inputs.flags & !(flag::N_MASK | flag::XY_MASK);
flags |= flag::H_MASK;
flags = write(flags, flag::S_MASK, result & 0x80 != 0);
flags = write(flags, flag::Z_MASK, result == 0);
flags = write(flags, flag::P_MASK, result == 0);
if inputs.ir & 0x07 != 0b110 {
flags = copy_undocumented(flags, inputs.bus_b);
}
(result, flags)
}
fn decimal_adjust(inputs: Inputs) -> (u8, u8) {
let mut flags = inputs.flags;
let mut adjusted = u16::from(inputs.bus_a);
let low_digit = adjusted & 0x0F;
if is_set(inputs.flags, flag::N_MASK) {
if low_digit > 9 || is_set(inputs.flags, flag::H_MASK) {
if low_digit > 5 {
flags &= !flag::H_MASK;
}
adjusted = (adjusted & 0x100) | (adjusted.wrapping_sub(6) & 0xFF);
}
if inputs.bus_a > 153 || is_set(inputs.flags, flag::C_MASK) {
adjusted = adjusted.wrapping_sub(0x160) & 0x1FF;
}
} else {
if low_digit > 9 || is_set(inputs.flags, flag::H_MASK) {
flags = write(flags, flag::H_MASK, low_digit > 9);
adjusted = (adjusted + 0x06) & 0x1FF;
}
if adjusted >> 4 > 9 || is_set(inputs.flags, flag::C_MASK) {
adjusted = (adjusted + 0x60) & 0x1FF;
}
}
let result = low_byte(adjusted);
flags = write(
flags,
flag::C_MASK,
is_set(inputs.flags, flag::C_MASK) || adjusted & 0x100 != 0,
);
flags = copy_undocumented(flags, result);
flags = describe_result(flags, result);
(result, flags)
}
fn rotate_nibble(inputs: Inputs) -> (u8, u8) {
let digit = if matches!(inputs.op, AluOp::Rld) {
inputs.bus_b >> 4
} else {
inputs.bus_b & 0x0F
};
let result = (inputs.bus_a & 0xF0) | digit;
let mut flags = inputs.flags & !(flag::H_MASK | flag::N_MASK);
flags = copy_undocumented(flags, result);
flags = describe_result(flags, result);
(result, flags)
}
fn bit_mask(ir: u8) -> u8 {
1 << ((ir >> 3) & 0x07)
}
fn low_byte(value: u16) -> u8 {
value.to_le_bytes()[0]
}
fn is_set(flags: u8, mask: u8) -> bool {
flags & mask == mask
}
fn write(flags: u8, mask: u8, condition: bool) -> u8 {
if condition {
flags | mask
} else {
flags & !mask
}
}
fn is_even_parity(value: u8) -> bool {
value.count_ones().is_multiple_of(2)
}
fn copy_undocumented(flags: u8, source: u8) -> u8 {
(flags & !flag::XY_MASK) | (source & flag::XY_MASK)
}
fn describe_result(flags: u8, result: u8) -> u8 {
let flags = write(flags, flag::S_MASK, result & 0x80 != 0);
let flags = write(flags, flag::Z_MASK, result == 0);
write(flags, flag::P_MASK, is_even_parity(result))
}
fn hold_result_flags(flags: u8, previous: u8) -> u8 {
const HELD: u8 = flag::S_MASK | flag::Z_MASK | flag::P_MASK;
(flags & !HELD) | (previous & HELD)
}
#[cfg(test)]
#[path = "alu_tests.rs"]
mod tests;