pub mod disasm;
mod exec;
pub mod isa;
#[cfg(test)]
mod tests;
#[cfg(all(test, feature = "std"))]
mod conformance;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::device::{Device, DeviceClass, Initiator, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::registry::Registry;
use crate::core::space::{AddressSpace, MemAttrs, RequesterId};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{self, AtomicBool, AtomicU8, LockRank, Ordering};
use crate::core::value::Width;
use crate::core::wire::{FanIn, Level, Resolve, WireId, WireSink};
use exec::{Exec, State};
pub mod flags {
pub const C: u8 = 0x01;
pub const N: u8 = 0x02;
pub const PV: u8 = 0x04;
pub const XF: u8 = 0x08;
pub const H: u8 = 0x10;
pub const YF: u8 = 0x20;
pub const Z: u8 = 0x40;
pub const S: u8 = 0x80;
pub const XY: u8 = XF | YF;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Regs {
pub a: u8,
pub f: u8,
pub b: u8,
pub c: u8,
pub d: u8,
pub e: u8,
pub h: u8,
pub l: u8,
pub ix: u16,
pub iy: u16,
pub sp: u16,
pub pc: u16,
pub i: u8,
pub r: u8,
pub wz: u16,
pub af_alt: u16,
pub bc_alt: u16,
pub de_alt: u16,
pub hl_alt: u16,
}
macro_rules! pair {
($get:ident, $set:ident, $hi:ident, $lo:ident, $name:literal) => {
#[doc = concat!("The ", $name, " pair.")]
#[inline]
#[must_use]
pub const fn $get(&self) -> u16 {
((self.$hi as u16) << 8) | self.$lo as u16
}
#[doc = concat!("Overwrite the ", $name, " pair.")]
#[inline]
pub const fn $set(&mut self, value: u16) {
self.$hi = (value >> 8) as u8;
self.$lo = value as u8;
}
};
}
impl Regs {
#[must_use]
pub const fn new() -> Regs {
Regs {
a: 0,
f: 0,
b: 0,
c: 0,
d: 0,
e: 0,
h: 0,
l: 0,
ix: 0,
iy: 0,
sp: 0,
pc: 0,
i: 0,
r: 0,
wz: 0,
af_alt: 0,
bc_alt: 0,
de_alt: 0,
hl_alt: 0,
}
}
pair!(bc, set_bc, b, c, "`BC`");
pair!(de, set_de, d, e, "`DE`");
pair!(hl, set_hl, h, l, "`HL`");
#[inline]
#[must_use]
pub const fn af(&self) -> u16 {
((self.a as u16) << 8) | self.f as u16
}
#[inline]
pub const fn set_af(&mut self, value: u16) {
self.a = (value >> 8) as u8;
self.f = value as u8;
}
#[inline]
#[must_use]
pub const fn flag(&self, mask: u8) -> bool {
self.f & mask != 0
}
}
impl fmt::Display for Regs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"AF:{:04x} BC:{:04x} DE:{:04x} HL:{:04x} IX:{:04x} IY:{:04x} \
SP:{:04x} PC:{:04x} I:{:02x} R:{:02x} WZ:{:04x}",
self.af(),
self.bc(),
self.de(),
self.hl(),
self.ix,
self.iy,
self.sp,
self.pc,
self.i,
self.r,
self.wz
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Reg {
Af,
Bc,
De,
Hl,
Ix,
Iy,
Sp,
Pc,
I,
R,
Wz,
AfAlt,
BcAlt,
DeAlt,
HlAlt,
}
impl Reg {
pub const ALL: &'static [Reg] = &[
Reg::Af,
Reg::Bc,
Reg::De,
Reg::Hl,
Reg::Ix,
Reg::Iy,
Reg::Sp,
Reg::Pc,
Reg::I,
Reg::R,
Reg::Wz,
Reg::AfAlt,
Reg::BcAlt,
Reg::DeAlt,
Reg::HlAlt,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Reg::Af => "af",
Reg::Bc => "bc",
Reg::De => "de",
Reg::Hl => "hl",
Reg::Ix => "ix",
Reg::Iy => "iy",
Reg::Sp => "sp",
Reg::Pc => "pc",
Reg::I => "i",
Reg::R => "r",
Reg::Wz => "wz",
Reg::AfAlt => "af'",
Reg::BcAlt => "bc'",
Reg::DeAlt => "de'",
Reg::HlAlt => "hl'",
}
}
#[must_use]
pub const fn width(self) -> Width {
match self {
Reg::I | Reg::R => Width::U8,
_ => Width::U16,
}
}
#[must_use]
pub const fn get(self, regs: &Regs) -> u16 {
match self {
Reg::Af => regs.af(),
Reg::Bc => regs.bc(),
Reg::De => regs.de(),
Reg::Hl => regs.hl(),
Reg::Ix => regs.ix,
Reg::Iy => regs.iy,
Reg::Sp => regs.sp,
Reg::Pc => regs.pc,
Reg::I => regs.i as u16,
Reg::R => regs.r as u16,
Reg::Wz => regs.wz,
Reg::AfAlt => regs.af_alt,
Reg::BcAlt => regs.bc_alt,
Reg::DeAlt => regs.de_alt,
Reg::HlAlt => regs.hl_alt,
}
}
pub const fn set(self, regs: &mut Regs, value: u16) {
match self {
Reg::Af => regs.set_af(value),
Reg::Bc => regs.set_bc(value),
Reg::De => regs.set_de(value),
Reg::Hl => regs.set_hl(value),
Reg::Ix => regs.ix = value,
Reg::Iy => regs.iy = value,
Reg::Sp => regs.sp = value,
Reg::Pc => regs.pc = value,
Reg::I => regs.i = value as u8,
Reg::R => regs.r = value as u8,
Reg::Wz => regs.wz = value,
Reg::AfAlt => regs.af_alt = value,
Reg::BcAlt => regs.bc_alt = value,
Reg::DeAlt => regs.de_alt = value,
Reg::HlAlt => regs.hl_alt = value,
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Reg> {
Reg::ALL.iter().copied().find(|r| r.name() == name)
}
}
impl fmt::Display for Reg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum MCycle {
Fetch,
Read,
Write,
PortRead,
PortWrite,
Ack,
#[default]
Internal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct BusCycle {
pub kind: MCycle,
pub addr: u16,
pub value: u8,
pub refresh: u16,
pub tstates: u8,
}
pub const CYCLE_LOG_LEN: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CycleLog {
cycles: [BusCycle; CYCLE_LOG_LEN],
len: u8,
truncated: bool,
}
impl CycleLog {
#[must_use]
pub const fn new() -> CycleLog {
CycleLog {
cycles: [BusCycle {
kind: MCycle::Internal,
addr: 0,
value: 0,
refresh: 0,
tstates: 0,
}; CYCLE_LOG_LEN],
len: 0,
truncated: false,
}
}
#[inline]
#[must_use]
pub fn cycles(&self) -> &[BusCycle] {
&self.cycles[..self.len as usize]
}
#[inline]
#[must_use]
pub const fn truncated(&self) -> bool {
self.truncated
}
#[must_use]
pub fn tstates(&self) -> u32 {
self.cycles().iter().map(|c| u32::from(c.tstates)).sum()
}
#[inline]
pub(crate) fn clear(&mut self) {
self.len = 0;
self.truncated = false;
}
#[inline]
pub(crate) fn push(&mut self, cycle: BusCycle) {
match self.cycles.get_mut(self.len as usize) {
Some(slot) => {
*slot = cycle;
self.len += 1;
}
None => self.truncated = true,
}
}
}
impl Default for CycleLog {
fn default() -> Self {
CycleLog::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Interrupt {
Int,
Nmi,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub out_c_zero: u8,
pub floating_bus: u8,
pub requester: RequesterId,
}
impl Config {
pub const NMOS: Config = Config {
out_c_zero: 0x00,
floating_bus: 0xff,
requester: RequesterId::ANONYMOUS,
};
pub const CMOS: Config = Config {
out_c_zero: 0xff,
..Config::NMOS
};
#[must_use]
pub const fn with_requester(mut self, id: RequesterId) -> Self {
self.requester = id;
self
}
}
impl Default for Config {
fn default() -> Self {
Config::NMOS
}
}
#[derive(Debug)]
pub(crate) struct Lines {
int: AtomicBool,
nmi_level: AtomicBool,
nmi_latch: AtomicBool,
vector: AtomicU8,
}
impl Default for Lines {
fn default() -> Self {
Lines {
int: AtomicBool::new(false),
nmi_level: AtomicBool::new(false),
nmi_latch: AtomicBool::new(false),
vector: AtomicU8::new(0xff),
}
}
}
impl Lines {
fn set_int(&self, asserted: bool) {
self.int.store(asserted, Ordering::Release);
}
fn irq_asserted(&self) -> bool {
self.int.load(Ordering::Acquire)
}
fn set_nmi(&self, asserted: bool) {
let previous = self.nmi_level.swap(asserted, Ordering::AcqRel);
if asserted && !previous {
self.nmi_latch.store(true, Ordering::Release);
}
}
fn nmi_pending(&self) -> bool {
self.nmi_latch.load(Ordering::Acquire)
}
fn take_nmi_pending(&self) -> bool {
self.nmi_latch.swap(false, Ordering::AcqRel)
}
fn clear_nmi_latch(&self) {
self.nmi_latch.store(false, Ordering::Release);
}
fn vector(&self) -> u8 {
self.vector.load(Ordering::Acquire)
}
fn set_vector(&self, value: u8) {
self.vector.store(value, Ordering::Release);
}
fn snapshot(&self) -> (bool, bool, bool, u8) {
(
self.irq_asserted(),
self.nmi_level.load(Ordering::Acquire),
self.nmi_pending(),
self.vector(),
)
}
fn restore(&self, (int, level, latch, vector): (bool, bool, bool, u8)) {
self.int.store(int, Ordering::Release);
self.nmi_level.store(level, Ordering::Release);
self.nmi_latch.store(latch, Ordering::Release);
self.vector.store(vector, Ordering::Release);
}
}
#[derive(Debug)]
struct Session {
state: State,
space: Option<Arc<AddressSpace>>,
io: Option<Arc<AddressSpace>>,
}
#[derive(Debug)]
pub struct Z80 {
cfg: Config,
lines: Lines,
session: sync::Mutex<Session>,
}
impl Z80 {
#[must_use]
pub fn new(cfg: Config) -> Z80 {
Z80 {
cfg,
lines: Lines::default(),
session: sync::Mutex::with_rank(
LockRank::BUS,
Session {
state: State::new(),
space: None,
io: None,
},
),
}
}
pub fn from_props(props: &Props) -> Result<Z80> {
let mut r = props.reader();
let cmos = r.or("cmos", false)?;
let default = if cmos { Config::CMOS } else { Config::NMOS };
let out_c_zero = r.or_range("out-c-zero", u64::from(default.out_c_zero), 0..=0xff)?;
let floating = r.or_range("floating-bus", u64::from(default.floating_bus), 0..=0xff)?;
r.finish()?;
Ok(Z80::new(Config {
out_c_zero: out_c_zero as u8,
floating_bus: floating as u8,
requester: RequesterId::ANONYMOUS,
}))
}
#[must_use]
pub fn config(&self) -> Config {
self.cfg
}
pub fn attach_space(&self, space: Arc<AddressSpace>) {
self.session.lock().space = Some(space);
}
pub fn attach_io_space(&self, space: Arc<AddressSpace>) {
self.session.lock().io = Some(space);
}
#[must_use]
pub fn space(&self) -> Option<Arc<AddressSpace>> {
self.session.lock().space.clone()
}
#[must_use]
pub fn io_space(&self) -> Option<Arc<AddressSpace>> {
self.session.lock().io.clone()
}
#[must_use]
pub fn regs(&self) -> Regs {
self.session.lock().state.regs
}
pub fn set_regs(&self, regs: Regs) {
self.session.lock().state.regs = regs;
}
#[must_use]
pub fn reg(&self, reg: Reg) -> u16 {
reg.get(&self.session.lock().state.regs)
}
pub fn set_reg(&self, reg: Reg, value: u16) {
reg.set(&mut self.session.lock().state.regs, value);
}
#[must_use]
pub fn cycles(&self) -> u64 {
self.session.lock().state.cycles
}
#[must_use]
pub fn last_cycles(&self) -> CycleLog {
self.session.lock().state.trace
}
#[must_use]
pub fn is_halted(&self) -> bool {
self.session.lock().state.halted
}
#[must_use]
pub fn reset_pending(&self) -> bool {
self.session.lock().state.reset_pending
}
#[must_use]
pub fn iff(&self) -> (bool, bool) {
let s = self.session.lock();
(s.state.iff1, s.state.iff2)
}
pub fn set_iff(&self, iff1: bool, iff2: bool) {
let mut s = self.session.lock();
s.state.iff1 = iff1;
s.state.iff2 = iff2;
}
#[must_use]
pub fn interrupt_mode(&self) -> u8 {
self.session.lock().state.im
}
pub fn set_interrupt_mode(&self, mode: u8) -> Result<()> {
if mode > 2 {
return Err(Error::Property(alloc::format!(
"interrupt mode {mode} does not exist; the Z80 has modes 0, 1 and 2"
)));
}
self.session.lock().state.im = mode;
Ok(())
}
#[must_use]
pub fn bus_faults(&self) -> (u64, u16) {
let s = self.session.lock();
(s.state.faults, s.state.last_fault)
}
pub fn set_int(&self, asserted: bool) {
self.lines.set_int(asserted);
}
#[must_use]
pub fn int_asserted(&self) -> bool {
self.lines.irq_asserted()
}
pub fn set_nmi(&self, asserted: bool) {
self.lines.set_nmi(asserted);
}
pub fn pulse_nmi(&self) {
self.lines.set_nmi(true);
self.lines.set_nmi(false);
}
#[must_use]
pub fn nmi_pending(&self) -> bool {
self.lines.nmi_pending()
}
pub fn set_interrupt_vector(&self, vector: u8) {
self.lines.set_vector(vector);
}
#[must_use]
pub fn interrupt_vector(&self) -> u8 {
self.lines.vector()
}
pub fn request_reset(&self) {
self.session.lock().state.reset_pending = true;
}
pub fn step(&self) -> u64 {
let mut session = self.session.lock();
let Session { state, space, io } = &mut *session;
let io = io.as_deref();
let Some(space) = space.as_deref() else {
return 0;
};
Exec::new(state, space, io, &self.cfg, &self.lines).step()
}
pub fn run(&self, budget: u64) -> u64 {
let mut used = 0;
while used < budget {
let n = self.step();
if n == 0 {
break;
}
used += n;
}
used
}
#[must_use]
pub fn disassemble(&self, pc: u16, count: usize) -> Vec<disasm::Disassembled> {
let Some(space) = self.space() else {
return Vec::new();
};
disasm::disassemble_run(pc, count, |addr| {
space
.read(u64::from(addr), Width::U8, MemAttrs::DEBUG)
.ok()
.map(|v| v as u8)
})
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: "cpu.z80",
version: 1,
summary: "Zilog Z80 8-bit CPU core, cycle-accurate interpreter",
properties: &[
PropertySpec {
name: "cmos",
kind: ValueKind::Bool,
required: false,
summary: "select the CMOS part, whose OUT (C),0 writes $ff instead of $00",
},
PropertySpec {
name: "out-c-zero",
kind: ValueKind::Uint,
required: false,
summary: "the byte the undocumented OUT (C),0 writes, overriding the part default",
},
PropertySpec {
name: "floating-bus",
kind: ValueKind::Uint,
required: false,
summary: "what a read nothing answers returns; $ff is a bus with pull-ups",
},
],
construct: |props| Ok(Box::new(Z80::from_props(props)?)),
};
pub fn register(reg: &mut Registry) -> Result<()> {
reg.add(&CLASS)
}
impl Device for Z80 {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, ctx: &mut RealizeCtx<'_>) -> Result<()> {
if self.session.lock().space.is_none() {
return Err(ctx.error("no address space attached to this core"));
}
Ok(())
}
fn reset(&self, kind: ResetKind) {
let mut session = self.session.lock();
if kind == ResetKind::Cold {
session.state = State::new();
} else {
session.state.reset_pending = true;
session.state.halted = false;
}
drop(session);
if kind == ResetKind::Cold {
self.lines.restore((false, false, false, 0xff));
} else {
self.lines.clear_nmi_latch();
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.session.lock().state;
let r = state.regs;
for value in [
r.af(),
r.bc(),
r.de(),
r.hl(),
r.ix,
r.iy,
r.sp,
r.pc,
r.wz,
r.af_alt,
r.bc_alt,
r.de_alt,
r.hl_alt,
] {
w.write_u16(value)?;
}
w.write_u8(r.i)?;
w.write_u8(r.r)?;
w.write_bool(state.iff1)?;
w.write_bool(state.iff2)?;
w.write_u8(state.im)?;
w.write_bool(state.halted)?;
w.write_bool(state.ei_pending)?;
w.write_bool(state.after_ld_ir)?;
w.write_u8(state.q)?;
w.write_u64(state.cycles)?;
w.write_bool(state.reset_pending)?;
w.write_u64(state.faults)?;
w.write_u16(state.last_fault)?;
let (int, nmi_level, nmi_latch, vector) = self.lines.snapshot();
w.write_bool(int)?;
w.write_bool(nmi_level)?;
w.write_bool(nmi_latch)?;
w.write_u8(vector)?;
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut state = State::new();
let regs = &mut state.regs;
regs.set_af(r.read_u16()?);
regs.set_bc(r.read_u16()?);
regs.set_de(r.read_u16()?);
regs.set_hl(r.read_u16()?);
regs.ix = r.read_u16()?;
regs.iy = r.read_u16()?;
regs.sp = r.read_u16()?;
regs.pc = r.read_u16()?;
regs.wz = r.read_u16()?;
regs.af_alt = r.read_u16()?;
regs.bc_alt = r.read_u16()?;
regs.de_alt = r.read_u16()?;
regs.hl_alt = r.read_u16()?;
regs.i = r.read_u8()?;
regs.r = r.read_u8()?;
state.iff1 = r.read_bool()?;
state.iff2 = r.read_bool()?;
state.im = r.read_u8()?;
if state.im > 2 {
return Err(Error::State(alloc::format!(
"snapshot names interrupt mode {}, which does not exist",
state.im
)));
}
state.halted = r.read_bool()?;
state.ei_pending = r.read_bool()?;
state.after_ld_ir = r.read_bool()?;
state.q = r.read_u8()?;
state.cycles = r.read_u64()?;
state.reset_pending = r.read_bool()?;
state.faults = r.read_u64()?;
state.last_fault = r.read_u16()?;
let int = r.read_bool()?;
let nmi_level = r.read_bool()?;
let nmi_latch = r.read_bool()?;
let vector = r.read_u8()?;
self.session.lock().state = state;
self.lines.restore((int, nmi_level, nmi_latch, vector));
Ok(())
}
}
impl Initiator for Z80 {
fn requester(&self) -> RequesterId {
self.cfg.requester
}
}
#[derive(Debug)]
pub struct InterruptPin {
cpu: Arc<Z80>,
which: Interrupt,
inputs: FanIn,
resolve: Resolve,
}
impl InterruptPin {
#[must_use]
pub fn new(cpu: Arc<Z80>, which: Interrupt, sources: &[WireId]) -> InterruptPin {
InterruptPin {
cpu,
which,
inputs: FanIn::new(sources),
resolve: Resolve::Or,
}
}
#[must_use]
pub fn with_resolve(mut self, resolve: Resolve) -> Self {
self.resolve = resolve;
self
}
#[must_use]
pub fn which(&self) -> Interrupt {
self.which
}
#[must_use]
pub fn inputs(&self) -> &FanIn {
&self.inputs
}
}
impl WireSink for InterruptPin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
let asserted = self.inputs.resolve(self.resolve).is_high();
match self.which {
Interrupt::Int => self.cpu.set_int(asserted),
Interrupt::Nmi => self.cpu.set_nmi(asserted),
}
}
}
#[must_use]
pub fn describe_isa() -> String {
use core::fmt::Write as _;
let mut out = String::new();
for opcode in 0..=255u8 {
let insn = isa::decode(opcode);
let mark = if insn.class.is_documented() { ' ' } else { '*' };
let _ = writeln!(
out,
"{opcode:02x} {mark}{:<10} {}",
disasm::mnemonic_and_operands(insn),
insn.op.summary()
);
}
out
}