use super::arena::{Arena, BuildMode};
use super::{
FourStateValue, NetLayout, SlotClass, SlotDescriptor, SlotOffset, StateLayout,
four_state_of_constant, four_state_to_owned_svint, owned_svint_to_four_state,
};
use crate::netlist::SymbolKey;
use crate::symid::SymbolUid;
use sv_lang::kinds::SymbolKind;
use sv_lang::nodes::{
CheckerDeclarationSyntax, ClockingDeclarationSyntax, DefaultClockingReferenceSyntax,
DefaultDisableDeclarationSyntax, GenerateBlockSyntax, MemberSyntax, ModuleDeclarationSyntax,
};
use sv_lang::{AstNode, Node, Symbol, SyntaxTree};
#[derive(Debug)]
pub enum LayoutError {
UnsupportedMemberKind(SymbolKind, String),
UnsupportedType(String),
NonConstantCheckerOutputInitial(String),
}
fn checked_bit_width(bit_width: u64, member_name: &str) -> Result<u32, LayoutError> {
u32::try_from(bit_width).map_err(|_| LayoutError::UnsupportedType(member_name.to_string()))
}
fn memory_initial_rows(
member: &Symbol,
init: &sv_lang::Expression,
ty: &sv_lang::Type,
words_per_row: usize,
) -> Result<FourStateValue, LayoutError> {
let unsupported = || LayoutError::UnsupportedType(member.name().to_string());
if ty.is_unpacked_array() && vif_array_elem(ty).is_some() {
let mut ids = Vec::new();
vif_pattern_rows(init, ty, &mut ids).ok_or_else(unsupported)?;
let mut aval = Vec::new();
for id in ids {
aval.push(id);
aval.resize(aval.len() + words_per_row - 1, 0);
}
let bval = vec![0; aval.len()];
return Ok(FourStateValue::Wide { aval, bval });
}
let cv = init.constant_value().ok_or_else(unsupported)?;
let mut aval = Vec::new();
let mut bval = Vec::new();
push_rows(&cv, ty, words_per_row, &mut aval, &mut bval).ok_or_else(unsupported)?;
Ok(FourStateValue::Wide { aval, bval })
}
fn vif_array_elem<'d>(ty: &sv_lang::Type<'d>) -> Option<sv_lang::Type<'d>> {
let mut t = *ty;
while t.is_unpacked_array() && !super::storage::is_collection(&t) {
t = t.element_type()?;
}
(t.is_virtual_interface() || t.is_class()).then_some(t)
}
fn vif_pattern_rows(
e: &sv_lang::Expression<'_>,
ty: &sv_lang::Type<'_>,
out: &mut Vec<u64>,
) -> Option<()> {
use sv_lang_kinds::ExpressionKind;
let mut e = *e;
while e.kind() == ExpressionKind::Conversion {
e = e.conversion_operand()?;
}
if ty.is_unpacked_array() && !super::storage::is_collection(ty) {
let range = ty.fixed_unpacked_array_range();
let elem_ty = ty.element_type()?;
let k = e.kind();
let elems = if matches!(
k,
ExpressionKind::SimpleAssignmentPattern | ExpressionKind::StructuredAssignmentPattern
) {
e.pattern_elements()
} else if k == ExpressionKind::ReplicatedAssignmentPattern {
let n = e.replicated_pattern_count()?.constant_value()?.as_i64()?;
let one = e.pattern_elements();
(0..n).flat_map(|_| one.iter().copied()).collect()
} else {
return None;
};
if elems.len() as u64 != u64::from(range.width()) {
return None;
}
let mut rows = Vec::new();
for el in &elems {
let mut r = Vec::new();
vif_pattern_rows(el, &elem_ty, &mut r)?;
rows.push(r);
}
if range.left > range.right {
rows.reverse();
}
out.extend(rows.into_iter().flatten());
return Some(());
}
out.push(vif_leaf(&e)?);
Some(())
}
fn push_rows(
cv: &sv_lang::ConstantValue,
ty: &sv_lang::Type,
words: usize,
aval: &mut Vec<u64>,
bval: &mut Vec<u64>,
) -> Option<()> {
if ty.is_unpacked_array() {
let elems = cv.as_array()?;
let range = ty.fixed_unpacked_array_range();
let elem_ty = ty.element_type()?;
if elems.len() as u64 != u64::from(range.width()) {
return None;
}
if range.left > range.right {
for e in elems.iter().rev() {
push_rows(e, &elem_ty, words, aval, bval)?;
}
} else {
for e in elems {
push_rows(e, &elem_ty, words, aval, bval)?;
}
}
return Some(());
}
if super::storage::is_unpacked_aggregate(ty) {
let (mut a, mut b) =
super::value::bits_to_aval_bval(&super::storage::constant_bits(cv, ty)?);
a.resize(words, 0);
b.resize(words, 0);
aval.extend(a);
bval.extend(b);
return Some(());
}
let (mut a, mut b) = match cv {
sv_lang::ConstantValue::Integer(i) => {
super::value::bits_to_aval_bval(&super::value::svint_bits(i)?)
}
sv_lang::ConstantValue::Real(f) if ty.bit_width() == 32 => {
(vec![u64::from((*f as f32).to_bits())], vec![0])
}
sv_lang::ConstantValue::Real(f) => (vec![f.to_bits()], vec![0]),
sv_lang::ConstantValue::String(s) if ty.is_string() => {
(vec![crate::strings::intern(s.as_bytes())], vec![0])
}
_ => return None,
};
a.resize(words, 0);
b.resize(words, 0);
aval.extend(a);
bval.extend(b);
Some(())
}
fn collection_initial(
member: &Symbol,
init: &sv_lang::Expression,
ty: &sv_lang::Type,
) -> Result<u64, LayoutError> {
let unsupported = || LayoutError::UnsupportedType(member.name().to_string());
if ty.element_type().is_some_and(|e| e.is_virtual_interface()) {
return vif_collection_initial(init, ty).ok_or_else(unsupported);
}
let cv = init.constant_value().ok_or_else(unsupported)?;
let elem_ty = ty.element_type().ok_or_else(unsupported)?;
let width = super::storage::storage_width(&elem_ty).ok_or_else(unsupported)?;
let elem_words = (2 * width).div_ceil(64).max(1) as usize;
let elem_words_of = |e: &sv_lang::ConstantValue| -> Result<Vec<u64>, LayoutError> {
let mut w = super::value::packed_words(
&super::storage::constant_bits(e, &elem_ty).ok_or_else(unsupported)?,
);
w.resize(elem_words, 0);
Ok(w)
};
if ty.is_associative_array() {
let mut pattern_probe = *init;
while pattern_probe.kind() == sv_lang::kinds::ExpressionKind::Conversion {
pattern_probe = pattern_probe.operand().ok_or_else(unsupported)?;
}
if matches!(cv, sv_lang::ConstantValue::Map(_, true))
&& pattern_probe.structured_pattern_default_setter().is_none()
{
return Ok(0);
}
let mut pattern = *init;
while pattern.kind() == sv_lang::kinds::ExpressionKind::Conversion {
pattern = pattern.operand().ok_or_else(unsupported)?;
}
let key_ty = ty.associative_index_type().ok_or_else(unsupported)?;
let shape = crate::collections::Shape {
elem_words,
assoc: true,
key: if key_ty.is_string() {
crate::collections::KeyKind::Str
} else {
crate::collections::KeyKind::Int {
width: key_ty.bit_width() as u32,
signed: key_ty.is_signed(),
}
},
obj: false,
nested: crate::collections::Nested::NONE,
};
let mut entries = Vec::new();
for setter in pattern.structured_pattern_index_setters() {
let kcv = setter.index().constant_value().ok_or_else(unsupported)?;
let vcv = setter.expr().constant_value().ok_or_else(unsupported)?;
let kbits = super::storage::constant_bits(&kcv, &key_ty).ok_or_else(unsupported)?;
let key = shape
.key_from_bits(&super::value::packed_words(&kbits))
.ok_or_else(unsupported)?;
entries.push((key, elem_words_of(&vcv)?));
}
let default = match pattern.structured_pattern_default_setter() {
Some(d)
if elem_ty.is_string()
&& d.kind() == sv_lang::kinds::ExpressionKind::StringLiteral =>
{
let text = d.string_literal_value().ok_or_else(unsupported)?;
Some(elem_words_of(&sv_lang::ConstantValue::String(text))?)
}
Some(d) => Some(elem_words_of(&d.constant_value().ok_or_else(unsupported)?)?),
None => None,
};
return Ok(crate::collections::frozen_map(entries, default));
}
let mut words = Vec::new();
for e in cv.as_array().ok_or_else(unsupported)? {
words.extend(elem_words_of(e)?);
}
if words.is_empty() {
return Ok(0);
}
Ok(crate::collections::frozen_from_words(elem_words, &words))
}
fn vif_collection_initial(init: &sv_lang::Expression, ty: &sv_lang::Type) -> Option<u64> {
use sv_lang_kinds::ExpressionKind;
let mut pattern = *init;
while pattern.kind() == ExpressionKind::Conversion {
pattern = pattern.conversion_operand()?;
}
if ty.is_associative_array() {
let key_ty = ty.associative_index_type()?;
let shape = crate::collections::Shape {
elem_words: 2,
assoc: true,
key: if key_ty.is_string() {
crate::collections::KeyKind::Str
} else {
crate::collections::KeyKind::Int {
width: key_ty.bit_width() as u32,
signed: key_ty.is_signed(),
}
},
obj: false,
nested: crate::collections::Nested::NONE,
};
if pattern.kind() != ExpressionKind::StructuredAssignmentPattern {
return None;
}
let mut entries = Vec::new();
for setter in pattern.structured_pattern_index_setters() {
let kcv = setter.index().constant_value()?;
let kbits = super::storage::constant_bits(&kcv, &key_ty)?;
let key = shape.key_from_bits(&super::value::packed_words(&kbits))?;
entries.push((key, vec![vif_leaf(&setter.expr())?, 0]));
}
let default = match pattern.structured_pattern_default_setter() {
Some(d) => Some(vec![vif_leaf(&d)?, 0]),
None => None,
};
return Some(crate::collections::frozen_map(entries, default));
}
let k = pattern.kind();
let elems = if matches!(
k,
ExpressionKind::SimpleAssignmentPattern | ExpressionKind::StructuredAssignmentPattern
) {
pattern.pattern_elements()
} else if k == ExpressionKind::Concatenation {
pattern.operands()
} else if k == ExpressionKind::ReplicatedAssignmentPattern {
let n = pattern
.replicated_pattern_count()?
.constant_value()?
.as_i64()?;
let one = pattern.pattern_elements();
(0..n).flat_map(|_| one.iter().copied()).collect()
} else {
return None;
};
let mut words = Vec::new();
for el in &elems {
words.push(vif_leaf(el)?);
words.push(0);
}
if words.is_empty() {
return Some(0);
}
Some(crate::collections::frozen_from_words(2, &words))
}
fn vif_leaf(e: &sv_lang::Expression<'_>) -> Option<u64> {
use sv_lang_kinds::ExpressionKind;
let mut e = *e;
while e.kind() == ExpressionKind::Conversion {
e = e.conversion_operand()?;
}
if e.kind() == ExpressionKind::NullLiteral {
Some(0)
} else if e.kind() == ExpressionKind::ArbitrarySymbol {
super::super::netlist::vif::instance_id(&e.referenced_symbol()?)
} else {
None
}
}
pub fn synthetic_z_layout(widths: &[u32]) -> StateLayout {
synthetic_z_layout_rows(widths, &[], &[])
}
pub fn synthetic_z_layout_rows(
widths: &[u32],
rows: &[u32],
fills: &[Vec<sv_lang::Bit>],
) -> StateLayout {
let arena = Arena::new(BuildMode::FourState);
let mut slots = Vec::with_capacity(widths.len());
for (k, &w) in widths.iter().enumerate() {
let w = w.clamp(1, u32::MAX);
let words = w.div_ceil(64);
let num_rows = rows.get(k).copied().unwrap_or(0);
if num_rows > 0 {
let total = words * num_rows;
let offset = unsafe { arena.alloc_wide_words(total) };
let fill = match fills.get(k) {
Some(f) if !f.is_empty() => f.clone(),
_ => vec![sv_lang::Bit::Z; w as usize],
};
let (a, b) = super::value::bits_to_aval_bval(&fill);
slots.push(SlotDescriptor {
key: SymbolKey(k as u32),
offset: SlotOffset {
class: SlotClass::Memory,
word_offset: offset,
bit_width: w,
elem_word_count: words,
row_stride_words: words,
num_rows,
},
initial: Some(FourStateValue::Wide {
aval: a.repeat(num_rows as usize),
bval: b.repeat(num_rows as usize),
}),
net_info: None,
});
continue;
}
let (class, offset) = if words <= 1 {
(SlotClass::Inline, unsafe { arena.alloc_inline_word() })
} else {
(SlotClass::Wide, unsafe { arena.alloc_wide_words(words) })
};
slots.push(SlotDescriptor {
key: SymbolKey(k as u32),
offset: SlotOffset {
class,
word_offset: offset,
bit_width: w,
elem_word_count: words,
row_stride_words: 0,
num_rows: 0,
},
initial: Some(match fills.get(k) {
Some(f) if !f.is_empty() => super::value::bits_to_four_state(f),
_ => super::value::bits_to_four_state(&vec![sv_lang::Bit::Z; w as usize]),
}),
net_info: None,
});
}
StateLayout {
inline_words: arena.inline().len() as u32,
wide_words: arena.wide_aval().len() as u32,
slots,
}
}
pub fn clocking_blocks<'d>(body: &Symbol<'d>) -> Vec<(Symbol<'d>, Vec<Symbol<'d>>)> {
crate::netlist::module_items(body)
.into_iter()
.filter(|m| m.kind() == SymbolKind::ClockingBlock)
.map(|cb| {
let vars = cb
.members()
.filter(|v| {
v.kind() == SymbolKind::ClockVar
&& matches!(
v.clock_var_direction(),
sv_lang::ArgumentDirection::In | sv_lang::ArgumentDirection::InOut
)
})
.collect();
(cb, vars)
})
.collect()
}
#[derive(Debug)]
pub enum DefaultScopeError {
DuplicateDefaultClocking(String),
DuplicateDefaultDisable(String),
UnresolvedDefaultClockingTarget(String),
DefaultClockingTargetNotAClockingBlock(String),
}
fn items_of<'t>(node: Node<'t>) -> Option<Vec<MemberSyntax<'t>>> {
if let Some(m) = ModuleDeclarationSyntax::cast(node) {
return Some(m.members().into_iter().collect());
}
if let Some(c) = CheckerDeclarationSyntax::cast(node) {
return Some(c.members().into_iter().collect());
}
if let Some(g) = GenerateBlockSyntax::cast(node) {
return Some(g.members().into_iter().collect());
}
None
}
fn each_lexical_scope<'d, 't>(
scope: Symbol<'d>,
tree: &'t SyntaxTree,
) -> impl Iterator<Item = (Symbol<'d>, Vec<MemberSyntax<'t>>)> {
let mut cur = Some(scope);
std::iter::from_fn(move || {
while let Some(s) = cur.take() {
cur = s.parent();
if let Some(items) = s.syntax(tree).and_then(items_of) {
return Some((s, items));
}
}
None
})
}
fn find_in_lexical_scope<'d>(scope: Symbol<'d>, name: &str) -> Option<Symbol<'d>> {
let mut cur = Some(scope);
while let Some(s) = cur {
if let Some(found) = s.find(name) {
return Some(found);
}
cur = s.parent();
}
None
}
pub fn default_clocking_of<'d>(
scope: &Symbol<'d>,
tree: &SyntaxTree,
) -> Result<Option<Symbol<'d>>, DefaultScopeError> {
for (decl_scope, items) in each_lexical_scope(*scope, tree) {
let mut via_ref: Option<DefaultClockingReferenceSyntax> = None;
let mut via_inline: Option<ClockingDeclarationSyntax> = None;
let mut count = 0usize;
for item in &items {
match item {
MemberSyntax::DefaultClockingReference(n) => {
count += 1;
via_ref = Some(*n);
}
MemberSyntax::ClockingDeclaration(n)
if n.global_or_default().map(|t| t.text()) == Some("default") =>
{
count += 1;
via_inline = Some(*n);
}
_ => {}
}
}
if count > 1 {
return Err(DefaultScopeError::DuplicateDefaultClocking(
decl_scope.name().to_string(),
));
}
if let Some(n) = via_ref {
let name = n.name().map(|t| t.text()).unwrap_or("");
let sym = find_in_lexical_scope(decl_scope, name).ok_or_else(|| {
DefaultScopeError::UnresolvedDefaultClockingTarget(name.to_string())
})?;
if sym.kind() != SymbolKind::ClockingBlock {
return Err(DefaultScopeError::DefaultClockingTargetNotAClockingBlock(
name.to_string(),
));
}
return Ok(Some(sym));
}
if let Some(n) = via_inline {
let decl_node = AstNode::syntax(&n);
let sym = decl_scope.members().find(|m| {
m.kind() == SymbolKind::ClockingBlock && m.syntax(tree) == Some(decl_node)
});
return match sym {
Some(sym) => Ok(Some(sym)),
None => Err(DefaultScopeError::UnresolvedDefaultClockingTarget(
"<inline default clocking>".to_string(),
)),
};
}
}
Ok(None)
}
pub fn default_disable_of<'d, 't>(
scope: &Symbol<'d>,
tree: &'t SyntaxTree,
) -> Result<Option<(Symbol<'d>, DefaultDisableDeclarationSyntax<'t>)>, DefaultScopeError> {
for (decl_scope, items) in each_lexical_scope(*scope, tree) {
let mut found: Option<DefaultDisableDeclarationSyntax> = None;
let mut count = 0usize;
for item in &items {
if let MemberSyntax::DefaultDisableDeclaration(n) = item {
count += 1;
found = Some(*n);
}
}
if count > 1 {
return Err(DefaultScopeError::DuplicateDefaultDisable(
decl_scope.name().to_string(),
));
}
if let Some(n) = found {
return Ok(Some((decl_scope, n)));
}
}
Ok(None)
}
pub fn build_template_layout(body: &Symbol) -> Result<StateLayout, LayoutError> {
let arena = Arena::new(BuildMode::FourState);
let mut slots = Vec::new();
let mut next_key: u32 = 0;
let mut slot_of_symbol: std::collections::HashMap<crate::symid::SymbolId, usize> =
std::collections::HashMap::new();
for member in crate::netlist::state_items(body) {
let kind = member.kind();
let is_static_class_property = kind == SymbolKind::ClassProperty
&& member.variable_lifetime() == sv_lang::VariableLifetime::Static;
if !matches!(
kind,
SymbolKind::Variable | SymbolKind::Net | SymbolKind::Port | SymbolKind::FormalArgument
) && !is_static_class_property
{
continue;
}
if kind == SymbolKind::Port && member.port_internal_symbol().is_some() {
continue;
}
if kind == SymbolKind::Port && member.port_is_null_port() {
continue;
}
let ty = if kind == SymbolKind::Port {
member.port_type()
} else {
member.value_type()
}
.ok_or_else(|| LayoutError::UnsupportedMemberKind(kind, member.name().to_string()))?;
let is_signed = ty.is_signed();
let _is_four_state = ty.is_four_state();
let field_syms: Vec<_> = ty.fields().collect();
if !field_syms.is_empty() && ty.is_integral() {
if ty.is_tagged_union() {
for f_ty in field_syms.iter().filter_map(|f| f.value_type()) {
debug_assert!(
f_ty.bit_width() <= ty.bit_width(),
"packed tagged-union flatten: a member is wider than \
the union for member {:?}",
member.name(),
);
}
} else if ty.is_packed_union() && ty.packed_union_is_soft() {
for f_ty in field_syms.iter().filter_map(|f| f.value_type()) {
debug_assert!(
f_ty.bit_width() <= ty.bit_width(),
"soft packed-union flatten: a member is wider than \
the union for member {:?}",
member.name(),
);
}
} else if ty.is_packed_union() {
for f_ty in field_syms.iter().filter_map(|f| f.value_type()) {
debug_assert_eq!(
f_ty.bit_width(),
ty.bit_width(),
"packed-union flatten: every field's width must equal \
Type::bit_width() for member {:?}",
member.name(),
);
}
} else {
let summed_width: u64 = field_syms
.iter()
.filter_map(|f| f.value_type())
.map(|f_ty| f_ty.bit_width())
.sum();
debug_assert_eq!(
summed_width,
ty.bit_width(),
"packed-struct flatten: fields() total width must equal \
Type::bit_width() for member {:?}",
member.name(),
);
}
}
let (class, word_offset, bit_width, elem_word_count, row_stride_words, num_rows, initial) =
if kind == SymbolKind::Net && super::storage::interconnect_dims(&ty).is_some() {
let offset = unsafe { arena.alloc_inline_word() };
(
SlotClass::Inline,
offset,
1u32,
1u32,
0u32,
0u32,
Some(FourStateValue::Narrow(0)),
)
} else if super::storage::is_collection(&ty) {
let handle = match crate::netlist::variable_initializer(&member) {
Some(init) => collection_initial(&member, &init, &ty)?,
None => 0,
};
let offset = unsafe { arena.alloc_inline_word() };
(
SlotClass::Inline,
offset,
64u32,
1u32,
0u32,
0u32,
Some(FourStateValue::Narrow(u128::from(handle))),
)
} else if ty.is_unpacked_array() {
let mut rows: u32 = 1;
let mut elem_ty = ty;
while elem_ty.is_unpacked_array() && !super::storage::is_collection(&elem_ty) {
rows = rows
.checked_mul(elem_ty.fixed_unpacked_array_range().width())
.ok_or_else(|| LayoutError::UnsupportedType(member.name().to_string()))?;
elem_ty = elem_ty
.element_type()
.ok_or_else(|| LayoutError::UnsupportedType(member.name().to_string()))?;
}
let elem_bit_width_u64 = if elem_ty.is_integral() || elem_ty.is_floating() {
elem_ty.bit_width()
} else if elem_ty.is_string()
|| elem_ty.is_chandle()
|| elem_ty.is_covergroup()
|| elem_ty.is_virtual_interface()
|| elem_ty.is_class()
|| super::storage::is_collection(&elem_ty)
{
64
} else if elem_ty.is_event() {
if crate::netlist::variable_initializer(&member).is_some() {
return Err(LayoutError::UnsupportedType(member.name().to_string()));
}
super::EVENT_CELL_WIDTH as u64
} else if super::storage::is_unpacked_aggregate(&elem_ty) {
super::storage::storage_width(&elem_ty)
.ok_or_else(|| LayoutError::UnsupportedType(member.name().to_string()))?
} else {
return Err(LayoutError::UnsupportedType(member.name().to_string()));
};
let elem_bit_width = checked_bit_width(elem_bit_width_u64, member.name())?;
let words_per_row = (elem_bit_width_u64.div_ceil(64).max(1)) as u32;
let total_words = words_per_row * rows;
let offset = unsafe { arena.alloc_wide_words(total_words) };
let init_expr = crate::netlist::variable_initializer(&member).filter(|init| {
init.constant_value().is_some()
|| vif_array_elem(&ty).is_some_and(|e| e.is_virtual_interface())
});
let initial = match init_expr {
Some(init) => Some(memory_initial_rows(
&member,
&init,
&ty,
words_per_row as usize,
)?),
None if super::storage::is_unpacked_aggregate(&elem_ty) => {
let bits = super::storage::default_bits(&elem_ty).ok_or_else(|| {
LayoutError::UnsupportedType(member.name().to_string())
})?;
let (mut a, mut b) = super::value::bits_to_aval_bval(&bits);
a.resize(words_per_row as usize, 0);
b.resize(words_per_row as usize, 0);
Some(FourStateValue::Wide {
aval: a.repeat(rows as usize),
bval: b.repeat(rows as usize),
})
}
None => (!elem_ty.is_four_state()).then_some(FourStateValue::Narrow(0)),
};
(
SlotClass::Memory,
offset,
elem_bit_width,
words_per_row,
words_per_row,
rows,
initial,
)
} else if super::storage::is_unpacked_aggregate(&ty) {
let unsupported = || LayoutError::UnsupportedType(member.name().to_string());
let width_u64 = super::storage::storage_width(&ty).ok_or_else(unsupported)?;
let bit_width = checked_bit_width(width_u64, member.name())?;
let bits = match crate::netlist::variable_initializer(&member) {
Some(_) if super::storage::contains_tagged_union(&ty) => {
super::storage::default_bits(&ty).ok_or_else(unsupported)?
}
Some(init) => {
let cv = init.constant_value().ok_or_else(unsupported)?;
super::storage::constant_bits(&cv, &ty).ok_or_else(unsupported)?
}
None => super::storage::default_bits(&ty).ok_or_else(unsupported)?,
};
let word_count = width_u64.div_ceil(64).max(1) as u32;
let (class, offset) = if word_count <= 1 {
(SlotClass::Inline, unsafe { arena.alloc_inline_word() })
} else {
(SlotClass::Wide, unsafe {
arena.alloc_wide_words(word_count)
})
};
(
class,
offset,
bit_width,
word_count,
0u32,
0u32,
Some(super::value::bits_to_four_state(&bits)),
)
} else if ty.is_floating() {
let width = ty.bit_width() as u32;
let bits: u64 = match crate::netlist::variable_initializer(&member)
.and_then(|init| init.constant_value())
{
Some(sv_lang::ConstantValue::Real(f)) if width == 32 => {
u64::from((f as f32).to_bits())
}
Some(sv_lang::ConstantValue::Real(f)) => f.to_bits(),
_ => 0,
};
let offset = unsafe { arena.alloc_inline_word() };
(
SlotClass::Inline,
offset,
width,
1u32,
0u32,
0u32,
Some(FourStateValue::Narrow(u128::from(bits))),
)
} else if ty.is_string() {
let id = match crate::netlist::variable_initializer(&member)
.and_then(|init| init.constant_value())
{
Some(sv_lang::ConstantValue::String(s)) => crate::strings::intern(s.as_bytes()),
_ => 0,
};
let offset = unsafe { arena.alloc_inline_word() };
(
SlotClass::Inline,
offset,
64u32,
1u32,
0u32,
0u32,
Some(FourStateValue::Narrow(u128::from(id))),
)
} else if ty.is_chandle()
|| ty.is_virtual_interface()
|| ty.is_class()
|| ty.is_covergroup()
{
let offset = unsafe { arena.alloc_inline_word() };
(
SlotClass::Inline,
offset,
64u32,
1u32,
0u32,
0u32,
Some(FourStateValue::Narrow(0)),
)
} else if ty.is_event() {
let words = super::EVENT_CELL_WIDTH.div_ceil(64);
let offset = unsafe { arena.alloc_wide_words(words) };
(
SlotClass::Wide,
offset,
super::EVENT_CELL_WIDTH,
words,
0u32,
0u32,
Some(FourStateValue::Wide {
aval: vec![0; words as usize],
bval: vec![0; words as usize],
}),
)
} else {
if !ty.is_integral() {
return Err(LayoutError::UnsupportedType(member.name().to_string()));
}
let bit_width_u64 = ty.bit_width();
let bit_width = checked_bit_width(bit_width_u64, member.name())?;
let word_count_u64 = bit_width_u64.div_ceil(64).max(1);
let word_count = word_count_u64 as u32;
let (class, offset) = if word_count <= 1 {
(SlotClass::Inline, unsafe { arena.alloc_inline_word() })
} else {
(SlotClass::Wide, unsafe {
arena.alloc_wide_words(word_count)
})
};
let checker_initial = crate::netlist::checker_output_of(&member)
.and_then(|o| o.initial)
.map(|init| {
if init.constant_value().is_some() {
Ok(init)
} else {
Err(LayoutError::NonConstantCheckerOutputInitial(
member.name().to_string(),
))
}
})
.transpose()?;
let initial = crate::netlist::variable_initializer(&member)
.or(checker_initial)
.and_then(|expr| expr.constant_value())
.and_then(|cv| four_state_of_constant(&cv))
.and_then(|fv| {
owned_svint_to_four_state(&four_state_to_owned_svint(
&fv, bit_width, is_signed,
))
});
let initial = initial.or_else(|| {
crate::netlist::free_var_kind(&member).map(|_| {
let mut state = crate::netlist::free_var_seed(&member);
let mut words: Vec<u64> = (0..word_count.max(1))
.map(|_| crate::sched::process_rng::splitmix64_next(&mut state))
.collect();
if bit_width % 64 != 0
&& let Some(last) = words.get_mut(((bit_width - 1) / 64) as usize)
{
*last &= (1u64 << (bit_width % 64)) - 1;
}
if class == SlotClass::Inline {
FourStateValue::Narrow(u128::from(words[0]))
} else {
FourStateValue::Wide {
bval: vec![0; words.len()],
aval: words,
}
}
})
});
let initial = initial.or_else(|| {
(member.kind() == sv_lang::kinds::SymbolKind::Net
&& ty.is_four_state()
&& member.net_charge_strength().is_none())
.then(|| {
super::value::bits_to_four_state(&vec![sv_lang::Bit::Z; bit_width as usize])
})
});
let initial = initial.or_else(|| {
(!ty.is_four_state() && !super::storage::contains_tagged_union(&ty)).then(
|| {
if class == SlotClass::Inline {
FourStateValue::Narrow(0)
} else {
FourStateValue::Wide {
aval: vec![0; word_count as usize],
bval: vec![0; word_count as usize],
}
}
},
)
});
let elem_word_count = word_count;
(
class,
offset,
bit_width,
elem_word_count,
0u32,
0u32,
initial,
)
};
let key = SymbolKey(next_key);
next_key += 1;
slot_of_symbol.insert(member.uid(), slots.len());
slots.push(SlotDescriptor {
key,
offset: SlotOffset {
class,
word_offset,
bit_width,
elem_word_count,
row_stride_words,
num_rows,
},
initial,
net_info: None::<NetLayout>,
});
}
for (cb, vars) in clocking_blocks(body) {
let _ = cb;
let offset = unsafe { arena.alloc_inline_word() };
slots.push(SlotDescriptor {
key: SymbolKey(next_key),
offset: SlotOffset {
class: SlotClass::Inline,
word_offset: offset,
bit_width: 32,
elem_word_count: 1,
row_stride_words: 0,
num_rows: 0,
},
initial: Some(FourStateValue::Narrow(0)),
net_info: None,
});
next_key += 1;
for v in vars {
let ty = v
.value_type()
.filter(|t| t.is_integral())
.ok_or_else(|| LayoutError::UnsupportedType(v.name().to_string()))?;
let width = checked_bit_width(ty.bit_width(), v.name())?;
let words = width.div_ceil(64).max(1);
let (class, offset) = if words <= 1 {
(SlotClass::Inline, unsafe { arena.alloc_inline_word() })
} else {
(SlotClass::Wide, unsafe { arena.alloc_wide_words(words) })
};
slots.push(SlotDescriptor {
key: SymbolKey(next_key),
offset: SlotOffset {
class,
word_offset: offset,
bit_width: width,
elem_word_count: words,
row_stride_words: 0,
num_rows: 0,
},
initial: (!ty.is_four_state()).then(|| {
super::value::bits_to_four_state(&vec![sv_lang::Bit::Zero; width as usize])
}),
net_info: None,
});
next_key += 1;
}
}
let hidden = crate::sva::sampled::hidden_slots(body)
.map_err(|e| LayoutError::UnsupportedType(format!("sampled value function: {e}")))?;
for cell in hidden.slots() {
let (class, words, bit_width, initial) = if let Some(shape) = &cell.foreign {
let words = shape.width.div_ceil(64).max(1);
let class = if words > 1 {
SlotClass::Wide
} else {
SlotClass::Inline
};
(class, words, shape.width, shape.initial.clone())
} else {
let src = slot_of_symbol.get(&cell.source).ok_or_else(|| {
LayoutError::UnsupportedType(format!(
"sampled value function over `{}`, which has no state slot",
cell.source
))
})?;
let s = &slots[*src];
(
s.offset.class,
s.offset.elem_word_count,
s.offset.bit_width,
s.initial.clone(),
)
};
let word_offset = match class {
SlotClass::Inline => unsafe { arena.alloc_inline_word() },
SlotClass::Wide => unsafe { arena.alloc_wide_words(words) },
other => {
return Err(LayoutError::UnsupportedType(format!(
"sampled value function over `{}`, a {other:?} slot",
cell.source
)));
}
};
slots.push(SlotDescriptor {
key: SymbolKey(next_key),
offset: SlotOffset {
class,
word_offset,
bit_width,
elem_word_count: words,
row_stride_words: 0,
num_rows: 0,
},
initial,
net_info: None,
});
next_key += 1;
}
for _ in crate::sva::endpoint::watchers(body) {
let words = super::EVENT_CELL_WIDTH.div_ceil(64);
let word_offset = unsafe { arena.alloc_wide_words(words) };
slots.push(SlotDescriptor {
key: SymbolKey(next_key),
offset: SlotOffset {
class: SlotClass::Wide,
word_offset,
bit_width: super::EVENT_CELL_WIDTH,
elem_word_count: words,
row_stride_words: 0,
num_rows: 0,
},
initial: Some(FourStateValue::Wide {
aval: vec![0; words as usize],
bval: vec![0; words as usize],
}),
net_info: None,
});
next_key += 1;
}
if let Some(pool) = super::event_pool::event_pool(body) {
let words_per_row = super::EVENT_CELL_WIDTH.div_ceil(64);
let rows = pool.total_rows();
let word_offset = unsafe { arena.alloc_wide_words(words_per_row * rows) };
slots.push(SlotDescriptor {
key: SymbolKey(next_key),
offset: SlotOffset {
class: SlotClass::Memory,
word_offset,
bit_width: super::EVENT_CELL_WIDTH,
elem_word_count: words_per_row,
row_stride_words: words_per_row,
num_rows: rows,
},
initial: Some(FourStateValue::Narrow(0)),
net_info: None,
});
}
Ok(StateLayout {
inline_words: arena.inline().len() as u32,
wide_words: arena.wide_aval().len() as u32,
slots,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::FourStateValue;
use crate::state::value::compile_source;
#[test]
fn layout_builder_is_read_only_and_deterministic_on_repeat_calls() {
let design = compile_source("module m; logic [7:0] x; wire w; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let first = build_template_layout(&body).expect("layout builds");
let second = build_template_layout(&body).expect("layout builds again");
assert_eq!(first.slots.len(), second.slots.len());
assert_eq!(first.inline_words, second.inline_words);
assert_eq!(first.wide_words, second.wide_words);
for (a, b) in first.slots.iter().zip(second.slots.iter()) {
assert_eq!(a.key, b.key);
assert_eq!(a.offset.class, b.offset.class);
assert_eq!(a.offset.word_offset, b.offset.word_offset);
assert_eq!(a.offset.bit_width, b.offset.bit_width);
}
}
#[test]
fn sampled_value_calls_append_cloned_hidden_slots() {
let src = "module top; logic clk; logic [69:0] w; int n;\n\
always @(posedge clk) begin n <= $past(n, 2); if ($rose(w)) n <= 0; end\n\
endmodule\n";
let plain = compile_source("module top; logic clk; logic [69:0] w; int n; endmodule\n")
.expect("compiles");
let design = compile_source(src).expect("compiles");
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let base = build_template_layout(
&plain
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap(),
)
.expect("base layout");
let hidden = crate::sva::sampled::hidden_slots(&body).expect("hidden");
let layout = build_template_layout(&body).expect("layout");
assert_eq!(
layout.slots.len(),
base.slots.len() + hidden.slots().count()
);
let n_slots: Vec<_> = layout.slots[base.slots.len()..]
.iter()
.filter(|s| s.offset.bit_width == 32)
.collect();
assert_eq!(n_slots.len(), 3, "P + two S cells of n");
for s in n_slots {
assert!(
matches!(s.initial, Some(FourStateValue::Narrow(0))),
"{:?}",
s.initial
);
}
let w_slots: Vec<_> = layout.slots[base.slots.len()..]
.iter()
.filter(|s| s.offset.bit_width == 70)
.collect();
assert_eq!(w_slots.len(), 2, "P + S_0 of w");
assert!(w_slots.iter().all(|s| s.offset.class == SlotClass::Wide));
let mut seen = std::collections::HashSet::new();
for s in &layout.slots {
assert!(seen.insert((s.offset.class as u8, s.offset.word_offset)));
}
}
#[test]
fn only_variable_net_port_kinds_produce_slots() {
let design = compile_source(
"module m #(parameter int P = 1) (input logic p_in); genvar i; logic x; wire w; endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("layout builds");
assert_eq!(layout.slots.len(), 3);
}
#[test]
fn only_static_class_properties_produce_slots() {
let design = compile_source(
"class Counter; static int count; int id; \
function void bump(); id = id + 1; endfunction endclass\n\
module m; Counter c; endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let class_sym = body
.find("c")
.expect("c declared")
.value_type()
.expect("c has a type")
.as_symbol();
let layout = build_template_layout(&class_sym).expect("layout builds");
assert_eq!(
layout.slots.len(),
1,
"only the static property `count` gets a slot -- not `id` (an \
instance field) and not `bump` (a method)"
);
assert_eq!(layout.slots[0].offset.bit_width, 32);
}
#[test]
fn static_const_initializer_is_baked_into_the_slot() {
let design = compile_source(
"class Counter; static const int FIVE = 5; endclass\n\
module m; Counter c; endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let class_sym = body
.find("c")
.expect("c declared")
.value_type()
.expect("c has a type")
.as_symbol();
let layout = build_template_layout(&class_sym).expect("layout builds");
assert_eq!(layout.slots.len(), 1);
match &layout.slots[0].initial {
Some(FourStateValue::Narrow(v)) => assert_eq!(*v, 5),
other => panic!("expected a baked-in Narrow(5) initial value, got {other:?}"),
}
}
#[test]
fn genvars_params_and_subroutine_locals_never_become_slots() {
let design = compile_source(
"module m; parameter int P = 1; genvar i; \
function automatic int f(int a); int local_v; return a; endfunction; \
logic only_real_signal; endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("layout builds");
assert_eq!(layout.slots.len(), 1);
let members: Vec<_> = body
.members()
.filter(|s| {
matches!(
s.kind(),
SymbolKind::Variable | SymbolKind::Net | SymbolKind::Port
)
})
.collect();
assert_eq!(members.len(), 1);
assert_eq!(members[0].name(), "only_real_signal");
}
#[test]
fn unsupported_member_kind_is_a_loud_typed_error_not_a_silent_skip() {
let design = compile_source(
"module m; function automatic int f(int a); return a; endfunction; \
logic ok_signal; endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("no unsupported member kind reached");
assert_eq!(layout.slots.len(), 1);
let members: Vec<_> = body
.members()
.filter(|s| s.kind() == SymbolKind::Subroutine)
.collect();
assert_eq!(
members.len(),
1,
"expected exactly one Subroutine-kind member for this fixture"
);
let err = LayoutError::UnsupportedMemberKind(SymbolKind::Variable, "x".to_string());
match err {
LayoutError::UnsupportedMemberKind(kind, name) => {
assert_eq!(kind, SymbolKind::Variable);
assert_eq!(name, "x");
}
LayoutError::UnsupportedType(name)
| LayoutError::NonConstantCheckerOutputInitial(name) => {
panic!("unexpected {name} error for this constructed value")
}
}
}
#[test]
fn null_ports_never_become_slots() {
let design = compile_source("module m(a, , c); input a; input c; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let ports: Vec<_> = body
.members()
.filter(|s| s.kind() == SymbolKind::Port)
.collect();
assert_eq!(ports.len(), 3, "expected 3 Port-kind members (a, null, c)");
assert_eq!(
ports.iter().filter(|p| p.port_is_null_port()).count(),
1,
"expected exactly one null port"
);
let layout = build_template_layout(&body).expect("layout builds");
assert_eq!(
layout.slots.len(),
2,
"only the two real ports (a, c) should produce slots -- the null port must not"
);
for slot in &layout.slots {
assert!(
slot.offset.bit_width > 0,
"no slot may be a phantom zero-width slot for the null port"
);
}
}
#[test]
fn signed_17bit_variable_gets_correct_width_not_sign_extended_into_18bits() {
let design = compile_source("module m; logic signed [16:0] x; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("layout builds");
assert_eq!(layout.slots.len(), 1);
assert_eq!(layout.slots[0].offset.class, SlotClass::Inline);
assert_eq!(layout.slots[0].offset.bit_width, 17);
}
#[test]
fn eight_deep_memory_of_32bit_rows_gets_memory_class_with_correct_stride_and_rows() {
let design = compile_source("module m; logic [31:0] mem [0:7]; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("layout builds");
assert_eq!(layout.slots.len(), 1);
let offset = layout.slots[0].offset;
assert_eq!(offset.class, SlotClass::Memory);
assert_eq!(offset.elem_word_count, 1);
assert_eq!(offset.row_stride_words, 1);
assert_eq!(offset.num_rows, 8);
}
#[test]
fn event_array_lays_out_as_memory_of_event_cells() {
let design = compile_source("module m; event ea [0:2]; event e2 [1:2][0:1]; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("layout builds");
assert_eq!(layout.slots.len(), 2);
let (flat, multi) = (&layout.slots[0].offset, &layout.slots[1].offset);
for (offset, rows) in [(flat, 3), (multi, 4)] {
assert_eq!(offset.class, SlotClass::Memory);
assert_eq!(offset.bit_width, super::super::EVENT_CELL_WIDTH);
assert_eq!(offset.elem_word_count, 2);
assert_eq!(offset.row_stride_words, 2);
assert_eq!(offset.num_rows, rows);
}
}
#[test]
fn packed_struct_flattens_to_exactly_one_slot_of_total_width() {
let design = compile_source(
"module m; typedef struct packed { logic [7:0] a; logic [15:0] b; \
logic [15:0] c; } s_t; s_t x; endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("layout builds");
assert_eq!(
layout.slots.len(),
1,
"expected one flattened slot, not one per field"
);
assert_eq!(layout.slots[0].offset.bit_width, 40);
assert_eq!(layout.slots[0].offset.class, SlotClass::Inline);
}
#[test]
fn initializer_constant_folds_into_slot_initial_value() {
let design = compile_source("module m; logic [7:0] x = 8'hAA; logic [7:0] y; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("layout builds");
assert_eq!(layout.slots.len(), 2);
let x_initial = layout.slots[0]
.initial
.as_ref()
.expect("x has an initializer and must have Some(initial)");
assert!(
matches!(x_initial, FourStateValue::Narrow(0xAA)),
"x's initializer must fold to 8'hAA with no unknown bits, got {x_initial:?}"
);
assert!(
layout.slots[1].initial.is_none(),
"y has no initializer and must stay None, never a fabricated zero"
);
}
#[test]
fn unsigned_and_signed_same_width_produce_identical_slot_shape() {
let design = compile_source("module m; logic [7:0] u; logic signed [7:0] s; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("layout builds");
assert_eq!(layout.slots.len(), 2);
assert_eq!(
layout.slots[0].offset.bit_width,
layout.slots[1].offset.bit_width
);
assert_eq!(layout.slots[0].offset.class, layout.slots[1].offset.class);
}
#[test]
fn a_collection_member_is_a_64_bit_handle_slot_starting_empty() {
for src in [
"module m; int q[$]; logic [7:0] ok_signal; endmodule\n",
"module m; int d[]; logic [7:0] ok_signal; endmodule\n",
"module m; int aa[string]; logic [7:0] ok_signal; endmodule\n",
] {
let design = compile_source(src).expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("a collection member lays out");
assert_eq!(layout.slots.len(), 2, "{src}");
assert_eq!(layout.slots[0].offset.class, SlotClass::Inline);
assert_eq!(layout.slots[0].offset.bit_width, 64);
assert!(matches!(
layout.slots[0].initial,
Some(FourStateValue::Narrow(0))
));
assert_eq!(layout.slots[1].offset.bit_width, 8);
}
}
#[test]
fn a_queue_initializer_is_a_frozen_collection_of_its_elements() {
let design = compile_source("module m; int q[$] = {4, 5, 6}; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("lays out");
let Some(FourStateValue::Narrow(h)) = layout.slots[0].initial else {
panic!("expected a handle initial value");
};
let h = h as u64;
assert_eq!(crate::collections::size(h), 3);
let mut out = [0u64];
assert!(crate::collections::get(h, 2, &mut out));
assert_eq!(out[0], 6);
}
#[test]
fn wide_128bit_scalar_gets_correct_elem_word_count_not_hardcoded_zero() {
let design = compile_source("module m; logic [127:0] w; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("layout builds");
assert_eq!(layout.slots.len(), 1);
let offset = layout.slots[0].offset;
assert_eq!(offset.class, SlotClass::Wide);
assert_eq!(offset.bit_width, 128);
assert_eq!(
offset.elem_word_count, 2,
"a 128-bit Wide slot must span exactly 2 wide words, not 0"
);
}
#[test]
fn packed_union_with_multiple_fields_does_not_panic_the_flatten_invariant() {
let design = compile_source(
"module m; typedef union packed { logic [7:0] a; logic [7:0] b; } u_t; u_t x; endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("layout builds, does not panic");
assert_eq!(layout.slots.len(), 1);
assert_eq!(layout.slots[0].offset.bit_width, 8);
assert_eq!(layout.slots[0].offset.class, SlotClass::Inline);
}
#[test]
fn multi_dimensional_unpacked_array_flattens_into_rows_of_the_innermost_element() {
let design = compile_source("module m; logic [7:0] mem2d [3:0][1:0]; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("lays out");
let offset = &layout.slots[0].offset;
assert_eq!(offset.class, SlotClass::Memory);
assert_eq!(offset.bit_width, 8);
assert_eq!(offset.num_rows, 8, "4 x 2 rows");
}
#[test]
fn class_handles_are_64_bit_cells_and_strings_are_id_cells_not_phantom_slots() {
let design = compile_source(
"module m; class C; endclass C c; string s; logic [7:0] ok_signal; endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body)
.expect("a class-handle member must now lay out, not be rejected");
let c_slot = &layout.slots[0];
assert_eq!(c_slot.offset.class, SlotClass::Inline);
assert_eq!(
c_slot.offset.bit_width, 64,
"a class handle is a plain 64-bit reference, exactly like a chandle's"
);
assert!(
matches!(c_slot.initial, Some(FourStateValue::Narrow(0))),
"a class variable's default initial value, absent an explicit `new`, is null (0)"
);
let design2 = compile_source("module m2; string s; string t = \"hi\"; endmodule\n")
.expect("valid SV source compiles");
let body2 = design2
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout2 = build_template_layout(&body2).expect("string members lay out");
assert_eq!(layout2.slots.len(), 2);
for slot in &layout2.slots {
assert_eq!(slot.offset.class, SlotClass::Inline);
assert_eq!(slot.offset.bit_width, 64);
}
assert!(matches!(
layout2.slots[0].initial,
Some(FourStateValue::Narrow(0))
));
let hi = u128::from(crate::strings::intern(b"hi"));
assert!(matches!(layout2.slots[1].initial, Some(FourStateValue::Narrow(v)) if v == hi));
let design3 = compile_source("module m3; logic [7:0] ok_signal; endmodule\n")
.expect("valid SV source compiles");
let body3 = design3
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout3 =
build_template_layout(&body3).expect("the ordinary signal alone must still build Ok");
assert_eq!(layout3.slots.len(), 1);
assert_eq!(layout3.slots[0].offset.bit_width, 8);
}
#[test]
fn unpacked_struct_and_union_members_are_flat_stored_vectors() {
let design = compile_source(
"module m; struct { logic a; logic b; } x; \
struct { logic [3:0] l; int i; real r; } y; \
union { logic [7:0] a; logic [7:0] b; } u; endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("unpacked aggregates lay out");
assert_eq!(layout.slots.len(), 3);
let widths: Vec<u32> = layout.slots.iter().map(|s| s.offset.bit_width).collect();
assert_eq!(widths, [2, 4 + 32 + 64, 8]);
let classes: Vec<SlotClass> = layout.slots.iter().map(|s| s.offset.class).collect();
assert_eq!(
classes,
[SlotClass::Inline, SlotClass::Wide, SlotClass::Inline]
);
let mut y_bits = vec![sv_lang::Bit::Zero; 96];
y_bits.extend([sv_lang::Bit::X; 4]);
let expect = [vec![sv_lang::Bit::X; 2], y_bits, vec![sv_lang::Bit::X; 8]];
for (slot, bits) in layout.slots.iter().zip(expect) {
assert_eq!(
format!("{:?}", slot.initial),
format!("{:?}", Some(crate::state::value::bits_to_four_state(&bits))),
);
}
}
#[test]
fn real_members_are_inline_ieee_bit_cells_with_real_initial_values() {
let design = compile_source(
"module m; real r; shortreal sr = 1.0/3.0; realtime rt = 2.5; endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("real members lay out");
assert_eq!(layout.slots.len(), 3);
let expect = [
(64u32, 0u64),
(32, u64::from((1.0f32 / 3.0).to_bits())),
(64, 2.5f64.to_bits()),
];
for (slot, (width, bits)) in layout.slots.iter().zip(expect) {
assert_eq!(slot.offset.class, SlotClass::Inline);
assert_eq!(slot.offset.bit_width, width);
assert!(
matches!(slot.initial, Some(FourStateValue::Narrow(v)) if v == u128::from(bits)),
"expected IEEE bits {bits:#x}, got {:?}",
slot.initial
);
}
}
#[test]
fn non_integral_memory_rows_are_their_stored_bits_and_start_at_zero() {
let design = compile_source(
"module m; real r [3:0]; shortreal s [2]; string t [2]; logic [3:0] l [2];\n\
endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("lays out");
let widths: Vec<u32> = layout.slots.iter().map(|s| s.offset.bit_width).collect();
assert_eq!(widths, [64, 32, 64, 4]);
for slot in &layout.slots[..3] {
assert_eq!(slot.offset.class, SlotClass::Memory);
assert!(slot.initial.is_some(), "2-state rows start at 0");
}
assert!(
layout.slots[3].initial.is_none(),
"4-state rows start all-X"
);
let nested = compile_source("module n; int a [2][3]; endmodule\n").expect("compiles");
let body = nested
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("lays out");
assert_eq!(layout.slots[0].offset.num_rows, 6);
assert!(layout.slots[0].initial.is_some(), "2-state rows start at 0");
}
#[test]
fn memory_initializer_gives_every_row_its_value_lowest_index_first() {
let design = compile_source(
"module m; int d [3:0] = '{1, 2, 3, 4}; logic [3:0] x [2] = '{4'b10x1, 4'h5};\n\
endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("lays out");
match &layout.slots[0].initial {
Some(FourStateValue::Wide { aval, bval }) => {
assert_eq!(aval, &[4, 3, 2, 1]);
assert_eq!(bval, &[0, 0, 0, 0]);
}
other => panic!("expected per-row initial values, got {other:?}"),
}
match &layout.slots[1].initial {
Some(FourStateValue::Wide { aval, bval }) => {
assert_eq!(aval, &[0b1011, 0x5]);
assert_eq!(bval, &[0b0010, 0]);
}
other => panic!("expected per-row initial values, got {other:?}"),
}
let dynamic =
compile_source("module n; int v; int a [2] = '{v, 1}; endmodule\n").expect("compiles");
let body = dynamic
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
build_template_layout(&body).expect("lays out with default rows");
}
#[test]
fn member_wider_than_65535_bits_keeps_its_true_width() {
let design = compile_source("module m; logic [70000:0] huge; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("a 70001-bit member builds");
assert_eq!(layout.slots.len(), 1);
let offset = layout.slots[0].offset;
assert_eq!(offset.bit_width, 70001);
assert_eq!(offset.class, SlotClass::Wide);
assert_eq!(offset.elem_word_count, 70001u32.div_ceil(64));
}
#[test]
fn memory_row_wider_than_65535_bits_keeps_its_true_width() {
let design = compile_source("module m; logic [70000:0] huge_row [3:0]; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("rows of 70001 bits build");
let offset = layout.slots[0].offset;
assert_eq!(offset.bit_width, 70001);
assert_eq!(offset.num_rows, 4);
assert!(offset.row_stride_words >= 70001u32.div_ceil(64));
}
#[test]
fn member_of_65535_bits_builds_with_correct_word_count() {
let design = compile_source("module m; logic [65534:0] w; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let layout = build_template_layout(&body).expect("a 65535-bit member must still build Ok");
assert_eq!(layout.slots.len(), 1);
let offset = layout.slots[0].offset;
assert_eq!(offset.bit_width, 65535);
assert_eq!(offset.class, SlotClass::Wide);
assert_eq!(
offset.elem_word_count,
65535u32.div_ceil(64),
"elem_word_count must be derived from the true 65535-bit width, not a clamped value"
);
}
fn disable_name(d: DefaultDisableDeclarationSyntax) -> String {
d.expr()
.syntax()
.first_token()
.expect("a disable iff expression has at least one token")
.text()
.to_string()
}
fn nested_instance_body<'d>(body: &Symbol<'d>) -> Symbol<'d> {
body.members()
.find(|m| m.kind() == SymbolKind::Instance)
.expect("fixture has exactly one nested module instance")
.instance_body()
.expect("instance has a body")
}
#[test]
fn examples_with_default_module_level_default_disable_resolves() {
let design = compile_source(
"module examples_with_default (input logic a, b, clk, rst, rst1);\n\
default disable iff rst;\n\
endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
let (decl_scope, found) = default_disable_of(&body, &tree)
.expect("no scoping error")
.expect("examples_with_default declares a module-level default");
assert_eq!(disable_name(found), "rst");
assert_eq!(decl_scope.name(), "examples_with_default");
}
#[test]
fn no_default_disable_declaration_resolves_to_none() {
let design = compile_source("module m; bit clk, rst; endmodule\n")
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
assert!(
default_disable_of(&body, &tree)
.expect("no scoping error")
.is_none()
);
}
#[test]
fn nested_module_with_no_own_default_inherits_enclosing_default() {
let design = compile_source(
"module m1;\n\
bit clk, rst1;\n\
default disable iff rst1;\n\
m2 u2();\n\
module m2;\n\
bit rst2;\n\
endmodule\n\
endmodule\n",
)
.expect("valid SV source compiles");
let m1 = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
let m2 = nested_instance_body(&m1);
let (m1_scope, m1_found) = default_disable_of(&m1, &tree)
.expect("no scoping error")
.expect("m1 declares its own default");
assert_eq!(disable_name(m1_found), "rst1");
assert_eq!(m1_scope.name(), "m1");
let (m2_scope, m2_found) = default_disable_of(&m2, &tree)
.expect("no scoping error")
.expect("m2 inherits m1's default -- it must not resolve to None");
assert_eq!(disable_name(m2_found), "rst1");
assert_eq!(
m2_scope.name(),
"m1",
"the inherited default's signals resolve from ITS OWN declaring scope (m1), not m2's"
);
}
#[test]
fn nested_module_with_own_default_overrides_enclosing_default() {
let design = compile_source(
"module m1;\n\
bit clk, rst1;\n\
default disable iff rst1;\n\
m2 u2();\n\
module m2;\n\
bit rst2;\n\
default disable iff rst2;\n\
endmodule\n\
endmodule\n",
)
.expect("valid SV source compiles");
let m1 = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
let m2 = nested_instance_body(&m1);
let (_, m1_found) = default_disable_of(&m1, &tree)
.expect("no scoping error")
.expect("m1 declares its own default");
assert_eq!(disable_name(m1_found), "rst1");
let (m2_scope, m2_found) = default_disable_of(&m2, &tree)
.expect("no scoping error")
.expect("m2 declares its own, overriding default");
assert_eq!(disable_name(m2_found), "rst2");
assert_eq!(m2_scope.name(), "m2");
}
#[test]
fn instantiated_but_not_nested_module_does_not_inherit_default() {
let design = compile_source(
"module m2;\n\
bit rst2;\n\
endmodule\n\
module m1;\n\
bit clk, rst1;\n\
default disable iff rst1;\n\
m2 u2();\n\
endmodule\n",
)
.expect("valid SV source compiles");
let m1 = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
let m2 = nested_instance_body(&m1);
assert!(
default_disable_of(&m2, &tree)
.expect("no scoping error")
.is_none(),
"a separately top-level-declared module must not inherit the instantiating \
module's default -- only textual nesting inherits"
);
}
#[test]
fn duplicate_default_disable_in_same_scope_is_an_error() {
let design = compile_source(
"module m;\n\
bit rst1, rst2;\n\
default disable iff rst1;\n\
default disable iff rst2;\n\
endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
let err = default_disable_of(&body, &tree).expect_err("duplicate must be rejected");
assert!(matches!(err, DefaultScopeError::DuplicateDefaultDisable(name) if name == "m"));
}
#[test]
fn default_clocking_reference_form_resolves_named_block_in_declaring_scope() {
let design = compile_source(
"module processor;\n\
bit clk1, clk2;\n\
clocking busA @(posedge clk1); endclocking\n\
clocking busB @(negedge clk2); endclocking\n\
cpu u_cpu();\n\
module cpu;\n\
default clocking busA;\n\
endmodule\n\
endmodule\n",
)
.expect("valid SV source compiles");
let processor = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
let cpu = nested_instance_body(&processor);
let cb = default_clocking_of(&cpu, &tree)
.expect("no scoping error")
.expect("cpu declares its own default clocking reference");
assert_eq!(cb.name(), "busA");
assert_eq!(cb.kind(), SymbolKind::ClockingBlock);
}
#[test]
fn no_default_clocking_declaration_resolves_to_none() {
let design = compile_source(
"module m; bit clk; clocking cb @(posedge clk); endclocking endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
assert!(
default_clocking_of(&body, &tree)
.expect("no scoping error")
.is_none(),
"a plain, non-default clocking block must not itself count as a default"
);
}
#[test]
fn nested_module_inherits_enclosing_inline_default_clocking() {
let design = compile_source(
"module m1;\n\
bit clk;\n\
default clocking cb @(posedge clk); endclocking\n\
m2 u2();\n\
module m2;\n\
endmodule\n\
endmodule\n",
)
.expect("valid SV source compiles");
let m1 = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
let m2 = nested_instance_body(&m1);
let cb = default_clocking_of(&m2, &tree)
.expect("no scoping error")
.expect("m2 inherits m1's inline default clocking block");
assert_eq!(cb.name(), "cb");
assert_eq!(cb.kind(), SymbolKind::ClockingBlock);
}
#[test]
fn anonymous_inline_default_clocking_resolves_via_syntax_identity() {
let design = compile_source(
"module m1;\n\
bit clk;\n\
default clocking @(posedge clk); endclocking\n\
endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
let cb = default_clocking_of(&body, &tree)
.expect("no scoping error")
.expect("m1 declares its own anonymous default clocking block");
assert_eq!(cb.name(), "");
assert_eq!(cb.kind(), SymbolKind::ClockingBlock);
}
#[test]
fn duplicate_default_clocking_in_same_scope_is_an_error() {
let design = compile_source(
"module m;\n\
bit clk1, clk2;\n\
clocking a @(posedge clk1); endclocking\n\
clocking b @(posedge clk2); endclocking\n\
default clocking a;\n\
default clocking b;\n\
endmodule\n",
)
.expect("valid SV source compiles");
let body = design
.top_instances()
.next()
.expect("one top instance")
.instance_body()
.expect("instance has a body");
let tree = design.syntax_trees().into_iter().next().unwrap();
let err = default_clocking_of(&body, &tree).expect_err("duplicate must be rejected");
assert!(matches!(err, DefaultScopeError::DuplicateDefaultClocking(name) if name == "m"));
}
}