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::{self, Write as _};
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, AtomicU16, AtomicU32, LockRank, Ordering};
use crate::core::value::Width;
use crate::core::wire::{FanIn, Level, Resolve, WireId, WireSink};
use exec::{Exec, State};
pub const ADDRESS_MASK: u32 = 0x00ff_ffff;
pub mod flags {
pub const C: u16 = 0x0001;
pub const V: u16 = 0x0002;
pub const Z: u16 = 0x0004;
pub const N: u16 = 0x0008;
pub const X: u16 = 0x0010;
pub const CCR: u16 = 0x001f;
pub const IPL: u16 = 0x0700;
pub const S: u16 = 0x2000;
pub const T: u16 = 0x8000;
pub const IMPLEMENTED: u16 = T | S | IPL | CCR;
}
pub mod vector {
pub const RESET_SSP: u8 = 0;
pub const RESET_PC: u8 = 1;
pub const BUS_ERROR: u8 = 2;
pub const ADDRESS_ERROR: u8 = 3;
pub const ILLEGAL: u8 = 4;
pub const DIVIDE_BY_ZERO: u8 = 5;
pub const CHK: u8 = 6;
pub const TRAPV: u8 = 7;
pub const PRIVILEGE: u8 = 8;
pub const TRACE: u8 = 9;
pub const LINE_A: u8 = 10;
pub const LINE_F: u8 = 11;
pub const UNINITIALIZED: u8 = 15;
pub const SPURIOUS: u8 = 24;
pub const AUTOVECTOR_BASE: u8 = 24;
pub const TRAP_BASE: u8 = 32;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Regs {
pub d: [u32; 8],
pub a: [u32; 8],
pub usp: u32,
pub ssp: u32,
pub pc: u32,
pub sr: u16,
pub prefetch: [u16; 2],
}
impl Regs {
#[must_use]
pub const fn supervisor(&self) -> bool {
self.sr & flags::S != 0
}
#[inline]
#[must_use]
pub const fn flag(&self, mask: u16) -> bool {
self.sr & mask != 0
}
#[must_use]
pub const fn ccr(&self) -> u8 {
(self.sr & flags::CCR) as u8
}
#[must_use]
pub const fn ipl_mask(&self) -> u8 {
((self.sr & flags::IPL) >> 8) as u8
}
}
impl fmt::Display for Regs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, value) in self.d.iter().enumerate() {
write!(f, "D{i}:{value:08x} ")?;
}
for (i, value) in self.a.iter().enumerate() {
write!(f, "A{i}:{value:08x} ")?;
}
write!(f, "PC:{:08x} SR:{:04x} [", self.pc, self.sr)?;
for (mask, name) in [
(flags::T, 'T'),
(flags::S, 'S'),
(flags::X, 'X'),
(flags::N, 'N'),
(flags::Z, 'Z'),
(flags::V, 'V'),
(flags::C, 'C'),
] {
f.write_char(if self.flag(mask) { name } else { '-' })?;
}
write!(f, "] I{}", self.ipl_mask())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Reg {
D(u8),
A(u8),
Usp,
Ssp,
Pc,
Sr,
}
impl Reg {
pub const ALL: &'static [Reg] = &[
Reg::D(0),
Reg::D(1),
Reg::D(2),
Reg::D(3),
Reg::D(4),
Reg::D(5),
Reg::D(6),
Reg::D(7),
Reg::A(0),
Reg::A(1),
Reg::A(2),
Reg::A(3),
Reg::A(4),
Reg::A(5),
Reg::A(6),
Reg::A(7),
Reg::Usp,
Reg::Ssp,
Reg::Pc,
Reg::Sr,
];
#[must_use]
pub const fn width(self) -> Width {
match self {
Reg::Sr => Width::U16,
_ => Width::U32,
}
}
#[must_use]
pub const fn get(self, regs: &Regs) -> u32 {
match self {
Reg::D(n) => regs.d[(n & 7) as usize],
Reg::A(n) => regs.a[(n & 7) as usize],
Reg::Usp => regs.usp,
Reg::Ssp => regs.ssp,
Reg::Pc => regs.pc,
Reg::Sr => regs.sr as u32,
}
}
pub const fn set(self, regs: &mut Regs, value: u32) {
match self {
Reg::D(n) => regs.d[(n & 7) as usize] = value,
Reg::A(n) => {
let n = (n & 7) as usize;
regs.a[n] = value;
if n == 7 {
if regs.supervisor() {
regs.ssp = value;
} else {
regs.usp = value;
}
}
}
Reg::Usp => {
regs.usp = value;
if !regs.supervisor() {
regs.a[7] = value;
}
}
Reg::Ssp => {
regs.ssp = value;
if regs.supervisor() {
regs.a[7] = value;
}
}
Reg::Pc => regs.pc = value,
Reg::Sr => regs.sr = value as u16,
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Reg> {
let bytes = name.as_bytes();
match (bytes.first(), bytes.len()) {
(Some(b'd' | b'D'), 2) if bytes[1].is_ascii_digit() && bytes[1] <= b'7' => {
Some(Reg::D(bytes[1] - b'0'))
}
(Some(b'a' | b'A'), 2) if bytes[1].is_ascii_digit() && bytes[1] <= b'7' => {
Some(Reg::A(bytes[1] - b'0'))
}
_ => match name {
"usp" => Some(Reg::Usp),
"ssp" | "sp" => Some(Reg::Ssp),
"pc" => Some(Reg::Pc),
"sr" => Some(Reg::Sr),
_ => None,
},
}
}
}
impl fmt::Display for Reg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Reg::D(n) => write!(f, "d{n}"),
Reg::A(n) => write!(f, "a{n}"),
Reg::Usp => f.write_str("usp"),
Reg::Ssp => f.write_str("ssp"),
Reg::Pc => f.write_str("pc"),
Reg::Sr => f.write_str("sr"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub requester: RequesterId,
}
impl Config {
pub const MC68000: Config = Config {
requester: RequesterId::ANONYMOUS,
};
#[must_use]
pub const fn with_requester(mut self, id: RequesterId) -> Self {
self.requester = id;
self
}
}
impl Default for Config {
fn default() -> Self {
Config::MC68000
}
}
const NO_VECTOR: u16 = 0x100;
#[derive(Debug)]
pub(crate) struct Lines {
ipl: AtomicU8,
vector: AtomicU16,
level_seven: AtomicBool,
resets: AtomicU32,
}
impl Default for Lines {
fn default() -> Lines {
Lines {
ipl: AtomicU8::new(0),
vector: AtomicU16::new(NO_VECTOR),
level_seven: AtomicBool::new(false),
resets: AtomicU32::new(0),
}
}
}
impl Lines {
fn set_ipl(&self, level: u8) {
let level = level.min(7);
let previous = self.ipl.swap(level, Ordering::AcqRel);
if level == 7 && previous != 7 {
self.level_seven.store(true, Ordering::Release);
}
}
pub(crate) fn take_level_seven(&self) -> bool {
self.level_seven.swap(false, Ordering::AcqRel)
}
pub(crate) fn ipl(&self) -> u8 {
self.ipl.load(Ordering::Acquire)
}
fn set_vector(&self, vector: Option<u8>) {
self.vector
.store(vector.map_or(NO_VECTOR, u16::from), Ordering::Release);
}
pub(crate) fn take_vector(&self) -> Option<u8> {
match self.vector.swap(NO_VECTOR, Ordering::AcqRel) {
NO_VECTOR => None,
other => Some(other as u8),
}
}
pub(crate) fn pulse_reset(&self) {
self.resets.fetch_add(1, Ordering::AcqRel);
}
fn resets(&self) -> u32 {
self.resets.load(Ordering::Acquire)
}
fn snapshot(&self) -> (u8, u16, bool, u32) {
(
self.ipl(),
self.vector.load(Ordering::Acquire),
self.level_seven.load(Ordering::Acquire),
self.resets(),
)
}
fn restore(&self, (ipl, vector, level_seven, resets): (u8, u16, bool, u32)) {
self.ipl.store(ipl, Ordering::Release);
self.vector.store(vector, Ordering::Release);
self.level_seven.store(level_seven, Ordering::Release);
self.resets.store(resets, Ordering::Release);
}
}
#[derive(Debug)]
struct Session {
state: State,
space: Option<Arc<AddressSpace>>,
}
#[derive(Debug)]
pub struct M68k {
cfg: Config,
lines: Lines,
session: sync::Mutex<Session>,
}
impl M68k {
#[must_use]
pub fn new(cfg: Config) -> M68k {
M68k {
cfg,
lines: Lines::default(),
session: sync::Mutex::with_rank(
LockRank::BUS,
Session {
state: State::new(),
space: None,
},
),
}
}
pub fn from_props(props: &Props) -> Result<M68k> {
let mut r = props.reader();
let requester = r.or_range("requester", 0u64, 0..=u64::from(u32::MAX))?;
r.finish()?;
Ok(M68k::new(
Config::default().with_requester(RequesterId(requester as u32)),
))
}
#[must_use]
pub fn config(&self) -> Config {
self.cfg
}
pub fn attach_space(&self, space: Arc<AddressSpace>) {
self.session.lock().space = Some(space);
}
#[must_use]
pub fn space(&self) -> Option<Arc<AddressSpace>> {
self.session.lock().space.clone()
}
#[must_use]
pub fn regs(&self) -> Regs {
let state = self.session.lock().state;
Regs {
d: state.d,
a: state.a,
usp: state.usp(),
ssp: state.ssp(),
pc: state.pc,
sr: state.sr,
prefetch: state.prefetch,
}
}
pub fn set_regs(&self, regs: Regs) {
let mut session = self.session.lock();
let state = &mut session.state;
state.d = regs.d;
state.a = regs.a;
state.sr = regs.sr & flags::IMPLEMENTED;
if state.supervisor() {
state.a[7] = regs.ssp;
state.other_sp = regs.usp;
} else {
state.a[7] = regs.usp;
state.other_sp = regs.ssp;
}
state.pc = regs.pc;
state.prefetch = regs.prefetch;
}
#[must_use]
pub fn reg(&self, reg: Reg) -> u32 {
reg.get(&self.regs())
}
pub fn set_reg(&self, reg: Reg, value: u32) {
let mut regs = self.regs();
reg.set(&mut regs, value);
self.set_regs(regs);
}
#[must_use]
pub fn cycles(&self) -> u64 {
self.session.lock().state.cycles
}
#[must_use]
pub fn is_halted(&self) -> bool {
self.session.lock().state.halted
}
#[must_use]
pub fn is_stopped(&self) -> bool {
self.session.lock().state.stopped
}
#[must_use]
pub fn reset_pending(&self) -> bool {
self.session.lock().state.reset_pending
}
#[must_use]
pub fn bus_faults(&self) -> (u64, u32) {
let s = self.session.lock();
(s.state.faults, s.state.last_fault)
}
#[must_use]
pub fn reset_pulses(&self) -> u32 {
self.lines.resets()
}
pub fn set_ipl(&self, level: u8) {
self.lines.set_ipl(level);
}
#[must_use]
pub fn ipl(&self) -> u8 {
self.lines.ipl()
}
pub fn set_interrupt_vector(&self, vector: Option<u8>) {
self.lines.set_vector(vector);
}
#[must_use]
pub fn interrupt_vector(&self) -> Option<u8> {
match self.lines.vector.load(Ordering::Acquire) {
NO_VECTOR => None,
other => Some(other as u8),
}
}
pub fn set_reset_pending(&self, pending: bool) {
self.session.lock().state.reset_pending = pending;
}
pub fn resume(&self) {
let mut session = self.session.lock();
session.state.halted = false;
session.state.stopped = false;
}
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 } = &mut *session;
let Some(space) = space.clone() else {
return 0;
};
Exec::new(state, &space, &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: u32, 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 & ADDRESS_MASK), Width::U16, MemAttrs::DEBUG)
.ok()
.map(|v| v as u16)
})
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: "cpu.m68k",
version: 1,
summary: "Motorola MC68000 32-bit CPU core, bus-accurate interpreter",
properties: &[PropertySpec {
name: "requester",
kind: ValueKind::Uint,
required: false,
summary: "this core's requester id in MemAttrs, for an IOMMU or a per-master filter",
}],
construct: |props| Ok(Box::new(M68k::from_props(props)?)),
};
pub fn register(reg: &mut Registry) -> Result<()> {
reg.add(&CLASS)
}
impl Device for M68k {
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;
session.state.stopped = false;
}
drop(session);
if kind == ResetKind::Cold {
self.lines.restore((0, NO_VECTOR, false, 0));
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.session.lock().state;
for value in state.d {
w.write_u32(value)?;
}
for value in state.a {
w.write_u32(value)?;
}
w.write_u32(state.other_sp)?;
w.write_u32(state.pc)?;
w.write_u16(state.sr)?;
w.write_u16(state.prefetch[0])?;
w.write_u16(state.prefetch[1])?;
w.write_u64(state.cycles)?;
w.write_bool(state.halted)?;
w.write_bool(state.stopped)?;
w.write_bool(state.reset_pending)?;
w.write_u64(state.faults)?;
w.write_u32(state.last_fault)?;
let (ipl, vector, level_seven, resets) = self.lines.snapshot();
w.write_u8(ipl)?;
w.write_u16(vector)?;
w.write_bool(level_seven)?;
w.write_u32(resets)?;
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut state = State::new();
for slot in &mut state.d {
*slot = r.read_u32()?;
}
for slot in &mut state.a {
*slot = r.read_u32()?;
}
state.other_sp = r.read_u32()?;
state.pc = r.read_u32()?;
state.sr = r.read_u16()?;
if state.sr & !flags::IMPLEMENTED != 0 {
return Err(Error::State(alloc::format!(
"status register 0x{:04x} sets bits a 68000 does not implement",
state.sr
)));
}
state.prefetch[0] = r.read_u16()?;
state.prefetch[1] = r.read_u16()?;
state.cycles = r.read_u64()?;
state.halted = r.read_bool()?;
state.stopped = r.read_bool()?;
state.reset_pending = r.read_bool()?;
state.faults = r.read_u64()?;
state.last_fault = r.read_u32()?;
let ipl = r.read_u8()?;
if ipl > 7 {
return Err(Error::State(alloc::format!(
"interrupt level {ipl} does not fit on three pins"
)));
}
let vector = r.read_u16()?;
if vector != NO_VECTOR && vector > 0xff {
return Err(Error::State(alloc::format!(
"interrupt vector 0x{vector:04x} is not a vector number"
)));
}
let level_seven = r.read_bool()?;
let resets = r.read_u32()?;
self.session.lock().state = state;
self.lines.restore((ipl, vector, level_seven, resets));
Ok(())
}
}
impl Initiator for M68k {
fn requester(&self) -> RequesterId {
self.cfg.requester
}
}
#[derive(Debug)]
pub struct InterruptPins {
cpu: Arc<M68k>,
inputs: [FanIn; 3],
resolve: Resolve,
}
impl InterruptPins {
#[must_use]
pub fn new(cpu: Arc<M68k>, sources: [&[WireId]; 3]) -> InterruptPins {
InterruptPins {
cpu,
inputs: [
FanIn::new(sources[0]),
FanIn::new(sources[1]),
FanIn::new(sources[2]),
],
resolve: Resolve::Or,
}
}
#[must_use]
pub fn with_resolve(mut self, resolve: Resolve) -> Self {
self.resolve = resolve;
self
}
#[must_use]
pub fn inputs(&self, line: usize) -> &FanIn {
&self.inputs[line.min(2)]
}
}
impl WireSink for InterruptPins {
fn set_level(&self, src: WireId, line: u32, level: Level) {
let index = (line as usize).min(2);
self.inputs[index].set(src, level);
let mut encoded = 0u8;
for (bit, input) in self.inputs.iter().enumerate() {
if input.resolve(self.resolve).is_high() {
encoded |= 1 << bit;
}
}
self.cpu.set_ipl(encoded);
}
}
#[must_use]
pub fn describe_isa() -> String {
use core::fmt::Write as _;
let mut out = String::new();
for pattern in isa::TABLE {
let insn = pattern.insn;
let mark = if insn.privileged { '!' } else { ' ' };
let _ = writeln!(
out,
"{:04x}/{:04x} {mark}{:<8} {}",
pattern.mask,
pattern.value,
insn.op.mnemonic(),
insn.op.summary()
);
}
out
}