use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::clock::{ClockForest, DomainId, GlobalTime};
use crate::core::device::{Deferred, Device, DeviceClass, ResetKind};
use crate::core::error::{Error, Result};
use crate::core::sched::{
Budget, Consumed, Event, EventId, EventTarget, HostClock, QuantumReport, Runnable, RunnableId,
Scheduler, SchedulerSnapshot,
};
use crate::core::space::{AddressSpace, RequesterId};
use crate::core::state::{MachineShape, Migrations, Sink, Source, StateReader, StateWriter};
use crate::core::wire::{Level, Wire, WireId};
use crate::machine::realize::Instance;
pub const CLOCK_PATH: &str = "/clock";
pub const CLOCK_CLASS: &str = "machine.clock";
pub const WIRE_PATH: &str = "/wires";
pub const WIRE_CLASS: &str = "machine.wires";
pub const SCHED_PATH: &str = "/sched";
pub const SCHED_CLASS: &str = "machine.sched";
pub const MACHINE_STATE_VERSION: u32 = 1;
#[derive(Debug)]
pub struct SpaceEntry {
name: String,
space: Arc<AddressSpace>,
}
impl SpaceEntry {
pub fn name(&self) -> &str {
&self.name
}
pub fn space(&self) -> &Arc<AddressSpace> {
&self.space
}
}
#[derive(Debug)]
pub struct DeviceEntry {
pub(crate) path: String,
pub(crate) class: &'static DeviceClass,
pub(crate) device: Arc<dyn Device>,
pub(crate) instance: Option<Arc<dyn Instance>>,
pub(crate) domain: Option<DomainId>,
pub(crate) space: Option<usize>,
pub(crate) requester: RequesterId,
pub(crate) runnable: Option<RunnableId>,
}
impl DeviceEntry {
pub fn path(&self) -> &str {
&self.path
}
pub fn class(&self) -> &'static DeviceClass {
self.class
}
pub fn device(&self) -> &Arc<dyn Device> {
&self.device
}
pub fn instance(&self) -> Option<&Arc<dyn Instance>> {
self.instance.as_ref()
}
pub fn domain(&self) -> Option<DomainId> {
self.domain
}
pub fn space_index(&self) -> Option<usize> {
self.space
}
pub fn requester(&self) -> RequesterId {
self.requester
}
pub fn runnable(&self) -> Option<RunnableId> {
self.runnable
}
}
#[derive(Debug)]
pub struct Net {
pub(crate) wire: Arc<Wire>,
pub(crate) sources: Vec<PinRef>,
}
impl Net {
pub fn wire(&self) -> &Arc<Wire> {
&self.wire
}
pub fn sources(&self) -> &[PinRef] {
&self.sources
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PinRef {
pub device: usize,
pub port: String,
pub id: WireId,
}
pub(crate) struct RunAdapter {
inner: Arc<dyn Instance>,
}
impl RunAdapter {
pub(crate) fn new(inner: Arc<dyn Instance>) -> RunAdapter {
RunAdapter { inner }
}
}
impl Runnable for RunAdapter {
fn run(&mut self, budget: Budget) -> Consumed {
self.inner.run(budget)
}
}
#[derive(Debug)]
pub struct Machine {
name: String,
spaces: Vec<SpaceEntry>,
sched: Scheduler,
devices: Vec<DeviceEntry>,
by_path: BTreeMap<String, usize>,
nets: Vec<Net>,
sweep: Vec<PinRef>,
shape: MachineShape,
deferred: Deferred,
}
#[derive(Debug)]
pub(crate) struct MachineParts {
pub(crate) name: String,
pub(crate) spaces: Vec<(String, Arc<AddressSpace>)>,
pub(crate) sched: Scheduler,
pub(crate) devices: Vec<DeviceEntry>,
pub(crate) nets: Vec<Net>,
pub(crate) sweep: Vec<PinRef>,
pub(crate) shape: MachineShape,
pub(crate) deferred: Deferred,
}
impl Machine {
pub(crate) fn assemble(parts: MachineParts) -> Machine {
let by_path = parts
.devices
.iter()
.enumerate()
.map(|(i, d)| (d.path.clone(), i))
.collect();
Machine {
name: parts.name,
spaces: parts
.spaces
.into_iter()
.map(|(name, space)| SpaceEntry { name, space })
.collect(),
sched: parts.sched,
devices: parts.devices,
by_path,
nets: parts.nets,
sweep: parts.sweep,
shape: parts.shape,
deferred: parts.deferred,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn spaces(&self) -> &[SpaceEntry] {
&self.spaces
}
pub fn space(&self, name: &str) -> Option<&Arc<AddressSpace>> {
self.spaces
.iter()
.find(|s| s.name == name)
.map(SpaceEntry::space)
}
pub fn devices(&self) -> &[DeviceEntry] {
&self.devices
}
pub fn device(&self, path: &str) -> Option<&DeviceEntry> {
self.by_path.get(path).and_then(|i| self.devices.get(*i))
}
pub fn device_index(&self, path: &str) -> Option<usize> {
self.by_path.get(path).copied()
}
pub fn nets(&self) -> &[Net] {
&self.nets
}
pub fn scheduler(&self) -> &Scheduler {
&self.sched
}
pub fn scheduler_mut(&mut self) -> &mut Scheduler {
&mut self.sched
}
pub fn clocks(&self) -> &ClockForest {
self.sched.forest()
}
pub fn now(&self) -> GlobalTime {
self.sched.now()
}
pub fn shape(&self) -> &MachineShape {
&self.shape
}
pub fn set_host_clock(&mut self, clock: Box<dyn HostClock>) {
self.sched.set_host_clock(clock);
}
pub fn reset(&mut self, kind: ResetKind) {
for i in 0..self.devices.len() {
let device = Arc::clone(&self.devices[i].device);
device.reset(kind);
self.deferred.drain();
}
self.sweep();
}
pub fn sweep(&mut self) {
for pin in &self.sweep {
if let Some(instance) = self.devices[pin.device].instance.as_ref() {
instance.announce(&pin.port);
}
}
self.deferred.drain();
}
pub fn run_quantum(&mut self) -> Result<QuantumReport> {
let report = self.sched.run_quantum()?;
self.dispatch(&report)?;
Ok(report)
}
pub fn run_until(&mut self, deadline: GlobalTime) -> Result<()> {
while self.sched.now() < deadline {
let before = self.sched.now();
let report = self.sched.run_quantum_until(deadline)?;
self.dispatch(&report)?;
if self.sched.now() <= before {
return Err(Error::Config {
at: self.name.clone(),
message: "virtual time did not advance: the scheduler quantum is zero"
.to_string(),
});
}
}
Ok(())
}
pub fn run_for(&mut self, span: GlobalTime) -> Result<()> {
let deadline = self.sched.now().saturating_add(span);
self.run_until(deadline)
}
pub fn schedule_after_ticks(&mut self, path: &str, ticks: u64, token: u64) -> Result<EventId> {
let index = self.device_index(path).ok_or_else(|| Error::Config {
at: path.to_string(),
message: "no device at this instance path".to_string(),
})?;
let domain = self.devices[index].domain.ok_or_else(|| Error::Config {
at: path.to_string(),
message: "cannot post an event for a device with no clock domain".to_string(),
})?;
let target = EventTarget(u32::try_from(index).unwrap_or(u32::MAX));
Ok(self
.sched
.schedule_after_ticks(domain, ticks, target, token)?)
}
fn dispatch(&mut self, report: &QuantumReport) -> Result<()> {
for event in &report.fired {
let index = event.target.0 as usize;
let Some(instance) = self
.devices
.get(index)
.map(|d| d.instance.clone())
.ok_or_else(|| Error::Config {
at: self.name.clone(),
message: format!(
"event {} is addressed to device {index}, which does not exist",
event.id.seq()
),
})?
else {
continue;
};
instance.event(event.token, &mut self.deferred);
self.deferred.drain();
}
Ok(())
}
pub fn save(&self) -> Result<Vec<u8>> {
let mut w = StateWriter::new(self.shape.clone());
for entry in &self.devices {
let mut chunk = w.chunk(&entry.path, entry.class.name, entry.class.version)?;
entry.device.save(&mut chunk)?;
}
{
let mut chunk = w.chunk(CLOCK_PATH, CLOCK_CLASS, MACHINE_STATE_VERSION)?;
save_clocks(self.sched.forest(), &mut chunk)?;
}
{
let mut chunk = w.chunk(SCHED_PATH, SCHED_CLASS, MACHINE_STATE_VERSION)?;
save_sched(&self.sched, &mut chunk)?;
}
{
let mut chunk = w.chunk(WIRE_PATH, WIRE_CLASS, MACHINE_STATE_VERSION)?;
save_wires(&self.nets, &mut chunk)?;
}
w.to_vec()
}
pub fn load(&mut self, bytes: &[u8]) -> Result<()> {
self.load_with(bytes, &Migrations::new())
}
pub fn load_with(&mut self, bytes: &[u8], migrations: &Migrations) -> Result<()> {
let reader = StateReader::new(bytes)?;
reader.check_shape(&self.shape)?;
for entry in &self.devices {
let chunk = reader.load(
&entry.path,
entry.class.name,
entry.class.version,
migrations,
)?;
let mut r = chunk.reader();
entry.device.load(&mut r)?;
}
let clocks = reader.load(CLOCK_PATH, CLOCK_CLASS, MACHINE_STATE_VERSION, migrations)?;
load_clocks(self.sched.forest_mut(), &mut clocks.reader())?;
let sched = reader.load(SCHED_PATH, SCHED_CLASS, MACHINE_STATE_VERSION, migrations)?;
load_sched(&mut self.sched, &mut sched.reader())?;
let wires = reader.load(WIRE_PATH, WIRE_CLASS, MACHINE_STATE_VERSION, migrations)?;
load_wires(&self.nets, &mut wires.reader())?;
self.deferred.drain();
self.sweep();
Ok(())
}
pub fn state_hash(&self) -> Result<u64> {
Ok(fnv1a(&self.save()?))
}
}
fn fnv1a(bytes: &[u8]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for b in bytes {
h ^= u64::from(*b);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
fn save_clocks(forest: &ClockForest, sink: &mut impl Sink) -> Result<()> {
let oscillators: Vec<_> = forest.oscillators().collect();
sink.write_seq_len(oscillators.len() as u64)?;
for osc in oscillators {
sink.write_u64(forest.unit_position(osc)?)?;
}
let domains: Vec<_> = forest.domains().collect();
sink.write_seq_len(domains.len() as u64)?;
for id in domains {
sink.write_u64(forest.ticks(id)?)?;
}
Ok(())
}
fn load_clocks<'a>(forest: &mut ClockForest, src: &mut impl Source<'a>) -> Result<()> {
let domains: Vec<_> = forest.domains().collect();
let count = src.read_seq_len(8)? as usize;
let oscillators: Vec<_> = forest.oscillators().collect();
if count != oscillators.len() {
return Err(Error::State(format!(
"snapshot has {count} oscillators, this machine has {}",
oscillators.len()
)));
}
for osc in oscillators {
forest.restore_unit_position(osc, src.read_u64()?)?;
}
let count = src.read_seq_len(8)? as usize;
if count != domains.len() {
return Err(Error::State(format!(
"snapshot has {count} clock domains, this machine has {}",
domains.len()
)));
}
for id in domains {
forest.restore_ticks(id, src.read_u64()?)?;
}
Ok(())
}
fn save_sched(sched: &Scheduler, sink: &mut impl Sink) -> Result<()> {
let snapshot = sched.snapshot();
sink.write_u128(snapshot.now.raw())?;
sink.write_u64(snapshot.next_seq)?;
sink.write_u64(snapshot.cursor as u64)?;
sink.write_seq_len(snapshot.events.len() as u64)?;
for event in &snapshot.events {
sink.write_u128(event.time.raw())?;
sink.write_u64(event.id.seq())?;
sink.write_u32(event.target.0)?;
sink.write_u64(event.token)?;
}
Ok(())
}
fn load_sched<'a>(sched: &mut Scheduler, src: &mut impl Source<'a>) -> Result<()> {
let now = GlobalTime::from_raw(src.read_u128()?);
let next_seq = src.read_u64()?;
let cursor = usize::try_from(src.read_u64()?)
.map_err(|_| Error::State(String::from("scheduler cursor does not fit this host")))?;
let count = src.read_seq_len(36)? as usize;
let mut events = Vec::with_capacity(count.min(src.remaining()));
for _ in 0..count {
events.push(Event {
time: GlobalTime::from_raw(src.read_u128()?),
id: EventId::from_seq(src.read_u64()?),
target: EventTarget(src.read_u32()?),
token: src.read_u64()?,
});
}
sched.restore(&SchedulerSnapshot {
now,
next_seq,
cursor,
events,
})?;
Ok(())
}
fn save_wires(nets: &[Net], sink: &mut impl Sink) -> Result<()> {
sink.write_seq_len(nets.len() as u64)?;
for net in nets {
let levels = net.wire.snapshot();
sink.write_seq_len(levels.len() as u64)?;
for (id, level) in levels {
sink.write_u64(id.raw())?;
sink.write_u8(u8::from(level.is_high()))?;
}
}
Ok(())
}
fn load_wires<'a>(nets: &[Net], src: &mut impl Source<'a>) -> Result<()> {
let count = src.read_seq_len(8)? as usize;
if count != nets.len() {
return Err(Error::State(format!(
"snapshot has {count} wire nets, this machine has {}",
nets.len()
)));
}
for net in nets {
let sources = src.read_seq_len(9)? as usize;
let mut levels = Vec::with_capacity(sources.min(src.remaining()));
for _ in 0..sources {
let id = WireId::new(src.read_u64()?);
let level = match src.read_u8()? {
0 => Level::Low,
1 => Level::High,
other => {
return Err(Error::State(format!("wire level {other} is not 0 or 1")));
}
};
levels.push((id, level));
}
net.wire.restore(&levels);
}
for net in nets {
net.wire.refresh();
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_machine_chunk_paths_cannot_collide_with_a_device() {
assert!(CLOCK_PATH.starts_with('/'));
assert!(WIRE_PATH.starts_with('/'));
}
#[test]
fn the_state_hash_is_a_function_of_the_bytes() {
assert_eq!(fnv1a(b"abc"), fnv1a(b"abc"));
assert_ne!(fnv1a(b"abc"), fnv1a(b"abd"));
assert_eq!(fnv1a(b""), 0xcbf2_9ce4_8422_2325);
}
}