use std::collections::{HashMap, HashSet};
use vj_abi::Region;
use super::StateCellId;
use super::instance::StateEngine;
#[derive(Clone, Debug)]
pub struct RegionAssignment {
pub region: Region,
pub cells: HashSet<StateCellId>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RegionOverlapError {
pub cell: StateCellId,
pub regions: (Region, Region),
}
pub fn check_region_disjoint(
_engine: &StateEngine,
assignments: &[RegionAssignment],
) -> Result<(), RegionOverlapError> {
let mut owner: HashMap<StateCellId, Region> = HashMap::new();
for assignment in assignments {
for &cell in &assignment.cells {
match owner.get(&cell) {
Some(&prior_region) => {
return Err(RegionOverlapError {
cell,
regions: (prior_region, assignment.region),
});
}
None => {
owner.insert(cell, assignment.region);
}
}
}
}
Ok(())
}
pub trait ForceObserved {
fn on_force_observed(&mut self, net: StateCellId);
}
impl StateEngine {
pub fn register_force_observed(&mut self, listener: Box<dyn ForceObserved>) {
self.force_observed.push(listener);
}
pub(crate) fn notify_force_observed(&mut self, net: StateCellId) {
for listener in &mut self.force_observed {
listener.on_force_observed(net);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::driver::{
DriveStrength, DriveStrengthPair, DriverEntry, DriverSet, resolve_net_step,
};
use crate::state::instance::TemplateRegistry;
use crate::state::write::SlotLocation;
use crate::state::{FourStateValue, NetKind, SignalAccess, SlotClass};
use std::cell::RefCell;
use std::rc::Rc;
use vj_abi::Bit4;
fn fresh_engine() -> StateEngine {
StateEngine::new(TemplateRegistry::new())
}
fn scalar_cell(engine: &mut StateEngine) -> StateCellId {
let word_offset = unsafe { engine.arena.alloc_inline_word() };
let loc = SlotLocation {
class: SlotClass::Inline,
word_offset,
bit_width: 1,
elem_word_count: 1,
row_stride_words: 0,
num_rows: 0,
};
engine.alloc_cell(loc)
}
fn seeded_scalar_cell(engine: &mut StateEngine, bit: Bit4) -> StateCellId {
let cell = scalar_cell(engine);
let (v, u) = bit.to_val_unk();
let word = (v as u128) | ((u as u128) << 64);
engine.commit_write(cell, FourStateValue::Narrow(word), &mut ());
cell
}
fn strong_pair() -> DriveStrengthPair {
DriveStrengthPair {
s0: DriveStrength::Strong,
s1: DriveStrength::Strong,
}
}
#[test]
fn disjoint_region_assignments_pass_the_check() {
let mut engine = fresh_engine();
let cell_a = scalar_cell(&mut engine);
let cell_b = scalar_cell(&mut engine);
let assignments = vec![
RegionAssignment {
region: Region::Active,
cells: HashSet::from([cell_a]),
},
RegionAssignment {
region: Region::Nba,
cells: HashSet::from([cell_b]),
},
];
assert_eq!(check_region_disjoint(&engine, &assignments), Ok(()));
}
#[test]
fn a_cell_claimed_by_two_regions_fails_with_the_offending_cell_named() {
let mut engine = fresh_engine();
let shared_cell = scalar_cell(&mut engine);
let solo_cell = scalar_cell(&mut engine);
let assignments = vec![
RegionAssignment {
region: Region::Active,
cells: HashSet::from([shared_cell, solo_cell]),
},
RegionAssignment {
region: Region::Nba,
cells: HashSet::from([shared_cell]),
},
];
let err = check_region_disjoint(&engine, &assignments)
.expect_err("a cell present in two RegionAssignments must be rejected");
assert_eq!(err.cell, shared_cell);
assert_eq!(err.regions, (Region::Active, Region::Nba));
}
#[test]
fn force_driver_notifies_every_registered_force_observed_listener() {
struct Spy {
calls: Rc<RefCell<Vec<StateCellId>>>,
}
impl ForceObserved for Spy {
fn on_force_observed(&mut self, net: StateCellId) {
self.calls.borrow_mut().push(net);
}
}
let mut engine = fresh_engine();
let resolved = scalar_cell(&mut engine);
let mut set = DriverSet {
resolved,
drivers: vec![],
net_kind: NetKind::Wire,
};
let spy1_calls = Rc::new(RefCell::new(Vec::new()));
let spy2_calls = Rc::new(RefCell::new(Vec::new()));
engine.register_force_observed(Box::new(Spy {
calls: spy1_calls.clone(),
}));
engine.register_force_observed(Box::new(Spy {
calls: spy2_calls.clone(),
}));
let (v, u) = Bit4::One.to_val_unk();
let one = FourStateValue::Narrow((v as u128) | ((u as u128) << 64));
crate::state::driver::force_driver(&mut engine, &mut set, resolved, one);
assert_eq!(*spy1_calls.borrow(), vec![resolved]);
assert_eq!(*spy2_calls.borrow(), vec![resolved]);
}
#[test]
fn no_second_authoritative_copy_exists_for_a_cell_read_through_two_apis() {
let mut engine = fresh_engine();
let resolved = scalar_cell(&mut engine);
let driver_cell = seeded_scalar_cell(&mut engine, Bit4::One);
let set = DriverSet {
resolved,
drivers: vec![DriverEntry {
source: driver_cell,
strength: strong_pair(),
forced: false,
}],
net_kind: NetKind::Wire,
};
resolve_net_step(&mut engine, &set, &mut ()).unwrap();
let word_offset = engine.cells[resolved.0 as usize].word_offset;
let via_signal_access = match engine.read(resolved) {
FourStateValue::Narrow(w) => w,
FourStateValue::Wide { .. } => panic!("expected Narrow"),
};
let via_arena_field = engine.arena.inline()[word_offset as usize];
assert_eq!(
via_signal_access, via_arena_field,
"both read APIs must observe the exact same storage"
);
let (v, u) = Bit4::Zero.to_val_unk();
let new_word = (v as u128) | ((u as u128) << 64);
engine.commit_write(resolved, FourStateValue::Narrow(new_word), &mut ());
let via_signal_access_after = match engine.read(resolved) {
FourStateValue::Narrow(w) => w,
FourStateValue::Wide { .. } => panic!("expected Narrow"),
};
let via_arena_field_after = engine.arena.inline()[word_offset as usize];
assert_eq!(via_signal_access_after, new_word);
assert_eq!(via_arena_field_after, new_word);
assert_eq!(via_signal_access_after, via_arena_field_after);
}
}