use crate::soc::Width;
use crate::sidecar::layout::{Slot, Version};
use crate::sidecar::sync::Ordering;
#[repr(u32)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Kind {
Hello = 1,
Read = 2,
Write = 3,
Stopped = 4,
RunUntil = 5,
Reply = 6,
Shutdown = 7,
}
impl Kind {
const ALL: [Self; 7] = [
Self::Hello,
Self::Read,
Self::Write,
Self::Stopped,
Self::RunUntil,
Self::Reply,
Self::Shutdown,
];
fn decode(kind: u32) -> Result<Self, DecodeError> {
Self::ALL
.into_iter()
.find(|candidate| *candidate as u32 == kind)
.ok_or(DecodeError::UnknownKind(kind))
}
}
const WIDTHS: [Width; 4] = [Width::W8, Width::W16, Width::W32, Width::W64];
fn decode_width(bits: u8) -> Result<Width, DecodeError> {
WIDTHS
.into_iter()
.find(|width| width.bits() == bits)
.ok_or(DecodeError::InvalidWidth(bits))
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum StopReason {
Deadline = 1,
Idle = 2,
}
impl StopReason {
const ALL: [Self; 2] = [Self::Deadline, Self::Idle];
fn decode(reason: u8) -> Result<Self, DecodeError> {
Self::ALL
.into_iter()
.find(|candidate| *candidate as u8 == reason)
.ok_or(DecodeError::UnknownReason(reason))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum DecodeError {
#[error("unknown message kind {0}")]
UnknownKind(u32),
#[error("unknown stop reason {0}")]
UnknownReason(u8),
#[error("width {0} is not a bus width")]
InvalidWidth(u8),
#[error("version {0:#x} does not fit the packed form")]
InvalidVersion(u64),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SidecarMessage {
Hello {
version: Version,
},
Read {
address: u64,
width: Width,
instruction_count: u64,
program_counter: u64,
virtual_time_ns: u64,
},
Write {
address: u64,
width: Width,
value: u64,
instruction_count: u64,
program_counter: u64,
virtual_time_ns: u64,
},
Stopped {
instruction_count: u64,
virtual_time_ns: u64,
reason: StopReason,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CoreMessage {
RunUntil {
deadline_ns: u64,
},
Reply {
value: u64,
deadline_ns: u64,
},
Shutdown,
}
#[derive(Default)]
struct SlotFields {
kind: u32,
width: u8,
reason: u8,
address: u64,
value: u64,
instruction_count: u64,
program_counter: u64,
virtual_time_ns: u64,
deadline_ns: u64,
}
impl SlotFields {
fn store(&self, slot: &Slot) {
slot.kind.store(self.kind, Ordering::Relaxed);
slot.width.store(self.width, Ordering::Relaxed);
slot.reason.store(self.reason, Ordering::Relaxed);
slot.address.store(self.address, Ordering::Relaxed);
slot.value.store(self.value, Ordering::Relaxed);
slot.instruction_count
.store(self.instruction_count, Ordering::Relaxed);
slot.program_counter
.store(self.program_counter, Ordering::Relaxed);
slot.virtual_time_ns
.store(self.virtual_time_ns, Ordering::Relaxed);
slot.deadline_ns.store(self.deadline_ns, Ordering::Relaxed);
}
fn load(slot: &Slot) -> Self {
Self {
kind: slot.kind.load(Ordering::Relaxed),
width: slot.width.load(Ordering::Relaxed),
reason: slot.reason.load(Ordering::Relaxed),
address: slot.address.load(Ordering::Relaxed),
value: slot.value.load(Ordering::Relaxed),
instruction_count: slot.instruction_count.load(Ordering::Relaxed),
program_counter: slot.program_counter.load(Ordering::Relaxed),
virtual_time_ns: slot.virtual_time_ns.load(Ordering::Relaxed),
deadline_ns: slot.deadline_ns.load(Ordering::Relaxed),
}
}
}
fn unpack_version(value: u64) -> Result<Version, DecodeError> {
u32::try_from(value)
.map(Version::unpack)
.map_err(|_too_wide| DecodeError::InvalidVersion(value))
}
impl SidecarMessage {
pub fn encode(&self, slot: &Slot) {
let fields = match *self {
Self::Hello { version } => SlotFields {
kind: Kind::Hello as u32,
value: u64::from(version.pack()),
..SlotFields::default()
},
Self::Read {
address,
width,
instruction_count,
program_counter,
virtual_time_ns,
} => SlotFields {
kind: Kind::Read as u32,
width: width.bits(),
address,
instruction_count,
program_counter,
virtual_time_ns,
..SlotFields::default()
},
Self::Write {
address,
width,
value,
instruction_count,
program_counter,
virtual_time_ns,
} => SlotFields {
kind: Kind::Write as u32,
width: width.bits(),
address,
value,
instruction_count,
program_counter,
virtual_time_ns,
..SlotFields::default()
},
Self::Stopped {
instruction_count,
virtual_time_ns,
reason,
} => SlotFields {
kind: Kind::Stopped as u32,
reason: reason as u8,
instruction_count,
virtual_time_ns,
..SlotFields::default()
},
};
fields.store(slot);
}
pub fn decode(slot: &Slot) -> Result<Self, DecodeError> {
let fields = SlotFields::load(slot);
match Kind::decode(fields.kind)? {
Kind::Hello => Ok(Self::Hello {
version: unpack_version(fields.value)?,
}),
Kind::Read => Ok(Self::Read {
address: fields.address,
width: decode_width(fields.width)?,
instruction_count: fields.instruction_count,
program_counter: fields.program_counter,
virtual_time_ns: fields.virtual_time_ns,
}),
Kind::Write => Ok(Self::Write {
address: fields.address,
width: decode_width(fields.width)?,
value: fields.value,
instruction_count: fields.instruction_count,
program_counter: fields.program_counter,
virtual_time_ns: fields.virtual_time_ns,
}),
Kind::Stopped => Ok(Self::Stopped {
instruction_count: fields.instruction_count,
virtual_time_ns: fields.virtual_time_ns,
reason: StopReason::decode(fields.reason)?,
}),
other => Err(DecodeError::UnknownKind(other as u32)),
}
}
}
impl CoreMessage {
pub fn encode(&self, slot: &Slot) {
let fields = match *self {
Self::RunUntil { deadline_ns } => SlotFields {
kind: Kind::RunUntil as u32,
deadline_ns,
..SlotFields::default()
},
Self::Reply { value, deadline_ns } => SlotFields {
kind: Kind::Reply as u32,
value,
deadline_ns,
..SlotFields::default()
},
Self::Shutdown => SlotFields {
kind: Kind::Shutdown as u32,
..SlotFields::default()
},
};
fields.store(slot);
}
pub fn decode(slot: &Slot) -> Result<Self, DecodeError> {
let fields = SlotFields::load(slot);
match Kind::decode(fields.kind)? {
Kind::RunUntil => Ok(Self::RunUntil {
deadline_ns: fields.deadline_ns,
}),
Kind::Reply => Ok(Self::Reply {
value: fields.value,
deadline_ns: fields.deadline_ns,
}),
Kind::Shutdown => Ok(Self::Shutdown),
other => Err(DecodeError::UnknownKind(other as u32)),
}
}
}
#[cfg(not(loom))]
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
fn width_strategy() -> impl Strategy<Value = Width> {
proptest::sample::select(WIDTHS.to_vec())
}
fn version_strategy() -> impl Strategy<Value = Version> {
(any::<u16>(), any::<u16>()).prop_map(|(major, minor)| Version { major, minor })
}
fn sidecar_message_strategy() -> impl Strategy<Value = SidecarMessage> {
prop_oneof![
version_strategy().prop_map(|version| SidecarMessage::Hello { version }),
(
any::<u64>(),
width_strategy(),
any::<u64>(),
any::<u64>(),
any::<u64>()
)
.prop_map(
|(address, width, instruction_count, program_counter, virtual_time_ns)| {
SidecarMessage::Read {
address,
width,
instruction_count,
program_counter,
virtual_time_ns,
}
}
),
(
any::<u64>(),
width_strategy(),
any::<u64>(),
any::<u64>(),
any::<u64>(),
any::<u64>()
)
.prop_map(
|(
address,
width,
value,
instruction_count,
program_counter,
virtual_time_ns,
)| {
SidecarMessage::Write {
address,
width,
value,
instruction_count,
program_counter,
virtual_time_ns,
}
}
),
(
any::<u64>(),
any::<u64>(),
proptest::sample::select(StopReason::ALL.to_vec())
)
.prop_map(|(instruction_count, virtual_time_ns, reason)| {
SidecarMessage::Stopped {
instruction_count,
virtual_time_ns,
reason,
}
}),
]
}
fn core_message_strategy() -> impl Strategy<Value = CoreMessage> {
prop_oneof![
any::<u64>().prop_map(|deadline_ns| CoreMessage::RunUntil { deadline_ns }),
(any::<u64>(), any::<u64>())
.prop_map(|(value, deadline_ns)| CoreMessage::Reply { value, deadline_ns }),
Just(CoreMessage::Shutdown),
]
}
proptest! {
#[test]
fn round_trip_every_message_kind(
sidecar in sidecar_message_strategy(),
core in core_message_strategy(),
) {
let slot = Slot::default();
sidecar.encode(&slot);
prop_assert_eq!(SidecarMessage::decode(&slot), Ok(sidecar));
core.encode(&slot);
prop_assert_eq!(CoreMessage::decode(&slot), Ok(core));
}
}
#[test]
fn decode_rejects_empty_slot() {
let slot = Slot::default();
assert_eq!(
SidecarMessage::decode(&slot),
Err(DecodeError::UnknownKind(0))
);
assert_eq!(CoreMessage::decode(&slot), Err(DecodeError::UnknownKind(0)));
}
#[test]
fn decode_rejects_the_other_sides_kind() {
let slot = Slot::default();
CoreMessage::Shutdown.encode(&slot);
assert_eq!(
SidecarMessage::decode(&slot),
Err(DecodeError::UnknownKind(Kind::Shutdown as u32))
);
SidecarMessage::Hello {
version: Version { major: 1, minor: 0 },
}
.encode(&slot);
assert_eq!(
CoreMessage::decode(&slot),
Err(DecodeError::UnknownKind(Kind::Hello as u32))
);
}
#[test]
fn decode_rejects_unknown_reason() {
let slot = Slot::default();
SidecarMessage::Stopped {
instruction_count: 0,
virtual_time_ns: 0,
reason: StopReason::Idle,
}
.encode(&slot);
slot.reason.store(0, Ordering::Relaxed);
assert_eq!(
SidecarMessage::decode(&slot),
Err(DecodeError::UnknownReason(0))
);
}
#[test]
fn decode_rejects_width_that_is_not_a_bus_width() {
let slot = Slot::default();
SidecarMessage::Read {
address: 0,
width: Width::W32,
instruction_count: 0,
program_counter: 0,
virtual_time_ns: 0,
}
.encode(&slot);
slot.width.store(24, Ordering::Relaxed);
assert_eq!(
SidecarMessage::decode(&slot),
Err(DecodeError::InvalidWidth(24))
);
}
#[test]
fn decode_rejects_version_above_thirty_two_bits() {
let slot = Slot::default();
SidecarMessage::Hello {
version: Version { major: 1, minor: 0 },
}
.encode(&slot);
slot.value.store(1 << 32, Ordering::Relaxed);
assert_eq!(
SidecarMessage::decode(&slot),
Err(DecodeError::InvalidVersion(1 << 32))
);
}
#[test]
fn hello_packs_the_version_into_value() {
let slot = Slot::default();
SidecarMessage::Hello {
version: Version { major: 1, minor: 2 },
}
.encode(&slot);
assert_eq!(slot.value.load(Ordering::Relaxed), 0x0001_0002);
}
#[test]
fn encode_zeroes_the_fields_a_message_lacks() {
let slot = Slot::default();
SidecarMessage::Write {
address: 1,
width: Width::W8,
value: 2,
instruction_count: 3,
program_counter: 4,
virtual_time_ns: 5,
}
.encode(&slot);
CoreMessage::Shutdown.encode(&slot);
let fields = SlotFields::load(&slot);
assert_eq!(fields.kind, Kind::Shutdown as u32);
assert_eq!(
[
u64::from(fields.width),
u64::from(fields.reason),
fields.address,
fields.value,
fields.instruction_count,
fields.program_counter,
fields.virtual_time_ns,
fields.deadline_ns
],
[0; 8]
);
}
}