use super::{FourStateValue, SignalAccess, StateEngine};
use crate::netlist::HierPathResolver;
pub fn read_by_hier_path<N: HierPathResolver + ?Sized>(
engine: &StateEngine,
netlist: &N,
path: &str,
) -> Option<FourStateValue> {
let sig = netlist.resolve_hier_path(path)?;
let cell = engine.cell_for_instantiated_signal(sig)?;
Some(engine.read(cell))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::netlist::{InstanceId, InstantiatedSignalId, SymbolKey, TemplateKey};
use crate::state::{SlotClass, SlotDescriptor, SlotOffset, StateLayout, TemplateRegistry};
use std::collections::HashMap;
struct MockNetlistView {
paths: HashMap<String, InstantiatedSignalId>,
}
impl MockNetlistView {
fn new() -> Self {
MockNetlistView {
paths: HashMap::new(),
}
}
fn map(&mut self, path: &str, sig: InstantiatedSignalId) {
self.paths.insert(path.to_string(), sig);
}
}
impl HierPathResolver for MockNetlistView {
fn resolve_hier_path(&self, path: &str) -> Option<InstantiatedSignalId> {
self.paths.get(path).copied()
}
}
fn key(definition: u32, params_hash: u64) -> TemplateKey {
TemplateKey {
definition,
params_hash,
}
}
fn inline_slot(sym: u32, word_offset: u32, bit_width: u32) -> SlotDescriptor {
SlotDescriptor {
key: SymbolKey(sym),
offset: SlotOffset {
class: SlotClass::Inline,
word_offset,
bit_width,
elem_word_count: 1,
row_stride_words: 0,
num_rows: 0,
},
initial: None,
net_info: None,
}
}
fn narrow_word(val: u64, unk: u64) -> u128 {
(val as u128) | ((unk as u128) << 64)
}
fn assert_narrow_eq(v: FourStateValue, expected: u128) {
match v {
FourStateValue::Narrow(w) => assert_eq!(w, expected),
FourStateValue::Wide { .. } => panic!("expected Narrow, got Wide"),
}
}
#[test]
fn read_by_hier_path_resolves_a_real_signal_through_a_mock_netlist_view() {
let templates = TemplateRegistry::new();
let tmpl = key(0, 0);
let member = SymbolKey(0);
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![inline_slot(member.0, 0, 8)],
inline_words: 1,
wide_words: 0,
},
);
}
let mut engine = StateEngine::new(templates);
let handle = engine
.allocate_instance(tmpl)
.expect("template is registered");
let sig = InstantiatedSignalId {
instance: InstanceId(0),
member,
};
let cell = engine
.cell_for_instantiated_signal(sig)
.expect("allocate_instance populates the hier map for every slot");
engine.commit_write(cell, FourStateValue::Narrow(narrow_word(0x2A, 0)), &mut ());
drop(handle);
let mut netlist = MockNetlistView::new();
netlist.map("top.leaf.x", sig);
match read_by_hier_path(&engine, &netlist, "top.leaf.x") {
Some(v) => assert_narrow_eq(v, narrow_word(0x2A, 0)),
None => panic!("expected a resolved value for a mapped path"),
}
assert!(
read_by_hier_path(&engine, &netlist, "top.leaf.unmapped").is_none(),
"an unresolvable path must return None, never a panic or a fabricated value"
);
}
#[test]
fn two_instances_of_a_deduped_template_have_independently_readable_identity() {
let templates = TemplateRegistry::new();
let tmpl = key(1, 0);
let member = SymbolKey(0);
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![inline_slot(member.0, 0, 8)],
inline_words: 1,
wide_words: 0,
},
);
}
let mut engine = StateEngine::new(templates);
let handle_a = engine
.allocate_instance(tmpl)
.expect("template is registered");
let handle_b = engine
.allocate_instance(tmpl)
.expect("template is registered");
drop(handle_a);
drop(handle_b);
let sig_a = InstantiatedSignalId {
instance: InstanceId(0),
member,
};
let sig_b = InstantiatedSignalId {
instance: InstanceId(1),
member,
};
let cell_a = engine
.cell_for_instantiated_signal(sig_a)
.expect("instance 0's hier entry exists");
let cell_b = engine
.cell_for_instantiated_signal(sig_b)
.expect("instance 1's hier entry exists");
assert_ne!(
cell_a, cell_b,
"two instances must never share one StateCellId"
);
engine.commit_write(
cell_a,
FourStateValue::Narrow(narrow_word(0x11, 0)),
&mut (),
);
engine.commit_write(
cell_b,
FourStateValue::Narrow(narrow_word(0x22, 0)),
&mut (),
);
let mut netlist = MockNetlistView::new();
netlist.map("top.a.x", sig_a);
netlist.map("top.b.x", sig_b);
match read_by_hier_path(&engine, &netlist, "top.a.x") {
Some(v) => assert_narrow_eq(v, narrow_word(0x11, 0)),
None => panic!("expected a resolved value for top.a.x"),
}
match read_by_hier_path(&engine, &netlist, "top.b.x") {
Some(v) => assert_narrow_eq(v, narrow_word(0x22, 0)),
None => panic!("expected a resolved value for top.b.x"),
}
}
#[test]
fn hier_path_map_grows_by_exactly_one_entry_per_signal_per_instance() {
let templates = TemplateRegistry::new();
let tmpl = key(2, 0);
unsafe {
templates.register(
tmpl,
StateLayout {
slots: vec![inline_slot(0, 0, 8), inline_slot(1, 1, 8)],
inline_words: 2,
wide_words: 0,
},
);
}
let mut engine = StateEngine::new(templates);
assert_eq!(engine.hier_cells_len(), 0);
for _ in 0..3 {
let _ = engine
.allocate_instance(tmpl)
.expect("template is registered");
}
assert_eq!(engine.hier_cells_len(), 6);
}
}