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, AtomicU32, 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 CF: u16 = 0x0001;
pub const PF: u16 = 0x0004;
pub const AF: u16 = 0x0010;
pub const ZF: u16 = 0x0040;
pub const SF: u16 = 0x0080;
pub const TF: u16 = 0x0100;
pub const IF: u16 = 0x0200;
pub const DF: u16 = 0x0400;
pub const OF: u16 = 0x0800;
pub const DEFINED: u16 = CF | PF | AF | ZF | SF | TF | IF | DF | OF;
pub const RESERVED_SET: u16 = 0xf002;
pub const LOW_BYTE: u16 = CF | PF | AF | ZF | SF;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Model {
I8086,
I8088,
}
impl Model {
#[must_use]
pub const fn queue_bytes(self) -> u8 {
match self {
Model::I8086 => 6,
Model::I8088 => 4,
}
}
#[must_use]
pub const fn bus_bytes(self) -> u8 {
match self {
Model::I8086 => 2,
Model::I8088 => 1,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Model::I8086 => "8086",
Model::I8088 => "8088",
}
}
}
impl fmt::Display for Model {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub model: Model,
pub requester: RequesterId,
}
impl Config {
pub const I8088: Config = Config {
model: Model::I8088,
requester: RequesterId::ANONYMOUS,
};
pub const I8086: Config = Config {
model: Model::I8086,
..Config::I8088
};
#[must_use]
pub const fn with_requester(mut self, id: RequesterId) -> Self {
self.requester = id;
self
}
}
impl Default for Config {
fn default() -> Self {
Config::I8088
}
}
#[inline]
#[must_use]
pub const fn linear(segment: u16, offset: u16) -> u32 {
(((segment as u32) << 4).wrapping_add(offset as u32)) & 0xf_ffff
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Regs {
pub ax: u16,
pub cx: u16,
pub dx: u16,
pub bx: u16,
pub sp: u16,
pub bp: u16,
pub si: u16,
pub di: u16,
pub es: u16,
pub cs: u16,
pub ss: u16,
pub ds: u16,
pub ip: u16,
pub flags: u16,
}
const WORD_ORDER: [Reg; 8] = [
Reg::Ax,
Reg::Cx,
Reg::Dx,
Reg::Bx,
Reg::Sp,
Reg::Bp,
Reg::Si,
Reg::Di,
];
impl Regs {
#[must_use]
pub const fn new() -> Regs {
Regs {
ax: 0,
cx: 0,
dx: 0,
bx: 0,
sp: 0,
bp: 0,
si: 0,
di: 0,
es: 0,
cs: 0xffff,
ss: 0,
ds: 0,
ip: 0,
flags: flags::RESERVED_SET,
}
}
#[inline]
#[must_use]
pub const fn normalise_flags(value: u16) -> u16 {
(value & flags::DEFINED) | flags::RESERVED_SET
}
#[inline]
#[must_use]
pub const fn flag(&self, mask: u16) -> bool {
self.flags & mask != 0
}
#[inline]
#[must_use]
pub const fn word(&self, index: u8) -> u16 {
match index & 7 {
0 => self.ax,
1 => self.cx,
2 => self.dx,
3 => self.bx,
4 => self.sp,
5 => self.bp,
6 => self.si,
_ => self.di,
}
}
#[inline]
pub const fn set_word(&mut self, index: u8, value: u16) {
match index & 7 {
0 => self.ax = value,
1 => self.cx = value,
2 => self.dx = value,
3 => self.bx = value,
4 => self.sp = value,
5 => self.bp = value,
6 => self.si = value,
_ => self.di = value,
}
}
#[inline]
#[must_use]
pub const fn byte(&self, index: u8) -> u8 {
let word = self.word(index & 3);
if index & 4 == 0 {
word as u8
} else {
(word >> 8) as u8
}
}
#[inline]
pub const fn set_byte(&mut self, index: u8, value: u8) {
let word = self.word(index & 3);
let merged = if index & 4 == 0 {
(word & 0xff00) | value as u16
} else {
(word & 0x00ff) | ((value as u16) << 8)
};
self.set_word(index & 3, merged);
}
#[inline]
#[must_use]
pub const fn segment(&self, index: u8) -> u16 {
match index & 3 {
0 => self.es,
1 => self.cs,
2 => self.ss,
_ => self.ds,
}
}
#[inline]
pub const fn set_segment(&mut self, index: u8, value: u16) {
match index & 3 {
0 => self.es = value,
1 => self.cs = value,
2 => self.ss = value,
_ => self.ds = value,
}
}
}
impl Default for Regs {
fn default() -> Self {
Regs::new()
}
}
impl fmt::Display for Regs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"AX:{:04x} BX:{:04x} CX:{:04x} DX:{:04x} SP:{:04x} BP:{:04x} SI:{:04x} DI:{:04x} \
ES:{:04x} CS:{:04x} SS:{:04x} DS:{:04x} IP:{:04x} F:{:04x}",
self.ax,
self.bx,
self.cx,
self.dx,
self.sp,
self.bp,
self.si,
self.di,
self.es,
self.cs,
self.ss,
self.ds,
self.ip,
self.flags
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Reg {
Ax,
Cx,
Dx,
Bx,
Sp,
Bp,
Si,
Di,
Es,
Cs,
Ss,
Ds,
Ip,
Flags,
}
impl Reg {
pub const ALL: &'static [Reg] = &[
Reg::Ax,
Reg::Cx,
Reg::Dx,
Reg::Bx,
Reg::Sp,
Reg::Bp,
Reg::Si,
Reg::Di,
Reg::Es,
Reg::Cs,
Reg::Ss,
Reg::Ds,
Reg::Ip,
Reg::Flags,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Reg::Ax => "ax",
Reg::Cx => "cx",
Reg::Dx => "dx",
Reg::Bx => "bx",
Reg::Sp => "sp",
Reg::Bp => "bp",
Reg::Si => "si",
Reg::Di => "di",
Reg::Es => "es",
Reg::Cs => "cs",
Reg::Ss => "ss",
Reg::Ds => "ds",
Reg::Ip => "ip",
Reg::Flags => "flags",
}
}
#[must_use]
pub const fn width(self) -> Width {
Width::U16
}
#[must_use]
pub const fn get(self, regs: &Regs) -> u16 {
match self {
Reg::Ax => regs.ax,
Reg::Cx => regs.cx,
Reg::Dx => regs.dx,
Reg::Bx => regs.bx,
Reg::Sp => regs.sp,
Reg::Bp => regs.bp,
Reg::Si => regs.si,
Reg::Di => regs.di,
Reg::Es => regs.es,
Reg::Cs => regs.cs,
Reg::Ss => regs.ss,
Reg::Ds => regs.ds,
Reg::Ip => regs.ip,
Reg::Flags => regs.flags,
}
}
pub const fn set(self, regs: &mut Regs, value: u16) {
match self {
Reg::Ax => regs.ax = value,
Reg::Cx => regs.cx = value,
Reg::Dx => regs.dx = value,
Reg::Bx => regs.bx = value,
Reg::Sp => regs.sp = value,
Reg::Bp => regs.bp = value,
Reg::Si => regs.si = value,
Reg::Di => regs.di = value,
Reg::Es => regs.es = value,
Reg::Cs => regs.cs = value,
Reg::Ss => regs.ss = value,
Reg::Ds => regs.ds = value,
Reg::Ip => regs.ip = value,
Reg::Flags => regs.flags = Regs::normalise_flags(value),
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Reg> {
Reg::ALL.iter().copied().find(|r| r.name() == name)
}
#[must_use]
pub const fn from_word_index(index: u8) -> Reg {
WORD_ORDER[(index & 7) as usize]
}
}
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)]
pub enum Interrupt {
Intr,
Nmi,
}
#[derive(Debug, Default)]
pub(crate) struct Lines {
intr: AtomicBool,
intr_vector: AtomicU32,
nmi_level: AtomicBool,
nmi_latch: AtomicBool,
}
impl Lines {
fn set_intr(&self, asserted: bool) {
self.intr.store(asserted, Ordering::Release);
}
fn intr_asserted(&self) -> bool {
self.intr.load(Ordering::Acquire)
}
fn set_intr_vector(&self, vector: u8) {
self.intr_vector.store(u32::from(vector), Ordering::Release);
}
pub(crate) fn intr_vector(&self) -> u8 {
self.intr_vector.load(Ordering::Acquire) as u8
}
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);
}
}
pub(crate) fn nmi_pending(&self) -> bool {
self.nmi_latch.load(Ordering::Acquire)
}
pub(crate) fn take_nmi_pending(&self) -> bool {
self.nmi_latch.swap(false, Ordering::AcqRel)
}
pub(crate) fn intr_pending(&self) -> bool {
self.intr_asserted()
}
fn clear_nmi_latch(&self) {
self.nmi_latch.store(false, Ordering::Release);
}
fn snapshot(&self) -> (bool, bool, bool, u8) {
(
self.intr_asserted(),
self.nmi_level.load(Ordering::Acquire),
self.nmi_pending(),
self.intr_vector(),
)
}
fn restore(&self, (intr, level, latch, vector): (bool, bool, bool, u8)) {
self.intr.store(intr, Ordering::Release);
self.nmi_level.store(level, Ordering::Release);
self.nmi_latch.store(latch, Ordering::Release);
self.intr_vector.store(u32::from(vector), Ordering::Release);
}
}
#[derive(Debug)]
struct Session {
state: State,
memory: Option<Arc<AddressSpace>>,
io: Option<Arc<AddressSpace>>,
}
#[derive(Debug)]
pub struct I8086 {
cfg: Config,
lines: Lines,
session: sync::Mutex<Session>,
}
impl I8086 {
#[must_use]
pub fn new(cfg: Config) -> I8086 {
I8086 {
cfg,
lines: Lines::default(),
session: sync::Mutex::with_rank(
LockRank::BUS,
Session {
state: State::new(cfg.model),
memory: None,
io: None,
},
),
}
}
pub fn from_props(props: &Props) -> Result<I8086> {
let mut r = props.reader();
let model = r.or_enum("model", "8088", &["8086", "8088"])?;
let model = if model == "8086" {
Model::I8086
} else {
Model::I8088
};
r.finish()?;
Ok(I8086::new(Config {
model,
requester: RequesterId::ANONYMOUS,
}))
}
#[must_use]
pub fn config(&self) -> Config {
self.cfg
}
pub fn attach_space(&self, space: Arc<AddressSpace>) {
self.session.lock().memory = 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().memory.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) {
let mut session = self.session.lock();
session.state.regs = regs;
session.state.regs.flags = Regs::normalise_flags(regs.flags);
session.state.queue.flush();
}
#[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) {
let mut session = self.session.lock();
reg.set(&mut session.state.regs, value);
if matches!(reg, Reg::Cs | Reg::Ip) {
session.state.queue.flush();
}
}
#[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 reset_pending(&self) -> bool {
self.session.lock().state.reset_pending
}
#[must_use]
pub fn prefetch_queue(&self) -> Vec<u8> {
self.session.lock().state.queue.contents()
}
pub fn set_prefetch_queue(&self, bytes: &[u8]) -> Result<()> {
let mut session = self.session.lock();
session.state.queue.install(bytes).map_err(|()| {
Error::Property(alloc::format!(
"the {} prefetch queue holds {} bytes, not {}",
self.cfg.model,
self.cfg.model.queue_bytes(),
bytes.len()
))
})
}
#[must_use]
pub fn bus_faults(&self) -> (u64, u32) {
let s = self.session.lock();
(s.state.faults, s.state.last_fault)
}
pub fn set_intr(&self, asserted: bool) {
self.lines.set_intr(asserted);
}
#[must_use]
pub fn intr_asserted(&self) -> bool {
self.lines.intr_asserted()
}
pub fn set_intr_vector(&self, vector: u8) {
self.lines.set_intr_vector(vector);
}
#[must_use]
pub fn intr_vector(&self) -> u8 {
self.lines.intr_vector()
}
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()
}
#[must_use]
pub fn interrupt_shadow(&self) -> bool {
self.session.lock().state.int_shadow
}
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, memory, io } = &mut *session;
let Some(memory) = memory.clone() else {
return 0;
};
let io = io.clone();
Exec::new(state, &memory, io.as_deref(), &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, cs: u16, ip: u16, count: usize) -> Vec<disasm::Disassembled> {
let Some(space) = self.space() else {
return Vec::new();
};
disasm::disassemble_run(cs, ip, count, |addr| {
space
.read(u64::from(addr), Width::U8, MemAttrs::DEBUG)
.ok()
.map(|v| v as u8)
})
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: "cpu.i8086",
version: 1,
summary: "Intel 8086 / 8088 16-bit CPU core, real mode, hardware-checked interpreter",
properties: &[PropertySpec {
name: "model",
kind: ValueKind::Str,
required: false,
summary: "\"8086\" (16-bit bus, 6-byte queue) or \"8088\" (8-bit bus, 4-byte queue)",
}],
construct: |props| Ok(Box::new(I8086::from_props(props)?)),
};
pub fn register(reg: &mut Registry) -> Result<()> {
reg.add(&CLASS)
}
impl Device for I8086 {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, ctx: &mut RealizeCtx<'_>) -> Result<()> {
if self.session.lock().memory.is_none() {
return Err(ctx.error("no memory 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(self.cfg.model);
} else {
session.state.reset_pending = true;
session.state.halted = false;
session.state.int_shadow = false;
session.state.queue.flush();
}
drop(session);
if kind == ResetKind::Cold {
self.lines.restore((false, false, false, 0));
} else {
self.lines.clear_nmi_latch();
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.session.lock().state;
for reg in Reg::ALL {
w.write_u16(reg.get(&state.regs))?;
}
w.write_u64(state.cycles)?;
w.write_bool(state.halted)?;
w.write_bool(state.reset_pending)?;
w.write_bool(state.int_shadow)?;
w.write_u8(state.open_bus)?;
w.write_u64(state.faults)?;
w.write_u32(state.last_fault)?;
let queue = state.queue.contents();
w.write_u8(queue.len() as u8)?;
for byte in queue {
w.write_u8(byte)?;
}
let (intr, nmi_level, nmi_latch, vector) = self.lines.snapshot();
w.write_bool(intr)?;
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(self.cfg.model);
for reg in Reg::ALL {
let value = r.read_u16()?;
reg.set(&mut state.regs, value);
}
state.cycles = r.read_u64()?;
state.halted = r.read_bool()?;
state.reset_pending = r.read_bool()?;
state.int_shadow = r.read_bool()?;
state.open_bus = r.read_u8()?;
state.faults = r.read_u64()?;
state.last_fault = r.read_u32()?;
let len = r.read_u8()?;
let mut queue = Vec::with_capacity(usize::from(len));
for _ in 0..len {
queue.push(r.read_u8()?);
}
state.queue.install(&queue).map_err(|()| {
Error::State(alloc::format!(
"snapshot has a {len}-byte prefetch queue; an {} holds {}",
self.cfg.model,
self.cfg.model.queue_bytes()
))
})?;
let intr = 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((intr, nmi_level, nmi_latch, vector));
Ok(())
}
}
impl Initiator for I8086 {
fn requester(&self) -> RequesterId {
self.cfg.requester
}
}
#[derive(Debug)]
pub struct InterruptPin {
cpu: Arc<I8086>,
which: Interrupt,
inputs: FanIn,
resolve: Resolve,
}
impl InterruptPin {
#[must_use]
pub fn new(cpu: Arc<I8086>, 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::Intr => self.cpu.set_intr(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 = |class: isa::Class| match class {
isa::Class::Documented => ' ',
isa::Class::Alias => '=',
isa::Class::Undocumented => '*',
isa::Class::Undefined => '?',
isa::Class::Prefix => ':',
isa::Class::Escape => '~',
};
if insn.group == isa::Grp::None {
let _ = writeln!(
out,
"{opcode:02x} {}{:<6} {}",
mark(insn.class),
insn.op.mnemonic(),
insn.op.summary()
);
} else {
for reg in 0..8u8 {
let row = isa::resolve(insn, reg);
let _ = writeln!(
out,
"{opcode:02x}/{reg} {}{:<6} {}",
mark(row.class),
row.op.mnemonic(),
row.op.summary()
);
}
}
}
out
}