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 shape of an instruction, read out of the decode tables rather than written down again.
//!
//! An instruction is a sequence of machine cycles, each with a kind and a length. The decode
//! already knows both — it is asked once per cycle and answers with `machine_cycles` and
//! `t_states` — but it answers one cycle at a time, which is what forces the sequencer to walk an
//! instruction an edge at a time to find out how long it is.
//!
//! Asking it for every cycle up front gives the whole shape in one place. Deriving that rather than
//! writing it out a second time is deliberate: a hand-written table is a second source of truth
//! that can drift from the first, and the first is the one the oracles check.
//!
//! Some instructions have no single shape. A conditional jump is shorter when it is not taken, a
//! block instruction repeats while its counter says so, and the extended set reads the accumulator
//! and the data latch. [`Plan::varies`] says so rather than pretending otherwise, and is derived by
//! asking the same question with different answers to those inputs.

#![allow(
    dead_code,
    reason = "the instruction-stepped layer is what consumes this; until then only its tests do"
)]

use crate::mcode::{self, Context, IndexState, InstructionSet};
use crate::types::MachineCycle;

#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub(crate) enum CycleKind {
    #[default]
    Fetch,
    MemoryRead,
    MemoryWrite,
    PortRead,
    PortWrite,
    Internal,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub(crate) struct PlannedCycle {
    pub kind: CycleKind,
    pub t_states: u8,
}

pub(crate) const MOST_CYCLES: usize = 7;

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) struct Plan {
    cycles: [PlannedCycle; MOST_CYCLES],
    count: u8,
    pub varies: bool,
}

impl Plan {
    pub(crate) fn cycles(&self) -> &[PlannedCycle] {
        &self.cycles[..self.count as usize]
    }

    pub(crate) fn t_states(&self) -> u32 {
        self.cycles().iter().map(|c| u32::from(c.t_states)).sum()
    }
}

fn context(set: InstructionSet, ir: u8, cycle: MachineCycle, seed: u8, z80n: bool) -> Context {
    Context {
        ir,
        instruction_set: set,
        machine_cycle: cycle,
        flags: seed,
        nmi_cycle: false,
        interrupt_cycle: false,
        index_state: IndexState::None,
        accumulator: seed,
        data: seed,
        z80n_enabled: z80n,
    }
}

fn kind_of(decoded: &mcode::Decoded, cycle: MachineCycle) -> CycleKind {
    if matches!(cycle, MachineCycle::M1) {
        return CycleKind::Fetch;
    }
    if decoded.no_read() && !decoded.write() {
        return CycleKind::Internal;
    }
    match (decoded.write(), decoded.iorq()) {
        (true, true) => CycleKind::PortWrite,
        (true, false) => CycleKind::MemoryWrite,
        (false, true) => CycleKind::PortRead,
        (false, false) => CycleKind::MemoryRead,
    }
}

// The cycle count is taken from each cycle's own answer rather than from the first, because a
// later cycle can revise it — a conditional jump that is not taken says so at its second cycle,
// not at its first. Reading it once at the fetch would make every conditional look like its taken
// form and hide the difference the seeds are there to find.
fn shape(
    set: InstructionSet,
    ir: u8,
    seed: u8,
    z80n: bool,
) -> ([PlannedCycle; MOST_CYCLES], u8, bool) {
    let mut cycles = [PlannedCycle::default(); MOST_CYCLES];
    let mut count = 0u8;
    let mut repeats = false;
    for (number, slot) in (1u8..).zip(cycles.iter_mut()) {
        let cycle = MachineCycle::from_number(number);
        let decoded = mcode::decode(context(set, ir, cycle, seed, z80n));
        let kind = kind_of(&decoded, cycle);
        // A port cycle carries an automatic wait the decode does not count: the sequencer holds
        // T1 for it, which is also why a port transfer completes a T-state later than a memory one.
        let port = matches!(kind, CycleKind::PortRead | CycleKind::PortWrite);
        *slot = PlannedCycle {
            kind,
            t_states: decoded.t_states + u8::from(port),
        };
        repeats |= decoded.i_bt() || decoded.i_bc() || decoded.i_btr();
        count = number;
        if count >= decoded.machine_cycles {
            break;
        }
    }
    (cycles, count, repeats)
}

// `varies` is measured rather than assumed: the same question is asked with four different
// answers to the flags, the accumulator and the data latch, and if any of them changes the shape
// then the instruction does not have a single one.
pub(crate) fn plan(set: InstructionSet, ir: u8, z80n: bool) -> Plan {
    let (cycles, count, repeats) = shape(set, ir, 0x00, z80n);
    // A block instruction's length is decided by the sequencer rather than the decode: it runs its
    // last cycle only while the counter says to repeat, and no answer to the decode reveals that.
    // So the seeds cannot find it and it has to be recognised from the operation itself.
    let varies = repeats
        || [0xFFu8, 0x55, 0xAA]
            .into_iter()
            .any(|seed| shape(set, ir, seed, z80n) != (cycles, count, repeats));
    Plan {
        cycles,
        count,
        varies,
    }
}

#[cfg(test)]
#[path = "plan_tests.rs"]
mod tests;