hg80 1.0.0

Z80 and Z80N CPU core, stepped one clock edge at a time
Documentation
// Portions of this file are derived from the T80 Z80-compatible microprocessor core,
// Copyright (c) 2001-2002 Daniel Wallner, and from the T80N modifications made for the
// ZX Spectrum Next Project, Copyright 2020 Fabio Belavenuto, Victor Trucco, Charlie Ingley,
// Garry Lancaster, ACX. Redistributed under the three-clause BSD licence reproduced in NOTICE.

//! The arithmetic and logic unit.
//!
//! A pure function of the two operand buses, the flag register and the decoded operation. It
//! returns the result and the new flags, and holds no state of its own — which matches the
//! combinational block it's derived from, and makes every operation testable on its own.

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);
    // The three logical operations reach none of the adder, so they do not run it. It is the only
    // part of this function that costs anything to compute.
    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;