pub mod cp0;
pub mod disasm;
pub mod elf;
mod exec;
pub mod isa;
#[cfg(test)]
mod tests;
#[cfg(all(test, feature = "std"))]
mod conformance;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::device::{
Device, DeviceClass, Initiator, PropertySpec, RealizeCtx, ResetKind, SinkPin,
};
use crate::core::error::{Error, Result};
use crate::core::exec::{Exit, ExitMask, ExitingCore, Run};
use crate::core::props::{Props, ValueKind};
use crate::core::registry::Registry;
use crate::core::sched::{Budget, Consumed};
use crate::core::space::{AddressSpace, MemAttrs, RequesterId};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{self, AtomicU32, LockRank, Ordering};
use crate::core::value::Width;
use crate::core::wire::{FanIn, Level, Resolve, WireId, WireSink};
use cp0::{Cp0, Lines, TLB_ENTRIES, Tlb, TlbEntry};
use exec::{Exec, State};
use isa::Endian;
pub use isa::REG_NAMES;
pub const PAGE_SIZE: u32 = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Arch {
pub part: &'static str,
pub tlb: bool,
pub load_interlock: bool,
pub cop1: bool,
pub cop2: bool,
pub cop3: bool,
pub dcache_bytes: u32,
pub icache_bytes: u32,
}
impl Arch {
pub const R3000A: Arch = Arch {
part: "r3000a",
tlb: true,
load_interlock: false,
cop1: false,
cop2: false,
cop3: false,
dcache_bytes: 4096,
icache_bytes: 4096,
};
pub const IDT_R3051: Arch = Arch {
part: "r3051",
dcache_bytes: 2048,
icache_bytes: 4096,
..Arch::R3000A
};
pub const LR33300: Arch = Arch {
part: "lr33300",
tlb: false,
dcache_bytes: 1024,
icache_bytes: 4096,
..Arch::R3000A
};
pub const ALL: &'static [Arch] = &[Arch::R3000A, Arch::IDT_R3051, Arch::LR33300];
#[must_use]
pub fn by_name(name: &str) -> Option<Arch> {
Arch::ALL.iter().copied().find(|a| a.part == name)
}
#[must_use]
pub const fn coprocessor(self, n: u32) -> bool {
match n {
0 => true,
1 => self.cop1,
2 => self.cop2,
3 => self.cop3,
_ => false,
}
}
#[must_use]
pub const fn coprocessor_mask(self) -> u32 {
let mut mask = !(0xf << cp0::status::CU_SHIFT);
mask |= 1 << cp0::status::CU_SHIFT;
if self.cop1 {
mask |= 1 << (cp0::status::CU_SHIFT + 1);
}
if self.cop2 {
mask |= 1 << (cp0::status::CU_SHIFT + 2);
}
if self.cop3 {
mask |= 1 << (cp0::status::CU_SHIFT + 3);
}
mask
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub arch: Arch,
pub endian: Endian,
pub reset_vector: u32,
pub prid: u32,
pub requester: RequesterId,
}
impl Config {
#[must_use]
pub const fn new(arch: Arch) -> Config {
Config {
arch,
endian: Endian::Little,
reset_vector: cp0::RESET_VECTOR,
prid: 0x0000_0230,
requester: RequesterId::ANONYMOUS,
}
}
#[must_use]
pub const fn with_endian(mut self, endian: Endian) -> Self {
self.endian = endian;
self
}
#[must_use]
pub const fn with_reset_vector(mut self, pc: u32) -> Self {
self.reset_vector = pc;
self
}
#[must_use]
pub const fn with_prid(mut self, prid: u32) -> Self {
self.prid = prid;
self
}
#[must_use]
pub const fn with_requester(mut self, id: RequesterId) -> Self {
self.requester = id;
self
}
fn validate(&self) -> Result<()> {
for (what, size) in [
("dcache", self.arch.dcache_bytes),
("icache", self.arch.icache_bytes),
] {
if size != 0 && !size.is_power_of_two() {
return Err(Error::Property(alloc::format!(
"`{what}` is {size} bytes, which is not a power of two; the \
cache data array is indexed by masking an address"
)));
}
}
Ok(())
}
}
impl Default for Config {
fn default() -> Self {
Config::new(Arch::R3000A)
}
}
#[derive(Debug)]
struct Session {
state: State,
space: Option<Arc<AddressSpace>>,
}
#[derive(Debug)]
pub struct Cpu {
cfg: Config,
lines: Arc<Lines>,
session: sync::Mutex<Session>,
exits: AtomicU32,
requester: AtomicU32,
pins: sync::Mutex<Pins>,
}
#[derive(Debug, Default)]
struct Pins {
interrupts: Vec<(u32, Arc<InterruptPin>)>,
reset: Option<Arc<ResetPin>>,
}
impl Cpu {
#[must_use]
pub fn new(cfg: Config) -> Cpu {
Cpu::try_new(cfg).expect("a preset configuration is always valid")
}
pub fn try_new(cfg: Config) -> Result<Cpu> {
cfg.validate()?;
Ok(Cpu {
lines: Arc::new(Lines::default()),
session: sync::Mutex::with_rank(
LockRank::BUS,
Session {
state: State::new(&cfg),
space: None,
},
),
exits: AtomicU32::new(ExitMask::NONE.bits()),
requester: AtomicU32::new(cfg.requester.0),
pins: sync::Mutex::new(Pins::default()),
cfg,
})
}
pub fn from_props(props: &Props) -> Result<Cpu> {
let mut r = props.reader();
let names: Vec<&'static str> = Arch::ALL.iter().map(|a| a.part).collect();
let part = r.or_enum("arch", Arch::R3000A.part, &names)?;
let arch = Arch::by_name(part).unwrap_or(Arch::R3000A);
let endian = match r.or_enum("endian", "little", &["little", "big"])? {
"big" => Endian::Big,
_ => Endian::Little,
};
let reset = r.or("reset", u64::from(cp0::RESET_VECTOR))?;
let prid = r.or_range("prid", u64::from(arch_prid(arch)), 0..=0xffff_ffff)?;
let _ = r.or_enum("engine", "interp", &["interp"])?;
r.finish()?;
if reset > 0xffff_ffff {
return Err(Error::Property(alloc::format!(
"`reset` is 0x{reset:x}, which does not fit a 32-bit program counter"
)));
}
Cpu::try_new(Config {
arch,
endian,
reset_vector: reset as u32,
prid: prid as u32,
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);
}
#[must_use]
pub fn space(&self) -> Option<Arc<AddressSpace>> {
self.session.lock().space.clone()
}
pub fn set_requester(&self, id: RequesterId) {
self.requester.store(id.0, Ordering::Relaxed);
}
fn effective_config(&self) -> Config {
Config {
requester: RequesterId(self.requester.load(Ordering::Relaxed)),
..self.cfg
}
}
#[must_use]
pub fn reg(&self, index: u32) -> u32 {
self.session.lock().state.regs[(index & 31) as usize]
}
pub fn set_reg(&self, index: u32, value: u32) {
if index & 31 != 0 {
self.session.lock().state.regs[(index & 31) as usize] = value;
}
}
#[must_use]
pub fn pending_load(&self) -> Option<(u32, u32)> {
self.session
.lock()
.state
.pending_load
.map(|l| (l.reg, l.value))
}
pub fn set_pending_load(&self, load: Option<(u32, u32)>) {
self.session.lock().state.pending_load = load.map(|(reg, value)| exec::PendingLoad {
reg: reg & 31,
value,
});
}
#[must_use]
pub fn hi(&self) -> u32 {
self.session.lock().state.hi
}
#[must_use]
pub fn lo(&self) -> u32 {
self.session.lock().state.lo
}
pub fn set_hi_lo(&self, hi: u32, lo: u32) {
let mut s = self.session.lock();
s.state.hi = hi;
s.state.lo = lo;
}
#[must_use]
pub fn pc(&self) -> u32 {
self.session.lock().state.pc
}
#[must_use]
pub fn next_pc(&self) -> u32 {
self.session.lock().state.next_pc
}
#[must_use]
pub fn in_delay_slot(&self) -> bool {
self.session.lock().state.in_delay
}
pub fn set_pc(&self, pc: u32) {
self.set_control(pc, pc.wrapping_add(4), false);
self.session.lock().state.pending_load = None;
}
pub fn set_control(&self, pc: u32, next_pc: u32, in_delay: bool) {
let mut s = self.session.lock();
s.state.pc = pc;
s.state.next_pc = next_pc;
s.state.in_delay = in_delay;
}
#[must_use]
pub fn cp0(&self) -> Cp0 {
self.session.lock().state.cp0.clone()
}
pub fn set_cp0(&self, cp0: Cp0) {
self.session.lock().state.cp0 = cp0;
}
#[must_use]
pub fn tlb(&self) -> Tlb {
self.session.lock().state.tlb.clone()
}
pub fn set_tlb(&self, tlb: Tlb) {
self.session.lock().state.tlb = tlb;
}
#[must_use]
pub fn cycles(&self) -> u64 {
self.session.lock().state.cycles
}
#[must_use]
pub fn bus_faults(&self) -> u64 {
self.session.lock().state.faults
}
pub fn set_interrupt(&self, pin: u32, asserted: bool) {
self.lines.set_hw(pin, asserted);
}
#[must_use]
pub fn interrupts(&self) -> u32 {
self.lines.hw()
}
pub fn request_reset(&self) {
self.lines.request_reset();
}
pub fn step(&self) -> u64 {
self.step_to_exit().0
}
pub fn step_to_exit(&self) -> (u64, Option<Exit>) {
let cfg = self.effective_config();
let exits = self.exit_mask();
let mut session = self.session.lock();
if self.lines.take_reset_request() {
session.state = State::new(&cfg);
}
let Session { state, space } = &mut *session;
let Some(space) = space.clone() else {
return (0, None);
};
let mut exec = Exec::new(state, &space, &cfg, &self.lines, exits);
let used = exec.step();
(used, exec.take_exit())
}
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
}
pub fn run_budget(&self, ticks: u64) -> u64 {
let owed = self.session.lock().state.debt;
if owed >= ticks {
self.session.lock().state.debt = owed - ticks;
return ticks;
}
let allowance = ticks - owed;
let mut used = 0u64;
while used < allowance {
let n = self.step();
if n == 0 {
break;
}
used += n;
}
if used >= allowance {
self.session.lock().state.debt = used - allowance;
ticks
} else {
self.session.lock().state.debt = 0;
owed + used
}
}
#[must_use]
pub fn cycle_debt(&self) -> u64 {
self.session.lock().state.debt
}
pub fn run_to_exit_ticks(&self, ticks: u64) -> Run {
let owed = self.session.lock().state.debt;
if owed >= ticks {
self.session.lock().state.debt = owed - ticks;
return Run::completed(Consumed::new(ticks));
}
let allowance = ticks - owed;
let mut used = 0u64;
while used < allowance {
let (n, exit) = self.step_to_exit();
if n == 0 {
break;
}
used += n;
if let Some(exit) = exit {
let total = owed + used;
self.session.lock().state.debt = total.saturating_sub(ticks);
return Run::exited(Consumed::new(total.min(ticks)), exit);
}
}
if used >= allowance {
self.session.lock().state.debt = used - allowance;
Run::completed(Consumed::new(ticks))
} else {
self.session.lock().state.debt = 0;
Run::completed(Consumed::new(owed + 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| {
let vaddr = addr as u32;
let segment = cp0::Segment::of(vaddr);
let phys = if segment.mapped() {
vaddr
} else {
cp0::Segment::unmapped_phys(vaddr)
};
space
.read(u64::from(phys), Width::U32, MemAttrs::DEBUG)
.ok()
.map(|v| v as u32)
})
}
}
const fn arch_prid(arch: Arch) -> u32 {
if arch.tlb { 0x0000_0230 } else { 0x0000_0002 }
}
pub static CLASS: DeviceClass = DeviceClass {
name: "cpu.mips",
version: 1,
summary: "MIPS I / R3000A interpreter with CP0, the 64-entry TLB and delay slots",
properties: &[
PropertySpec {
name: "arch",
kind: ValueKind::Str,
required: false,
summary: "which part: `r3000a`, `r3051` or `lr33300` (which has no TLB)",
},
PropertySpec {
name: "endian",
kind: ValueKind::Str,
required: false,
summary: "the byte-order pin: `little` (default) or `big`",
},
PropertySpec {
name: "reset",
kind: ValueKind::Uint,
required: false,
summary: "where the program counter starts (default 0xbfc00000)",
},
PropertySpec {
name: "prid",
kind: ValueKind::Uint,
required: false,
summary: "the value the `PRId` register reports",
},
PropertySpec {
name: "engine",
kind: ValueKind::Str,
required: false,
summary: "which execution engine; only `interp` exists until phase 5",
},
],
construct: |props| Ok(Box::new(Cpu::from_props(props)?)),
};
pub fn register(reg: &mut Registry) -> Result<()> {
reg.add(&CLASS)
}
fn pin_number(port: &str) -> Option<u32> {
let n = port.strip_prefix("int")?.parse::<u32>().ok()?;
(n < 6).then_some(n)
}
impl Device for Cpu {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn sink(&self, port: &str, sources: &[WireId]) -> Option<SinkPin> {
let mut pins = self.pins.lock();
if port == "reset" {
let pin = Arc::new(ResetPin::new(Arc::clone(&self.lines), sources));
pins.reset = Some(Arc::clone(&pin));
return Some(SinkPin { sink: pin, line: 0 });
}
let n = pin_number(port)?;
let pin = Arc::new(InterruptPin::new(Arc::clone(&self.lines), n, sources));
pins.interrupts.push((n, Arc::clone(&pin)));
Some(SinkPin { sink: pin, line: n })
}
fn is_runnable(&self) -> bool {
true
}
fn run(&self, budget: Budget) -> Consumed {
Consumed::new(self.run_budget(budget.ticks))
}
fn reset(&self, kind: ResetKind) {
let cfg = self.effective_config();
let mut session = self.session.lock();
session.state = State::new(&cfg);
drop(session);
if kind == ResetKind::Cold {
self.lines.set_all_hw(0);
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let session = self.session.lock();
let s = &session.state;
for r in s.regs {
w.write_u32(r)?;
}
for v in [s.hi, s.lo, s.pc, s.next_pc] {
w.write_u32(v)?;
}
w.write_bool(s.in_delay)?;
match s.pending_load {
None => w.write_bool(false)?,
Some(load) => {
w.write_bool(true)?;
w.write_u32(load.reg)?;
w.write_u32(load.value)?;
}
}
let c = &s.cp0;
for v in [
c.index,
c.random,
c.entry_lo,
c.context,
c.bad_vaddr,
c.entry_hi,
c.status,
c.cause,
c.epc,
c.prid,
] {
w.write_u32(v)?;
}
for v in c.debug {
w.write_u32(v)?;
}
for entry in s.tlb.entries() {
w.write_u32(entry.hi)?;
w.write_u32(entry.lo)?;
}
w.write_bytes(&s.dcache)?;
w.write_bytes(&s.icache)?;
w.write_u64(s.cycles)?;
w.write_u64(s.debt)?;
w.write_u64(s.faults)?;
w.write_u32(self.lines.hw())?;
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let cfg = self.effective_config();
let mut s = State::new(&cfg);
for slot in &mut s.regs {
*slot = r.read_u32()?;
}
s.regs[0] = 0;
s.hi = r.read_u32()?;
s.lo = r.read_u32()?;
s.pc = r.read_u32()?;
s.next_pc = r.read_u32()?;
s.in_delay = r.read_bool()?;
s.pending_load = if r.read_bool()? {
let reg = r.read_u32()?;
let value = r.read_u32()?;
Some(exec::PendingLoad { reg, value })
} else {
None
};
let c = &mut s.cp0;
for slot in [
&mut c.index,
&mut c.random,
&mut c.entry_lo,
&mut c.context,
&mut c.bad_vaddr,
&mut c.entry_hi,
&mut c.status,
&mut c.cause,
&mut c.epc,
&mut c.prid,
] {
*slot = r.read_u32()?;
}
for slot in &mut c.debug {
*slot = r.read_u32()?;
}
for i in 0..TLB_ENTRIES {
let hi = r.read_u32()?;
let lo = r.read_u32()?;
s.tlb.set_entry(i as u32, TlbEntry { hi, lo });
}
s.dcache = r.read_bytes()?.to_vec();
s.icache = r.read_bytes()?.to_vec();
s.cycles = r.read_u64()?;
s.debt = r.read_u64()?;
s.faults = r.read_u64()?;
let pins = r.read_u32()?;
self.session.lock().state = s;
self.lines.set_all_hw(pins);
Ok(())
}
}
impl ExitingCore for Cpu {
fn exit_mask(&self) -> ExitMask {
ExitMask::from_bits(self.exits.load(Ordering::Relaxed))
}
fn set_exit_mask(&self, mask: ExitMask) {
self.exits.store(mask.bits(), Ordering::Relaxed);
}
fn run_to_exit(&self, budget: Budget) -> Run {
self.run_to_exit_ticks(budget.ticks)
}
fn pc(&self) -> u64 {
u64::from(Cpu::pc(self))
}
fn set_pc(&self, pc: u64) {
Cpu::set_pc(self, pc as u32);
}
fn sp(&self) -> u64 {
u64::from(self.reg(29))
}
fn set_sp(&self, sp: u64) {
self.set_reg(29, sp as u32);
}
}
impl Initiator for Cpu {
fn requester(&self) -> RequesterId {
RequesterId(self.requester.load(Ordering::Relaxed))
}
}
impl crate::machine::Instance for Cpu {
fn bind(&self, ctx: &crate::machine::BindCtx<'_>) -> Result<()> {
let space = ctx.space().ok_or_else(|| Error::Config {
at: ctx.path().to_string(),
message: "a MIPS core needs an address space to fetch from (`space = mem`)".to_string(),
})?;
self.attach_space(Arc::clone(space));
self.set_requester(ctx.requester());
Ok(())
}
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS.name, |props| Ok(Arc::new(Cpu::from_props(props)?)))
}
#[must_use]
pub fn schema() -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
let parts: &'static [&'static str] = &["r3000a", "r3051", "lr33300"];
ClassSchema::new(CLASS.name)
.prop(PropSchema::new("arch", ValueKind::Str).values(parts))
.prop(PropSchema::new("endian", ValueKind::Str).values(&["little", "big"]))
.prop(PropSchema::new("reset", ValueKind::Uint))
.prop(PropSchema::new("prid", ValueKind::Uint))
.prop(PropSchema::new("engine", ValueKind::Str).values(&["interp"]))
.port("int0", PortDir::In)
.port("int1", PortDir::In)
.port("int2", PortDir::In)
.port("int3", PortDir::In)
.port("int4", PortDir::In)
.port("int5", PortDir::In)
.port("reset", PortDir::In)
}
#[derive(Debug)]
pub struct InterruptPin {
lines: Arc<Lines>,
pin: u32,
inputs: FanIn,
resolve: Resolve,
}
impl InterruptPin {
#[must_use]
pub fn new(lines: Arc<Lines>, pin: u32, sources: &[WireId]) -> InterruptPin {
InterruptPin {
lines,
pin,
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 pin(&self) -> u32 {
self.pin
}
#[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();
self.lines.set_hw(self.pin, asserted);
}
}
#[derive(Debug)]
pub struct ResetPin {
lines: Arc<Lines>,
inputs: FanIn,
resolve: Resolve,
}
impl ResetPin {
#[must_use]
pub fn new(lines: Arc<Lines>, sources: &[WireId]) -> ResetPin {
ResetPin {
lines,
inputs: FanIn::new(sources),
resolve: Resolve::Or,
}
}
#[must_use]
pub fn inputs(&self) -> &FanIn {
&self.inputs
}
}
impl WireSink for ResetPin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
if self.inputs.resolve(self.resolve).is_high() {
self.lines.request_reset();
}
}
}
#[must_use]
pub fn describe_isa() -> String {
use core::fmt::Write as _;
let mut out = String::new();
for insn in isa::TABLE {
let _ = writeln!(
out,
"{:08x}/{:08x} {:<8} {:<6} {}",
insn.bits,
insn.mask,
insn.op.mnemonic(),
insn.req.name(),
insn.op.summary()
);
}
out
}