use vj_abi::{Bit4, Region, SimTime};
#[cfg(feature = "frontend")]
pub(crate) mod value;
#[cfg(feature = "frontend")]
pub use value::*;
mod arena;
pub use arena::*;
#[cfg(feature = "frontend")]
pub mod storage;
#[cfg(feature = "frontend")]
pub mod event_plan;
#[cfg(feature = "frontend")]
pub mod event_pool;
#[cfg(feature = "frontend")]
mod layout;
#[cfg(feature = "frontend")]
pub use layout::*;
#[cfg(feature = "frontend")]
mod dedup;
#[cfg(feature = "frontend")]
pub use dedup::*;
mod instance;
pub use instance::*;
mod write;
mod huge_mirror;
pub use huge_mirror::{
HugeCommit, NbaWindow, huge_commit, huge_commit_full_done, huge_mark, huge_nba_window,
huge_prime,
};
pub mod force;
pub use write::*;
mod driver;
pub use driver::*;
pub use crate::driver_counts::*;
mod region_check;
pub use region_check::*;
mod accessor_impl;
pub use accessor_impl::*;
mod alias;
pub use alias::{AliasChange, AliasWindow, splice_window, window_value};
mod dispatch;
pub use dispatch::*;
mod rollback;
pub use rollback::*;
mod snapshot;
pub use snapshot::*;
mod frame;
pub use frame::*;
mod introspect;
pub use introspect::*;
mod kernel_ctx;
pub use kernel_ctx::*;
mod event;
pub use event::*;
mod gvt;
#[allow(unused_imports)]
pub use gvt::*;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct StateCellId(pub u64);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SlotClass {
Inline,
Wide,
Memory,
ForceWide,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct SlotOffset {
pub class: SlotClass,
pub word_offset: u32,
pub bit_width: u32,
pub elem_word_count: u32,
pub row_stride_words: u32,
pub num_rows: u32,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum EdgeKind {
None,
Posedge,
Negedge,
BothEdges,
}
pub(crate) fn four_state_bit_at(v: &FourStateValue, i: u32) -> Bit4 {
match v {
FourStateValue::Narrow(word) => {
let val = *word as u64;
let unk = (*word >> 64) as u64;
Bit4::from_val_unk((val >> i) & 1 != 0, (unk >> i) & 1 != 0)
}
FourStateValue::Wide { aval, bval } => {
let word = (i / 64) as usize;
let bit = i % 64;
let a = aval.get(word).copied().unwrap_or(0);
let b = bval.get(word).copied().unwrap_or(0);
Bit4::from_aval_bval((a >> bit) & 1 != 0, (b >> bit) & 1 != 0)
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum FourStateValue {
Narrow(u128),
Wide { aval: Vec<u64>, bval: Vec<u64> },
}
impl FourStateValue {
pub fn planes(&self) -> (Vec<u64>, Vec<u64>) {
match self {
FourStateValue::Narrow(w) => {
let (val, unk) = (*w as u64, (*w >> 64) as u64);
let (a, b) = vj_abi::bit4::narrow_word_to_wide(val, unk);
(vec![a], vec![b])
}
FourStateValue::Wide { aval, bval } => (aval.clone(), bval.clone()),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ListenerRole {
Scheduler,
Waveform,
Vpi,
}
pub trait ValueChangeSink {
fn notify_change(&mut self, cell: StateCellId, edge: EdgeKind, at: SimTime, region: Region);
fn wants_delta(&self, cell: StateCellId) -> bool {
let _ = cell;
false
}
fn note_delta(&mut self, cell: StateCellId, delta: ChangeDelta) {
let _ = (cell, delta);
}
}
#[derive(Clone, Debug, Default)]
pub struct ChangeDelta {
pub lo_word: usize,
pub old_a: Vec<u64>,
pub old_b: Vec<u64>,
pub new_a: Vec<u64>,
pub new_b: Vec<u64>,
}
impl ChangeDelta {
pub fn between(old: &FourStateValue, new: &FourStateValue) -> Self {
let (old_a, old_b) = old.planes();
let (new_a, new_b) = new.planes();
Self {
lo_word: 0,
old_a,
old_b,
new_a,
new_b,
}
}
fn word(plane: &[u64], lo_word: usize, word: usize) -> Option<u64> {
word.checked_sub(lo_word)
.and_then(|i| plane.get(i))
.copied()
}
pub fn bit(&self, index: u32) -> Option<((bool, bool), (bool, bool))> {
let (w, b) = ((index / 64) as usize, index % 64);
let pick = |p: &[u64]| Self::word(p, self.lo_word, w).map(|x| (x >> b) & 1 != 0);
Some((
(pick(&self.old_a)?, pick(&self.old_b)?),
(pick(&self.new_a)?, pick(&self.new_b)?),
))
}
pub fn range_changed(&self, lsb: u32, width: u32) -> bool {
let (lo, hi) = (lsb as usize, lsb as usize + width as usize);
let first = lo / 64;
let last = hi.saturating_sub(1) / 64;
for w in first..=last {
let (Some(oa), Some(ob), Some(na), Some(nb)) = (
Self::word(&self.old_a, self.lo_word, w),
Self::word(&self.old_b, self.lo_word, w),
Self::word(&self.new_a, self.lo_word, w),
Self::word(&self.new_b, self.lo_word, w),
) else {
continue;
};
let lo_bit = lo.saturating_sub(w * 64);
let hi_bit = (hi - w * 64).min(64);
let mask = if hi_bit - lo_bit >= 64 {
u64::MAX
} else {
((1u64 << (hi_bit - lo_bit)) - 1) << lo_bit
};
if ((oa ^ na) | (ob ^ nb)) & mask != 0 {
return true;
}
}
false
}
}
pub trait SignalAccess {
fn read(&self, cell: StateCellId) -> FourStateValue;
fn write(&mut self, cell: StateCellId, value: FourStateValue) -> bool;
fn read_row(&self, cell: StateCellId, row: u64) -> FourStateValue {
let _ = row;
unreachable!("read_row({cell:?}) on a SignalAccess without memory rows")
}
fn write_row(&mut self, cell: StateCellId, row: u64, value: FourStateValue) -> bool {
let _ = (row, value);
unreachable!("write_row({cell:?}) on a SignalAccess without memory rows")
}
fn read_wide_window(
&self,
cell: StateCellId,
lo_word: usize,
words: usize,
) -> (Vec<u64>, Vec<u64>) {
match self.read(cell) {
FourStateValue::Wide { aval, bval } => (
aval[lo_word..lo_word + words].to_vec(),
bval[lo_word..lo_word + words].to_vec(),
),
FourStateValue::Narrow(_) => {
unreachable!("read_wide_window({cell:?}) on a narrow cell")
}
}
}
fn write_wide_window(
&mut self,
cell: StateCellId,
lo_word: usize,
aval: &[u64],
bval: &[u64],
) -> bool {
write_wide_window_whole(self, cell, lo_word, aval, bval)
}
fn subscribe(
&mut self,
cell: StateCellId,
role: ListenerRole,
sink: Box<dyn ValueChangeSink + Send>,
);
fn take_alias_changes(&mut self) -> Vec<AliasChange> {
Vec::new()
}
}
pub(crate) fn write_wide_window_whole<A: SignalAccess + ?Sized>(
acc: &mut A,
cell: StateCellId,
lo_word: usize,
aval: &[u64],
bval: &[u64],
) -> bool {
let FourStateValue::Wide {
aval: mut a,
bval: mut b,
} = acc.read(cell)
else {
unreachable!("write_wide_window({cell:?}) on a narrow cell")
};
a[lo_word..lo_word + aval.len()].copy_from_slice(aval);
b[lo_word..lo_word + bval.len()].copy_from_slice(bval);
acc.write(cell, FourStateValue::Wide { aval: a, bval: b })
}
#[derive(Clone, Debug, PartialEq)]
pub struct StateLayout {
pub slots: Vec<SlotDescriptor>,
pub inline_words: u32,
pub wide_words: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SlotDescriptor {
pub key: crate::netlist::SymbolKey,
pub offset: SlotOffset,
pub initial: Option<FourStateValue>,
pub net_info: Option<NetLayout>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct NetLayout {
pub kind: NetKind,
pub shape: NetShape,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NetShape {
AliasedSingleDriver,
MultiDriverUniformStrength,
FullStrengthLattice,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
pub enum NetKind {
Wire,
WAnd,
WOr,
Tri,
TriAnd,
TriOr,
Tri0,
Tri1,
TriReg,
Supply0,
Supply1,
UWire,
UserDefined,
}
#[cfg(test)]
mod tests {
use super::*;
use vj_abi::Bit4;
#[test]
fn four_state_narrow_round_trips_via_abi_bit4() {
for b in [Bit4::Zero, Bit4::One, Bit4::X, Bit4::Z] {
let (val, unk) = b.to_val_unk();
let word: u128 = (val as u128) | ((unk as u128) << 64);
let fv = FourStateValue::Narrow(word);
match fv {
FourStateValue::Narrow(w) => assert_eq!(w, word),
FourStateValue::Wide { .. } => panic!("expected Narrow"),
}
}
}
#[test]
fn four_state_narrow_at_width_8_uses_fixed_64_offset_not_width_offset() {
let width: u32 = 8;
let val: u64 = 0b1011_0010;
let unk: u64 = 0b0000_1000;
let fixed_word: u128 = (val as u128) | ((unk as u128) << 64);
let width_tight_word: u128 = (val as u128) | ((unk as u128) << width);
assert_ne!(fixed_word, width_tight_word);
let fv = FourStateValue::Narrow(fixed_word);
match fv {
FourStateValue::Narrow(w) => {
assert_eq!((w >> 64) as u64, unk);
assert_eq!((w & u128::from(u64::MAX)) as u64, val);
}
FourStateValue::Wide { .. } => panic!("expected Narrow"),
}
}
#[test]
fn state_layout_has_exactly_the_three_contracted_fields() {
let layout = StateLayout {
slots: vec![],
inline_words: 0,
wide_words: 0,
};
assert!(layout.slots.is_empty());
assert_eq!(layout.inline_words, 0);
assert_eq!(layout.wide_words, 0);
}
#[test]
fn slot_descriptor_net_info_is_none_for_a_non_net_signal() {
let descriptor = SlotDescriptor {
key: crate::netlist::SymbolKey(0),
offset: SlotOffset {
class: SlotClass::Inline,
word_offset: 0,
bit_width: 1,
elem_word_count: 0,
row_stride_words: 0,
num_rows: 0,
},
initial: None,
net_info: None,
};
assert!(descriptor.net_info.is_none());
}
#[test]
fn net_shape_and_net_kind_cover_the_documented_variant_sets() {
fn describe_shape(shape: NetShape) -> &'static str {
match shape {
NetShape::AliasedSingleDriver => "aliased-single-driver",
NetShape::MultiDriverUniformStrength => "multi-driver-uniform-strength",
NetShape::FullStrengthLattice => "full-strength-lattice",
}
}
fn describe_kind(kind: NetKind) -> &'static str {
match kind {
NetKind::Wire => "wire",
NetKind::WAnd => "wand",
NetKind::WOr => "wor",
NetKind::Tri => "tri",
NetKind::TriAnd => "triand",
NetKind::TriOr => "trior",
NetKind::Tri0 => "tri0",
NetKind::Tri1 => "tri1",
NetKind::TriReg => "trireg",
NetKind::Supply0 => "supply0",
NetKind::Supply1 => "supply1",
NetKind::UWire => "uwire",
NetKind::UserDefined => "user-defined",
}
}
for shape in [
NetShape::AliasedSingleDriver,
NetShape::MultiDriverUniformStrength,
NetShape::FullStrengthLattice,
] {
assert!(!describe_shape(shape).is_empty());
}
for kind in [
NetKind::Wire,
NetKind::WAnd,
NetKind::WOr,
NetKind::Tri,
NetKind::TriAnd,
NetKind::TriOr,
NetKind::Tri0,
NetKind::Tri1,
NetKind::TriReg,
NetKind::Supply0,
NetKind::Supply1,
NetKind::UWire,
NetKind::UserDefined,
] {
assert!(!describe_kind(kind).is_empty());
}
let layout = NetLayout {
kind: NetKind::Tri,
shape: NetShape::FullStrengthLattice,
};
assert_eq!(layout.kind, NetKind::Tri);
assert_eq!(layout.shape, NetShape::FullStrengthLattice);
}
}