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.

//! Flag rules that are not the arithmetic unit's, written once for both ways of running.
//!
//! Each of these is a measured behavioural rule rather than a convenience. They live here because
//! the edge-stepped sequencer and the instruction-stepped engine both need them, and a rule that
//! two implementations state separately is a rule that can end up meaning two things.

use crate::consts::flag;
use crate::types::UndocumentedFlags;

pub(crate) const fn take_undocumented(flags: u8, source: u8) -> u8 {
    (flags & !flag::XY_MASK) | (source & flag::XY_MASK)
}

pub(crate) fn report_byte(flags: u8, value: u8, parity: bool) -> u8 {
    let mut flags =
        flags & !(flag::N_MASK | flag::H_MASK | flag::S_MASK | flag::Z_MASK | flag::P_MASK);
    if value & 0x80 != 0 {
        flags |= flag::S_MASK;
    }
    if value == 0 {
        flags |= flag::Z_MASK;
    }
    if parity {
        flags |= flag::P_MASK;
    }
    flags
}

pub(crate) const fn condition_holds(flags: u8, code: u8) -> bool {
    let mask = match (code >> 1) & 0x03 {
        0b00 => flag::Z_MASK,
        0b01 => flag::C_MASK,
        0b10 => flag::P_MASK,
        _ => flag::S_MASK,
    };
    (flags & mask != 0) == (code & 0x01 != 0)
}

pub(crate) const fn undocumented_carry_source(
    mode: UndocumentedFlags,
    accumulator: u8,
    flags: u8,
) -> u8 {
    match mode {
        UndocumentedFlags::Accumulator => accumulator,
        UndocumentedFlags::Combined => accumulator | flags,
    }
}

// The documented rule for the port block instructions, which the part this was derived from does not
// implement at all — it writes only the counter's own flags. The published vectors govern, so this
// is what both ways of running follow. The addend is the port with the same step applied for the
// counting forms and the low half of the pointer for the outward ones.
pub(crate) fn port_block(flags: u8, bc: u16, hl: u16, ir: u8, value: u8) -> u8 {
    let count = bc.to_be_bytes()[0];
    let addend = if ir & 0x01 == 0 {
        let port = bc.to_be_bytes()[1];
        if ir & 0x08 == 0 {
            port.wrapping_add(1)
        } else {
            port.wrapping_sub(1)
        }
    } else {
        hl.to_be_bytes()[1]
    };

    let sum = u16::from(value) + u16::from(addend);
    let carried = sum & 0x100 != 0;
    let parity = ((sum.to_be_bytes()[1] & 0x07) ^ count)
        .count_ones()
        .is_multiple_of(2);

    let mut flags = take_undocumented(report_byte(flags, count, parity), count);
    if value & 0x80 != 0 {
        flags |= flag::N_MASK;
    }
    if carried {
        flags |= flag::H_MASK | flag::C_MASK;
    } else {
        flags &= !(flag::H_MASK | flag::C_MASK);
    }
    flags
}