diff --git a/conformance/sv-tests/expected.tsv b/conformance/sv-tests/expected.tsv index aad26f6f9c..6dbe602fca 100644 --- a/conformance/sv-tests/expected.tsv +++ b/conformance/sv-tests/expected.tsv @@ -1451,8 +1451,8 @@ chapter-7/structures/packed/signed.sv 2state pass chapter-7/structures/packed/signed.sv 4state pass chapter-7/structures/packed/unsigned.sv 2state pass chapter-7/structures/packed/unsigned.sv 4state pass -chapter-7/structures/unpacked/basic.sv 2state fail -chapter-7/structures/unpacked/basic.sv 4state fail +chapter-7/structures/unpacked/basic.sv 2state pass +chapter-7/structures/unpacked/basic.sv 4state pass chapter-7/structures/unpacked/default-value.sv 2state fail chapter-7/structures/unpacked/default-value.sv 4state fail chapter-7/unions/packed/basic.sv 2state pass diff --git a/crates/celox-frontend-sv/src/lowering/ff.rs b/crates/celox-frontend-sv/src/lowering/ff.rs index b4688e9cd7..fb0cf6b38c 100644 --- a/crates/celox-frontend-sv/src/lowering/ff.rs +++ b/crates/celox-frontend-sv/src/lowering/ff.rs @@ -3138,11 +3138,24 @@ impl<'p, 'a> Ff<'p, 'a> { &mut self, lhs: &sv::ir::LValue, rhs: &sv::ir::Expr, + ) -> Result<(), sv::AnalyzerError> { + self.assign_value(lhs, rhs, true) + } + + /// Assign `rhs` to `lhs`; `convert` turns an integral value assigned to + /// a string variable into its bytes. The parts of a concatenation target + /// take the bits of the value instead: a part that is a string is a + /// member of an unpacked structure, and its bits are a handle. + fn assign_value( + &mut self, + lhs: &sv::ir::LValue, + rhs: &sv::ir::Expr, + convert: bool, ) -> Result<(), sv::AnalyzerError> { let lhs = self.propagate_lvalue(lhs); let lhs = self.hoist_lvalue(&lhs)?; let converted = match self.m.id(lhs.name()) { - Some(id) if matches!(lhs, sv::ir::LValue::Ident(_)) => { + Some(id) if convert && matches!(lhs, sv::ir::LValue::Ident(_)) => { self.string_conversion(id, rhs.clone()) } _ => rhs.clone(), @@ -3622,7 +3635,7 @@ impl<'p, 'a> Ff<'p, 'a> { lsb: sv::ir::ConstExpr::Literal(lsb.to_string()), signed: false, }; - self.assign(part, &slice)?; + self.assign_value(part, &slice, false)?; } Ok(()) } @@ -4744,7 +4757,10 @@ impl<'p, 'a> Ff<'p, 'a> { })?; let guards = tagged_actual_guards(reference); self.check_tagged_guards(&guards)?; - outputs.push((param.name.clone(), id, lvalues, guards)); + // The members of an unpacked structure take the formal's + // bits, even a single string or handle member. + let concat = matches!(reference, sv::ir::Expr::Concat(_)); + outputs.push((param.name.clone(), id, lvalues, guards, concat)); } } if param.direction.is_read() { @@ -4855,10 +4871,12 @@ impl<'p, 'a> Ff<'p, 'a> { self.jump(return_block); } self.b.switch_to_block(return_block); - for (formal, id, lvalues, guards) in outputs { + for (formal, id, lvalues, guards, concat) in outputs { self.check_tagged_guards(&guards)?; let signed = self.m.var(id).signed; - if let [lvalue] = lvalues.as_slice() { + if let [lvalue] = lvalues.as_slice() + && !concat + { self.assign(lvalue, &sv::ir::Expr::Ident(formal))?; } else { let _ = signed; diff --git a/crates/celox-sv-analyzer/src/analyze.rs b/crates/celox-sv-analyzer/src/analyze.rs index cfb68bee17..699f39e19e 100644 --- a/crates/celox-sv-analyzer/src/analyze.rs +++ b/crates/celox-sv-analyzer/src/analyze.rs @@ -134,6 +134,8 @@ fn analyze_source_impl(source: ast::Source) -> Result { let signals = module .signals() .iter() + // A variable of unpacked structure type is its leaf variables. + .filter(|signal| !signal.is_aggregate()) .map(|signal| { ir::Signal::new( signal.name().to_string(), diff --git a/crates/celox-sv-analyzer/src/ast.rs b/crates/celox-sv-analyzer/src/ast.rs index 1814f0c8f3..8ea57e2326 100644 --- a/crates/celox-sv-analyzer/src/ast.rs +++ b/crates/celox-sv-analyzer/src/ast.rs @@ -185,6 +185,7 @@ mod subroutine_owners; pub(crate) mod timescales; pub(crate) mod type_parameters; mod types; +mod unpacked_structs; pub(crate) mod validation; pub(crate) mod warnings; @@ -1183,12 +1184,20 @@ impl Module { } // A generate block shares its name space with the module's other // declarations (IEEE 1800-2023 27.6, 23.9). + // The members of a variable of unpacked structure type are named + // like declarations of a scope of the variable's name. + let aggregates: HashSet<&str> = signals + .iter() + .filter(|signal| signal.is_aggregate()) + .map(Signal::name) + .collect(); let scope_heads: HashSet<&str> = signals .iter() .filter(|signal| signal.hierarchical.is_none()) .map(Signal::name) .chain(instances.iter().map(Instance::name)) .filter_map(generate_scope_head) + .filter(|head| !aggregates.contains(head)) .collect(); if let Some(conflict) = ports .iter() @@ -1984,6 +1993,12 @@ impl Signal { &self.name } + /// A variable of unpacked structure type, which only names its leaf + /// variables. + pub(crate) fn is_aggregate(&self) -> bool { + self.r#type.unpacked_struct.is_some() + } + pub fn r#type(&self) -> &Type { &self.r#type } @@ -2248,6 +2263,8 @@ pub struct Type { class: Option>, /// The typedef that names an enumeration, struct or union type. nominal: Nominal, + /// The members of an unpacked structure type (IEEE 1800-2023 7.2). + unpacked_struct: Option>, } /// The declaration that names an enumeration, struct or union type, which @@ -2291,6 +2308,7 @@ impl Type { class: None, container: None, nominal: Nominal::default(), + unpacked_struct: None, } } @@ -2307,6 +2325,7 @@ impl Type { signed_element_depth: None, class: None, container: None, + unpacked_struct: None, nominal: Nominal::default(), } } @@ -2315,7 +2334,9 @@ impl Type { /// unless an earlier typedef already named it (`typedef A B` names the /// type of `A`). fn name_declaration(&mut self, name: impl FnOnce() -> String) { - if (self.is_enum || !self.members.is_empty()) && self.nominal.0.is_none() { + if (self.is_enum || !self.members.is_empty() || self.unpacked_struct.is_some()) + && self.nominal.0.is_none() + { self.nominal = Nominal(Some(name().into())); } } @@ -2325,6 +2346,11 @@ impl Type { self.nominal.0.as_deref() } + /// The members of an unpacked structure type. + pub(in crate::ast) fn unpacked_struct(&self) -> Option<&Arc> { + self.unpacked_struct.as_ref() + } + /// The class of an object handle type. pub fn class(&self) -> Option<&str> { self.class.as_deref() @@ -3090,6 +3116,9 @@ struct VariableDimensions { members: Vec, /// See [`Type::signed_element_depth`]. signed_element_depth: Option, + /// A variable of unpacked structure type: the names of its members + /// are variables (see [`unpacked_structs`]). + unpacked_struct: Option>, } type VariablePackedDimensions = HashMap; diff --git a/crates/celox-sv-analyzer/src/ast/bound_patterns.rs b/crates/celox-sv-analyzer/src/ast/bound_patterns.rs index f151c001d4..d9ba65455d 100644 --- a/crates/celox-sv-analyzer/src/ast/bound_patterns.rs +++ b/crates/celox-sv-analyzer/src/ast/bound_patterns.rs @@ -296,6 +296,7 @@ impl BoundPattern { packed: Vec::new(), signed: false, signed_element_depth: None, + unpacked_struct: None, ..shape.clone() }; width diff --git a/crates/celox-sv-analyzer/src/ast/bound_patterns/binding.rs b/crates/celox-sv-analyzer/src/ast/bound_patterns/binding.rs index 4dac52c909..8e1aabe572 100644 --- a/crates/celox-sv-analyzer/src/ast/bound_patterns/binding.rs +++ b/crates/celox-sv-analyzer/src/ast/bound_patterns/binding.rs @@ -60,9 +60,13 @@ pub(in crate::ast) fn prepare_native( if shape.is_some_and(|shape| { shape.unpacked.is_empty() && shape.packed.len() <= 1 - && shape.members.iter().any(|member| { + && (shape.members.iter().any(|member| { member.name() == instances::normalize_identifier(&name.spelling) - }) + }) || shape.unpacked_struct.as_ref().is_some_and(|aggregate| { + aggregate + .member(&instances::normalize_identifier(&name.spelling)) + .is_some() + })) }) || !dims .const_env .contains_key(&instances::normalize_identifier(&name.spelling)) => diff --git a/crates/celox-sv-analyzer/src/ast/classes.rs b/crates/celox-sv-analyzer/src/ast/classes.rs index 61a9dad89f..fd0d179b66 100644 --- a/crates/celox-sv-analyzer/src/ast/classes.rs +++ b/crates/celox-sv-analyzer/src/ast/classes.rs @@ -1167,6 +1167,9 @@ fn field( env: &HashMap, offset: u32, ) -> Converted { + if ty.unpacked_struct().is_some() { + return Err(unsupported("class property of unpacked structure type")); + } let (width, signed, handle) = match ty.kind() { _ if ty.container.is_some() => (HANDLE_WIDTH, false, Some(HandleProperty::Object)), TypeKind::String => (HANDLE_WIDTH, false, Some(HandleProperty::String)), diff --git a/crates/celox-sv-analyzer/src/ast/comb_process.rs b/crates/celox-sv-analyzer/src/ast/comb_process.rs index 4bc6aaf825..9d708ca1e7 100644 --- a/crates/celox-sv-analyzer/src/ast/comb_process.rs +++ b/crates/celox-sv-analyzer/src/ast/comb_process.rs @@ -267,6 +267,21 @@ fn continuous_assignments( tree, dimensions, )?; + // A variable of unpacked structure type: drive its leaves. + if let LValue::Ident(name) = &lhs + && let Some(parts) = unpacked_structs::targets(name, dimensions) + { + unpacked_structs::check_assignment(name, assignment.right, tree, dimensions)?; + return Ok(CombProcess::procedural( + CombProcessKind::ContinuousAssign, + None, + vec![Stmt::AssignConcat { + parts, + rhs, + nonblocking: false, + }], + )); + } let rhs = if matches!( lhs, LValue::Select { diff --git a/crates/celox-sv-analyzer/src/ast/containers.rs b/crates/celox-sv-analyzer/src/ast/containers.rs index 236e11d6af..5c7aadc585 100644 --- a/crates/celox-sv-analyzer/src/ast/containers.rs +++ b/crates/celox-sv-analyzer/src/ast/containers.rs @@ -79,6 +79,9 @@ pub(super) fn with_dimensions( if matches!(base.kind(), TypeKind::Real | TypeKind::Shortreal) { return Err(unsupported("real dynamic container elements")); } + if base.unpacked_struct().is_some() { + return Err(unsupported("dynamic container of unpacked structures")); + } let shape = casts::expr_type_from_type(&tree.state, &base, &context.const_env) .ok_or_else(|| unsupported("dynamic container element type"))?; if shape.width == 0 || shape.width > 64 { diff --git a/crates/celox-sv-analyzer/src/ast/declarations.rs b/crates/celox-sv-analyzer/src/ast/declarations.rs index 9fe31e0b16..bdba2b9e6b 100644 --- a/crates/celox-sv-analyzer/src/ast/declarations.rs +++ b/crates/celox-sv-analyzer/src/ast/declarations.rs @@ -395,7 +395,37 @@ pub(super) fn signals_from_body_item( _ => Vec::new(), }; substitute_signal_local_constants(&mut declared, env); - signals.extend(declared); + for signal in declared { + if signal.r#type.unpacked_struct().is_none() { + signals.push(signal); + continue; + } + if signal.is_net { + return Err(unsupported("net of unpacked structure type")); + } + if !signal.r#type.unpacked_ranges.is_empty() { + return Err(unsupported("array of unpacked structures")); + } + if signal.is_const { + return Err(unsupported("const variable of unpacked structure type")); + } + if signal.package_variable.is_some() || signal.hierarchical.is_some() { + return Err(unsupported("shared variable of unpacked structure type")); + } + // The variable and its structure members only name its leaf + // variables; they are not storage (see `unpacked_structs`). + let leaves = signal + .r#type + .unpacked_struct() + .expect("checked above") + .leaves(); + for (name, r#type) in unpacked_structs::aggregates(&signal.name, &signal.r#type) { + signals.push(Signal::new(name, r#type)); + } + for leaf in leaves { + signals.push(Signal::new(leaf.name(&signal.name), leaf.r#type)); + } + } Ok(()) } diff --git a/crates/celox-sv-analyzer/src/ast/dimensions.rs b/crates/celox-sv-analyzer/src/ast/dimensions.rs index 6a9c799395..eb94a5d85e 100644 --- a/crates/celox-sv-analyzer/src/ast/dimensions.rs +++ b/crates/celox-sv-analyzer/src/ast/dimensions.rs @@ -155,6 +155,7 @@ pub(super) fn packed_dimensions_from_ports_and_signals( ), members: port.r#type().members.clone(), signed_element_depth: port.r#type().signed_element_depth, + unpacked_struct: port.r#type().unpacked_struct.clone(), }, ); } @@ -186,6 +187,7 @@ pub(super) fn packed_dimensions_from_ports_and_signals( ), members: signal.r#type().members.clone(), signed_element_depth: signal.r#type().signed_element_depth, + unpacked_struct: signal.r#type().unpacked_struct.clone(), }, ); } @@ -377,6 +379,7 @@ fn extend_variable_dimensions_from_const_env( signed_element_depth: None, class: None, nominal: Nominal::default(), + unpacked_struct: None, }; let mut shape = procedural::dimensions_from_type(&ty); if let Some(existing) = context.get(name) { @@ -565,6 +568,7 @@ pub(super) fn parameter_packed_dimensions(parameters: &[Parameter]) -> VariableP is_2state: parameter.declared_is_2state, members: parameter.members.clone(), signed_element_depth: parameter.signed_element_depth, + unpacked_struct: None, }, ) }) diff --git a/crates/celox-sv-analyzer/src/ast/expression_binding/direct.rs b/crates/celox-sv-analyzer/src/ast/expression_binding/direct.rs index 847b186a6f..54e4c83558 100644 --- a/crates/celox-sv-analyzer/src/ast/expression_binding/direct.rs +++ b/crates/celox-sv-analyzer/src/ast/expression_binding/direct.rs @@ -280,6 +280,24 @@ pub(in crate::ast) fn bind_value( match job { Job::Enter(id, target, scope) => { let dimensions = scope.as_deref().unwrap_or(dimensions); + unpacked_structs::check_operands(id, native, tree, dimensions)?; + // An argument, result or other value received as an unpacked + // structure is a pattern or a value of its type (6.22.3). + if let Some(aggregate) = target + .as_deref() + .filter(|shape| shape.unpacked.is_empty()) + .and_then(|shape| shape.unpacked_struct.as_deref()) + { + unpacked_structs::check_value(aggregate, id, native, tree, dimensions)?; + } else if target + .as_deref() + .is_some_and(|shape| shape.unpacked.is_empty() && shape.container.is_none()) + && unpacked_structs::native_struct(id, native, tree, dimensions)?.is_some() + { + return Err(unsupported( + "unpacked structure value received by a destination that is not of its type", + )); + } if let Some(shape) = target.as_deref() && shape .members @@ -463,6 +481,10 @@ pub(in crate::ast) fn bind_value( { return Err(unsupported(format!("`{name}`"))); } + if let Some(value) = unpacked_structs::whole(&name, dimensions) { + values.push(value); + continue; + } if let Some(value) = objects::unqualified_property(tree, dimensions, &name)? { values.push(value); @@ -1592,6 +1614,9 @@ fn path( } } parts.reverse(); + if qualified.is_none() { + unpacked_structs::collapse(&mut parts, dimensions); + } if qualified.is_none() && let Some(name) = interface_binding::unindexed_parameter(&parts, tree, dimensions, authored) @@ -1720,6 +1745,11 @@ fn selected_value( &selected_lvalue(root, native, tree, dimensions, false)?, dimensions, ); + if let Expr::Ident(name) = &value + && let Some(value) = unpacked_structs::whole(name, dimensions) + { + return Ok(value); + } let guards = member_context(root, native, tree, dimensions)? .map(|(guards, _)| guards) .unwrap_or_default(); diff --git a/crates/celox-sv-analyzer/src/ast/expression_binding/objects.rs b/crates/celox-sv-analyzer/src/ast/expression_binding/objects.rs index 3103b79bf8..f436e4d63e 100644 --- a/crates/celox-sv-analyzer/src/ast/expression_binding/objects.rs +++ b/crates/celox-sv-analyzer/src/ast/expression_binding/objects.rs @@ -42,6 +42,10 @@ pub(in crate::ast) fn native_class( { return Some(super::super::processes::CLASS.to_owned()); } + // A class handle member of an unpacked structure variable. + if let Some(name) = unpacked_structs::native_member(ungroup(id, native), native, dimensions) { + return dimensions.get(&name)?.class.clone(); + } let expression = native.expression(ungroup(id, native)); if let ExprKind::Select { expression, diff --git a/crates/celox-sv-analyzer/src/ast/expression_binding/references.rs b/crates/celox-sv-analyzer/src/ast/expression_binding/references.rs index 8607172043..6c41949185 100644 --- a/crates/celox-sv-analyzer/src/ast/expression_binding/references.rs +++ b/crates/celox-sv-analyzer/src/ast/expression_binding/references.rs @@ -134,6 +134,15 @@ pub(super) fn check_actual( .subroutine_param_shapes .get(name) .and_then(|shapes| shapes.get(position)); + // The members of a structure are separate variables, which one reference + // cannot alias. + if formal.is_some_and(|formal| formal.unpacked_struct.is_some()) + || shape.unpacked_struct.is_some() + { + return Err(unsupported(format!( + "ref argument of `{name}` of unpacked structure type" + ))); + } // Bounded and unbounded queues are equivalent (6.22.2), but a formal's // operations would apply its own bound to the actual. if let (Some(formal), Some(actual)) = ( diff --git a/crates/celox-sv-analyzer/src/ast/expression_binding/strings.rs b/crates/celox-sv-analyzer/src/ast/expression_binding/strings.rs index ebde30f4ea..0fadf8370f 100644 --- a/crates/celox-sv-analyzer/src/ast/expression_binding/strings.rs +++ b/crates/celox-sv-analyzer/src/ast/expression_binding/strings.rs @@ -179,6 +179,15 @@ pub(in crate::ast) fn native_string( } // `e.name` without parentheses (6.19.5.6, 6.19.5.7). ExprKind::Member { .. } if enum_name(tree, id, native, dimensions) => true, + // A string member of an unpacked structure variable. + ExprKind::Member { .. } + if unpacked_structs::native_member(ungroup(id, native), native, dimensions) + .is_some() => + { + unpacked_structs::native_member(ungroup(id, native), native, dimensions) + .and_then(|name| dimensions.get(&name)) + .is_some_and(|ty| ty.string) + } // A string property of a class object. _ => objects::property(tree, id, native, dimensions) .ok() diff --git a/crates/celox-sv-analyzer/src/ast/functions.rs b/crates/celox-sv-analyzer/src/ast/functions.rs index 0c0813c79a..a6d1e25580 100644 --- a/crates/celox-sv-analyzer/src/ast/functions.rs +++ b/crates/celox-sv-analyzer/src/ast/functions.rs @@ -263,6 +263,7 @@ fn native_signature( !ty.members.is_empty() || !ty.unpacked_ranges.is_empty() || ty.packed_ranges.len() > 1 + || ty.unpacked_struct.is_some() }) .and_then(|ty| { ty.pattern_target(&const_functions::Environment { diff --git a/crates/celox-sv-analyzer/src/ast/generate/memo/sites.rs b/crates/celox-sv-analyzer/src/ast/generate/memo/sites.rs index f44b20d91d..0dadfcf2e1 100644 --- a/crates/celox-sv-analyzer/src/ast/generate/memo/sites.rs +++ b/crates/celox-sv-analyzer/src/ast/generate/memo/sites.rs @@ -161,6 +161,7 @@ impl CallSites for Type { class: _, container: _, nominal: _, + unpacked_struct: _, } = self; packed_ranges.call_sites(visit); unpacked_ranges.call_sites(visit); @@ -201,6 +202,7 @@ impl CallSites for VariableDimensions { is_2state: _, members, signed_element_depth: _, + unpacked_struct: _, } = self; packed.call_sites(visit); unpacked.call_sites(visit); diff --git a/crates/celox-sv-analyzer/src/ast/packed_structs.rs b/crates/celox-sv-analyzer/src/ast/packed_structs.rs index 9d89177852..b7de43e09e 100644 --- a/crates/celox-sv-analyzer/src/ast/packed_structs.rs +++ b/crates/celox-sv-analyzer/src/ast/packed_structs.rs @@ -11,7 +11,7 @@ pub(in crate::ast) use tagged::{ #[cfg(test)] mod tests; -pub(super) use layout::{AggregateKind, native_type}; +pub(super) use layout::{AggregateKind, capture_member_type, native_type}; pub(super) use selects::{select_member, whole_member}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/celox-sv-analyzer/src/ast/packed_structs/layout.rs b/crates/celox-sv-analyzer/src/ast/packed_structs/layout.rs index 2409ee9756..f2f72a5e80 100644 --- a/crates/celox-sv-analyzer/src/ast/packed_structs/layout.rs +++ b/crates/celox-sv-analyzer/src/ast/packed_structs/layout.rs @@ -17,7 +17,10 @@ pub(in crate::ast) fn capture_member_type( mut ty: Type, env: &HashMap, ) -> Option { - if !ty.unpacked_ranges.is_empty() || matches!(ty.kind, TypeKind::Real | TypeKind::Shortreal) { + if !ty.unpacked_ranges.is_empty() + || ty.unpacked_struct.is_some() + || matches!(ty.kind, TypeKind::Real | TypeKind::Shortreal) + { return None; } for range in &mut ty.packed_ranges { diff --git a/crates/celox-sv-analyzer/src/ast/packed_structs/selects.rs b/crates/celox-sv-analyzer/src/ast/packed_structs/selects.rs index 3c033e560d..afa4c904d6 100644 --- a/crates/celox-sv-analyzer/src/ast/packed_structs/selects.rs +++ b/crates/celox-sv-analyzer/src/ast/packed_structs/selects.rs @@ -87,6 +87,7 @@ fn member_dimensions(name: &str, r#type: &Type, dimensions: &PackedDimensions) - is_2state: r#type.kind == TypeKind::Bit, members: r#type.members.clone(), signed_element_depth: r#type.signed_element_depth, + unpacked_struct: None, }, ); result diff --git a/crates/celox-sv-analyzer/src/ast/packed_structs/tests.rs b/crates/celox-sv-analyzer/src/ast/packed_structs/tests.rs index 6c745e2f93..5611d331d7 100644 --- a/crates/celox-sv-analyzer/src/ast/packed_structs/tests.rs +++ b/crates/celox-sv-analyzer/src/ast/packed_structs/tests.rs @@ -47,7 +47,7 @@ fn preserves_layout_signedness_and_state_kind() { #[test] fn rejects_invalid_or_unsupported_struct_shapes() { for body in [ - "struct { logic a; } value;", + "union { logic a; } value;", "union packed { logic a; logic [1:0] b; } value;", "union soft { real a; bit [63:0] b; } value;", "union soft { logic a[2]; logic b; } value;", diff --git a/crates/celox-sv-analyzer/src/ast/parameters.rs b/crates/celox-sv-analyzer/src/ast/parameters.rs index 7f51980793..98b1501288 100644 --- a/crates/celox-sv-analyzer/src/ast/parameters.rs +++ b/crates/celox-sv-analyzer/src/ast/parameters.rs @@ -375,6 +375,12 @@ pub(super) fn parameters_from_native_with_environment( && declared_alias .as_ref() .is_none_or(|alias| alias.members.is_empty() && alias.unpacked_ranges.is_empty()); + if declared_alias + .as_ref() + .is_some_and(|alias| alias.unpacked_struct().is_some()) + { + return Err(unsupported("parameter of unpacked structure type")); + } let parameter_members = declared_alias .as_ref() .map(|ty| ty.members.clone()) diff --git a/crates/celox-sv-analyzer/src/ast/parameters/skipped.rs b/crates/celox-sv-analyzer/src/ast/parameters/skipped.rs index f05a05e2eb..84e9584282 100644 --- a/crates/celox-sv-analyzer/src/ast/parameters/skipped.rs +++ b/crates/celox-sv-analyzer/src/ast/parameters/skipped.rs @@ -527,6 +527,7 @@ fn pattern_items( signed: false, is_2state: shape.is_2state, signed_element_depth: None, + unpacked_struct: None, }, ) }; diff --git a/crates/celox-sv-analyzer/src/ast/patterns/binding.rs b/crates/celox-sv-analyzer/src/ast/patterns/binding.rs index 0e5444bf9a..8077603163 100644 --- a/crates/celox-sv-analyzer/src/ast/patterns/binding.rs +++ b/crates/celox-sv-analyzer/src/ast/patterns/binding.rs @@ -102,6 +102,15 @@ fn select<'a>( shape: Shape<'a>, dims: &PackedDimensions, ) -> Converted<(Vec>, bool, bool)> { + if let Some(aggregate) = shape.unpacked_struct.clone() + && shape.unpacked.is_empty() + { + return Ok(( + unpacked_struct_items(&aggregate, kind, bindings)?, + false, + true, + )); + } if let Some((dim, rest)) = shape.unpacked.split_first() { let (left, right, count) = dimension_bounds(state, &dim.left, &dim.right, dims)?; let sub = Shape::Owned(Rc::new(VariableDimensions { @@ -153,6 +162,7 @@ fn select<'a>( .signed_element_depth .and_then(|depth| depth.checked_sub(1)) .filter(|depth| *depth > 0), + unpacked_struct: None, })); let items = indexed_items(kind, bindings, count, |index| { (left.min(right)..=left.max(right)) @@ -262,6 +272,7 @@ fn select<'a>( is_2state: shape.is_2state, members: Vec::new(), signed_element_depth: None, + unpacked_struct: None, })); let items = indexed_items(kind, bindings, width, |index| { usize::try_from(index) @@ -415,6 +426,69 @@ pub(in crate::ast) fn bind_native( if check { check_native_array(&tree.state, id, native, &shape, dims)?; } + // A typed pattern of a member must name the member's + // structure type (6.22.3). + if pattern && let Some(aggregate) = shape.unpacked_struct.as_deref() { + unpacked_structs::check_value(aggregate, id, native, tree, dims)?; + } + if let Some(aggregate) = shape.unpacked_struct.clone() + && !pattern + { + // A default that is not a structure of the member's type + // applies to each of its members (IEEE 1800-2023 10.9.2). + if default && unpacked_structs::native_struct(id, native, tree, dims)?.is_none() + { + let members = aggregate.members(); + jobs.push(Job::Finish { + count: members.len(), + reverse: false, + keep: true, + }); + jobs.extend(members.iter().rev().map(|member| { + Job::Value(Selected { + value: id, + shape: Shape::Owned(Rc::new(procedural::dimensions_from_type( + &member.r#type, + ))), + default: true, + check: false, + }) + })); + continue; + } + unpacked_structs::check_value(&aggregate, id, native, tree, dims)?; + values.push(expression_binding::bind_value( + id, native, tree, dims, None, + )?); + continue; + } + if let Some(class) = shape.class.as_deref() + && !pattern + { + if let Some(call) = + expression_binding::object_constructor(tree, id, native, dims, class)? + { + values.push(call); + continue; + } + expression_binding::check_handle_assignment( + tree, + id, + native, + dims, + class, + || "assignment pattern member".into(), + )?; + } + if (shape.string || shape.class.is_some()) && !pattern { + let value = expression_binding::bind_value(id, native, tree, dims, None)?; + values.push(if shape.string { + expression_binding::string_operand(value, dims) + } else { + value + }); + continue; + } if pattern { let typed = typed_shape(id, tree, dims)?; jobs.push(Job::Pattern( @@ -475,6 +549,74 @@ pub(in crate::ast) fn bind_native( } values.pop().ok_or_else(mismatch) } +/// The member values of an assignment pattern for an unpacked structure +/// (IEEE 1800-2023 10.9.2), first declared first. +fn unpacked_struct_items<'a>( + aggregate: &unpacked_structs::UnpackedStruct, + kind: NativeKind, + bindings: Vec, +) -> Converted>> { + let members = aggregate.members(); + let values = match kind { + NativeKind::List => { + if bindings.len() != members.len() { + return Err(unsupported(format!( + "assignment pattern with {} items for {} structure members", + bindings.len(), + members.len() + ))); + } + bindings.into_iter().map(|value| (value, false)).collect() + } + NativeKind::Keyed(keys) => { + let mut named = Vec::new(); + let mut default = None; + for (key, value) in keys.into_iter().zip(bindings) { + match key { + Key::Member(name) if aggregate.member(&name).is_some() => { + named.push((name, value)) + } + Key::Member(name) => { + return Err(unsupported(format!( + "assignment pattern key `{name}` that is not a structure member" + ))); + } + Key::Default => default = Some(value), + Key::Index(_) | Key::Name { .. } => { + return Err(unsupported("structure assignment pattern form")); + } + } + } + members + .iter() + .map(|member| { + named + .iter() + .find(|(name, _)| *name == member.name) + .map(|(_, value)| (*value, false)) + .or(default.map(|value| (value, true))) + .ok_or_else(|| { + unsupported(format!( + "assignment pattern without a value for member `{}`", + member.name + )) + }) + }) + .collect::>>()? + } + _ => return Err(unsupported("structure assignment pattern form")), + }; + Ok(members + .iter() + .zip(values) + .map(|(member, (value, default))| Selected { + value, + shape: Shape::Owned(Rc::new(procedural::dimensions_from_type(&member.r#type))), + default, + check: false, + }) + .collect()) +} fn native_rank( mut id: ExprId, native: &celox_sv_syntax::Declarations, diff --git a/crates/celox-sv-analyzer/src/ast/procedural.rs b/crates/celox-sv-analyzer/src/ast/procedural.rs index 9506260718..cb235b2722 100644 --- a/crates/celox-sv-analyzer/src/ast/procedural.rs +++ b/crates/celox-sv-analyzer/src/ast/procedural.rs @@ -100,6 +100,7 @@ pub(super) fn dimensions_from_type(r#type: &Type) -> VariableDimensions { ), members: r#type.members.clone(), signed_element_depth: r#type.signed_element_depth, + unpacked_struct: r#type.unpacked_struct.clone(), } } @@ -121,6 +122,60 @@ pub(super) fn scoped_type(mut r#type: Type, const_env: &HashMap) - r#type } +/// Run `epilogue` at each `return` in `body`. A returned value is first +/// stored in the result variable `result`, so the epilogue sees the effects +/// of evaluating it. +fn before_returns(body: &mut Vec, epilogue: &[Stmt], result: Option<&str>) { + let mut pending = vec![body]; + while let Some(stmts) = pending.pop() { + let mut index = 0; + while index < stmts.len() { + if let Stmt::Return(value) = &mut stmts[index] { + let mut exit = Vec::new(); + if let (Some(value), Some(result)) = (value.take(), result) { + exit.push(Stmt::Assign { + lhs: LValue::Ident(result.to_owned()), + rhs: value, + nonblocking: false, + }); + stmts[index] = Stmt::Return(Some(Expr::Ident(result.to_owned()))); + } + exit.extend(epilogue.iter().cloned()); + let count = exit.len(); + stmts.splice(index..index, exit); + index += count + 1; + continue; + } + index += 1; + } + for stmt in stmts.iter_mut() { + match stmt { + Stmt::If { + then_body, + else_body, + .. + } => { + pending.push(then_body); + pending.push(else_body); + } + Stmt::Case { items, default, .. } => { + pending.extend(items.iter_mut().map(|item| &mut item.body)); + pending.extend(default.as_mut()); + } + Stmt::Loop { + init, step, body, .. + } => { + pending.push(init); + pending.push(step); + pending.push(body); + } + // A `return` in a forked branch ends only that branch. + _ => {} + } + } + } +} + /// The `int` type of a loop variable declared by `foreach`. pub(super) fn int_type() -> Type { Type { @@ -139,6 +194,7 @@ pub(super) fn int_type() -> Type { signed_element_depth: None, class: None, nominal: Nominal::default(), + unpacked_struct: None, } } @@ -554,6 +610,9 @@ impl<'s, 't, 'a> BodyBuilder<'s, 't, 'a> { if let Some(stmt) = self.property_assignment(op, nonblocking, left, right)? { return Ok(stmt); } + if let Some(stmt) = self.aggregate_assignment(op, nonblocking, left, right)? { + return Ok(stmt); + } if op != "=" { self.reject_tagged_integral_target(left)?; } @@ -624,6 +683,51 @@ impl<'s, 't, 'a> BodyBuilder<'s, 't, 'a> { }) } + /// An assignment to a whole variable or member of unpacked structure + /// type, which writes each of its leaf variables (IEEE 1800-2023 7.2). + fn aggregate_assignment( + &self, + op: &str, + nonblocking: bool, + left: celox_sv_syntax::ExprId, + right: celox_sv_syntax::ExprId, + ) -> Result, AnalyzerError> { + let Ok(written) = expression_binding::variable_lvalue( + left, + self.tree.declarations(), + self.tree, + &self.dims, + ) else { + return Ok(None); + }; + let LValue::Ident(name) = &written else { + return Ok(None); + }; + let Some(mut parts) = unpacked_structs::targets(name, &self.dims) else { + return Ok(None); + }; + if op != "=" { + return Err(unsupported(format!( + "operator `{op}` on an unpacked structure" + ))); + } + let mut rhs = expression_binding::expression( + right, + self.tree.declarations(), + &written, + self.tree, + &self.dims, + )?; + unpacked_structs::check_assignment(name, right, self.tree, &self.dims)?; + parts.iter_mut().for_each(|part| self.rename_lvalue(part)); + self.rename_expr(&mut rhs); + Ok(Some(Stmt::AssignConcat { + parts, + rhs, + nonblocking, + })) + } + /// `str[i] = c`, which replaces one character like `str.putc(i, c)` /// (IEEE 1800-2023 6.16.2). fn string_character_assignment( @@ -1226,17 +1330,28 @@ impl<'s, 't, 'a> BodyBuilder<'s, 't, 'a> { } None => None, }; + let automatic = match native.lifetime.as_deref() { + Some("automatic") => true, + Some("static") => false, + _ => self.automatic, + }; + if signal.r#type().unpacked_struct().is_some() { + let explicit = native.lifetime.as_deref() == Some("static"); + stmts.extend(self.aggregate_local( + &signal, + assignment.value, + init, + automatic, + explicit, + )?); + continue; + } let name = self.declare(signal.name(), signal.r#type().clone()); if signal.is_const { let mut shape = self.dims.get(signal.name()).unwrap().clone(); shape.randomizable = false; self.dims.insert(signal.name().to_string(), shape); } - let automatic = match native.lifetime.as_deref() { - Some("automatic") => true, - Some("static") => false, - _ => self.automatic, - }; if !automatic { if native.lifetime.as_deref() == Some("static") { self.state.explicit_statics.insert(name.clone()); @@ -1249,6 +1364,124 @@ impl<'s, 't, 'a> BodyBuilder<'s, 't, 'a> { Ok(stmts) } + /// A local of unpacked structure type: its leaf variables, then the + /// assignment of its initializer. + fn aggregate_local( + &mut self, + signal: &crate::ast::Signal, + root: Option, + init: Option, + automatic: bool, + explicit: bool, + ) -> Result, AnalyzerError> { + if signal.is_const { + return Err(unsupported("const variable of unpacked structure type")); + } + if let Some(root) = root { + let aggregate = signal + .r#type() + .unpacked_struct() + .expect("an unpacked structure local"); + unpacked_structs::check_value( + aggregate, + root, + self.tree.declarations(), + self.tree, + &self.dims, + )?; + } + let leaves = self.declare_aggregate(signal.name(), signal.r#type())?; + // A static variable is initialized once, not on each entry: each leaf + // keeps its part of the initializer, which is hoisted with it. + let aggregate = signal + .r#type() + .unpacked_struct() + .expect("an unpacked structure local") + .clone(); + let mut init = init; + let mut parts = match init.take() { + Some(value) + if !automatic && unpacked_structs::leaf_parts(&aggregate, &value).is_some() => + { + unpacked_structs::leaf_parts(&aggregate, &value) + .expect("checked by the guard") + .into_iter() + .map(Some) + .collect() + } + Some(_) if !automatic => { + return Err(unsupported( + "initializer of a static variable of unpacked structure type that is not a pattern or variable", + )); + } + other => { + init = other; + vec![None; leaves.len()] + } + }; + let mut stmts = Vec::new(); + for ((name, r#type), part) in leaves.iter().zip(parts.drain(..)) { + if !automatic { + // An explicitly static member of a repeating process is + // initialized before simulation, like a scalar. + if explicit { + self.state.explicit_statics.insert(name.clone()); + } + let r#type = scoped_type(r#type.clone(), &self.dims.const_env); + self.state.statics.push((name.clone(), r#type)); + } + stmts.push(Stmt::Local { + name: name.clone(), + init: part, + }); + } + if let Some(rhs) = init { + stmts.push(Stmt::AssignConcat { + parts: leaves + .into_iter() + .map(|(name, _)| LValue::Ident(name)) + .collect(), + rhs, + nonblocking: false, + }); + } + Ok(stmts) + } + + /// Declare a variable of unpacked structure type: hide the names of it + /// and its members, then declare its leaf variables. Returns their unique + /// names and types, first declared first. + pub(super) fn declare_aggregate( + &mut self, + source: &str, + r#type: &Type, + ) -> Result, AnalyzerError> { + if !r#type.unpacked_ranges.is_empty() { + return Err(unsupported("array of unpacked structures")); + } + for (name, ty) in unpacked_structs::aggregates(source, r#type) { + let entry = self.take_binding(&name, None, 0); + self.dims.insert(name.clone(), dimensions_from_type(&ty)); + self.scopes + .last_mut() + .expect("a body always has a scope") + .entries + .push(entry); + } + let aggregate = r#type + .unpacked_struct() + .expect("an unpacked structure type") + .clone(); + Ok(aggregate + .leaves() + .into_iter() + .map(|leaf| { + let name = self.declare(&leaf.name(source), leaf.r#type.clone()); + (name, leaf.r#type) + }) + .collect()) + } + /// Make the parameters of a declaration in the body known as constants. fn local_parameters( &mut self, @@ -2556,7 +2789,8 @@ pub(super) fn lower_subroutine( .map(|(_, _, ty, default)| { default .map(|root| { - let shape = ty.tagged_identity().map(|_| dimensions_from_type(ty)); + let shape = (ty.tagged_identity().is_some() || ty.unpacked_struct().is_some()) + .then(|| dimensions_from_type(ty)); let mut value = expression_binding::bind_value( root, tree.declarations(), @@ -2587,8 +2821,12 @@ pub(super) fn lower_subroutine( .map(|_| None) .chain(defaults) .collect::>(); + let mut aggregate_formals = Vec::new(); for ((source, direction, r#type, _), default) in this.into_iter().chain(params).zip(defaults) { let name = builder.declare(&source, r#type.clone()); + if r#type.unpacked_struct().is_some() { + aggregate_formals.push((lowered_params.len(), r#type.clone())); + } formal_types.insert(name.clone(), scoped_type(r#type.clone(), declaration_env)); lowered_params.push(SubroutineParam { name, @@ -2612,8 +2850,72 @@ pub(super) fn lower_subroutine( } builder.return_shape = return_type .as_ref() - .filter(|ty| ty.tagged_identity().is_some()) + .filter(|ty| ty.tagged_identity().is_some() || ty.unpacked_struct().is_some()) .map(dimensions_from_type); + // An argument of unpacked structure type is passed as its flat value; + // the body works on its members (see `unpacked_structs`). + let mut aggregate_prologue = Vec::new(); + let mut aggregate_epilogue = Vec::new(); + // A result of unpacked structure type assigned through the function's + // name is assembled from its members when the body ends without + // `return` (IEEE 1800-2023 13.4.1). + let mut result_leaves = None; + if dispatch.is_none() + && let (Some(result), Some(r#type)) = (&return_var, &return_type) + && r#type.unpacked_struct().is_some() + && !constructor + { + let leaves = builder + .declare_aggregate(&name, r#type)? + .into_iter() + .map(|(leaf, _)| leaf) + .collect::>(); + // An automatic result starts each call at its default. + if builder.automatic { + aggregate_prologue.extend(leaves.iter().map(|leaf| Stmt::Local { + name: leaf.clone(), + init: None, + })); + } + result_leaves = Some((result.clone(), leaves)); + } + if dispatch.is_none() { + for (index, r#type) in &aggregate_formals { + let param = &lowered_params[*index]; + let (name, source, direction) = ( + param.name.clone(), + param.source_name.clone(), + param.direction, + ); + let leaves = builder + .declare_aggregate(&source, r#type)? + .into_iter() + .map(|(leaf, _)| leaf) + .collect::>(); + if direction.is_read() { + aggregate_prologue.push(Stmt::AssignConcat { + parts: leaves.iter().cloned().map(LValue::Ident).collect(), + rhs: Expr::Ident(name.clone()), + nonblocking: false, + }); + } else if builder.automatic { + // An automatic output starts each call at its default. + aggregate_prologue.extend(leaves.iter().map(|leaf| Stmt::Local { + name: leaf.clone(), + init: None, + })); + } + if direction.is_written() { + aggregate_epilogue.push(Stmt::Assign { + lhs: LValue::Ident(name), + rhs: Expr::Concat(leaves.into_iter().map(Expr::Ident).collect()), + nonblocking: false, + }); + } + } + } + // The members of formals and of the result have the subroutine's + // lifetime, like the formals themselves. let parameter_locals_end = builder.state.locals.len(); let mut items = native.items.clone(); let mut prologue = Vec::new(); @@ -2649,8 +2951,24 @@ pub(super) fn lower_subroutine( &lowered_params, return_var.as_deref(), )), - None => body.extend(builder.native_body(&items, &native.authored_statements)?), + None => { + body.extend(aggregate_prologue); + body.extend(builder.native_body(&items, &native.authored_statements)?); + } + } + if !aggregate_epilogue.is_empty() { + // Copy the members back on every way out of the body, after the + // value of a `return` is taken. + before_returns(&mut body, &aggregate_epilogue, return_var.as_deref()); } + if let Some((result, leaves)) = result_leaves { + body.push(Stmt::Assign { + lhs: LValue::Ident(result), + rhs: Expr::Concat(leaves.into_iter().map(Expr::Ident).collect()), + nonblocking: false, + }); + } + body.extend(aggregate_epilogue); builder.pop_scope(); if class.is_some() { builder.pop_scope(); @@ -3375,10 +3693,24 @@ pub(super) fn initial_processes_from_scope( tree, item_dimensions, )?; - let mut stmt = Stmt::Assign { - lhs, - rhs, - nonblocking: false, + let LValue::Ident(name) = lhs else { + unreachable!("an initializer target is a variable") + }; + // A variable of unpacked structure type initializes its leaves. + let mut stmt = match unpacked_structs::targets(&name, item_dimensions) { + Some(parts) => { + unpacked_structs::check_assignment(&name, native, tree, item_dimensions)?; + Stmt::AssignConcat { + parts, + rhs, + nonblocking: false, + } + } + None => Stmt::Assign { + lhs: LValue::Ident(name), + rhs, + nonblocking: false, + }, }; // The parameters of the class are its own constants. if scoped.is_some() { diff --git a/crates/celox-sv-analyzer/src/ast/type_binding.rs b/crates/celox-sv-analyzer/src/ast/type_binding.rs index 371ab227f0..e354b7d1df 100644 --- a/crates/celox-sv-analyzer/src/ast/type_binding.rs +++ b/crates/celox-sv-analyzer/src/ast/type_binding.rs @@ -56,7 +56,11 @@ pub(super) fn port_type( let Some(id) = port.data_type else { return Ok(None); }; - bind(id, tree, &context) + let ty = bind(id, tree, &context)?; + if ty.as_ref().is_some_and(|ty| ty.unpacked_struct().is_some()) { + return Err(unsupported("port of unpacked structure type")); + } + Ok(ty) } /// Formal/return implicit types use scalar logic rather than an unsized local. diff --git a/crates/celox-sv-analyzer/src/ast/type_binding/binding.rs b/crates/celox-sv-analyzer/src/ast/type_binding/binding.rs index 3a451b401e..c19067bbbb 100644 --- a/crates/celox-sv-analyzer/src/ast/type_binding/binding.rs +++ b/crates/celox-sv-analyzer/src/ast/type_binding/binding.rs @@ -88,6 +88,7 @@ fn bind_in_mode( signed: bool, names: Vec, ranges: Vec, + packed: bool, }, } let mut jobs = vec![Job::Enter(root)]; @@ -281,7 +282,8 @@ fn bind_in_mode( packed, fields, } => { - if !packed && !soft { + // Unpacked unions are not supported (IEEE 1800-2023 7.3). + if !packed && !soft && (*union || *tagged) { return Ok(None); } let mut names = Vec::new(); @@ -325,6 +327,7 @@ fn bind_in_mode( signed: data.signed.unwrap_or(false), names, ranges, + packed: *packed || *soft, }); jobs.extend(fields.iter().zip(voids).rev().map(|(field, is_void)| { if is_void { @@ -383,11 +386,28 @@ fn bind_in_mode( signed, names, ranges, + packed, } => { let start = values .len() .checked_sub(names.len()) .ok_or_else(|| unsupported("aggregate fields"))?; + if !packed { + if !ranges.is_empty() { + return Ok(None); + } + let fields = names.into_iter().zip(values.split_off(start)).collect(); + let Some(ty) = unpacked_structs::native_type( + &tree.state, + owner, + fields, + &context.const_env, + ) else { + return Ok(None); + }; + values.push(ty); + continue; + } let fields = names .into_iter() .zip(values.split_off(start)) diff --git a/crates/celox-sv-analyzer/src/ast/unpacked_structs.rs b/crates/celox-sv-analyzer/src/ast/unpacked_structs.rs new file mode 100644 index 0000000000..0b7bb83d67 --- /dev/null +++ b/crates/celox-sv-analyzer/src/ast/unpacked_structs.rs @@ -0,0 +1,439 @@ +//! Unpacked structures (IEEE 1800-2023 7.2). +//! +//! An unpacked structure is not storage of its own. A variable of such a type +//! is a set of *leaf* variables, one for each member that is not itself an +//! unpacked structure, named by its member path (`s.a.b`). Each leaf keeps the +//! type of its member, so a `string` or class handle member is an ordinary +//! string or handle variable. +//! +//! A value of the whole structure is the concatenation of its leaves, the +//! first declared member in the most significant bits. Such a flat value only +//! lives inside one evaluation, so the handles it carries need no root of +//! their own. + +use super::*; +use celox_sv_syntax::{ExprId, ExprKind}; + +/// The members of an unpacked structure type. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(in crate::ast) struct UnpackedStruct { + /// Nominal identity (IEEE 1800-2023 6.22.1): the declaration. + owner: Arc, + members: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(in crate::ast) struct Member { + pub(in crate::ast) name: String, + pub(in crate::ast) r#type: Type, + /// Bits of the member in a flat value. + pub(in crate::ast) width: usize, +} + +/// A member that is not an unpacked structure, with its place in a flat value. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(in crate::ast) struct Leaf { + /// Member names from the structure down to the leaf. + pub(in crate::ast) path: Vec, + pub(in crate::ast) r#type: Type, + /// Least significant bit in a flat value. + pub(in crate::ast) offset: usize, + pub(in crate::ast) width: usize, +} + +impl Leaf { + /// The name of this leaf of the variable `base`. + pub(in crate::ast) fn name(&self, base: &str) -> String { + leaf_name(base, &self.path) + } +} + +/// The leaf or member variable `path` of `base`. +pub(in crate::ast) fn leaf_name(base: &str, path: &[String]) -> String { + let mut name = base.to_owned(); + for part in path { + name.push('.'); + name.push_str(part); + } + name +} + +impl UnpackedStruct { + pub(in crate::ast) fn same_type(&self, other: &Self) -> bool { + self.owner == other.owner + } + + pub(in crate::ast) fn members(&self) -> &[Member] { + &self.members + } + + pub(in crate::ast) fn member(&self, name: &str) -> Option<&Member> { + self.members.iter().find(|member| member.name == name) + } + + /// Bits of a flat value. + pub(in crate::ast) fn width(&self) -> usize { + self.members.iter().map(|member| member.width).sum() + } + + /// The leaves in declaration order. + pub(in crate::ast) fn leaves(&self) -> Vec { + let mut leaves = Vec::new(); + let mut offset = self.width(); + // Depth-first in declaration order: members are pushed in reverse. + fn push<'m>( + stack: &mut Vec<(Vec, &'m Member)>, + path: &[String], + members: &'m [Member], + ) { + for member in members.iter().rev() { + let mut member_path = path.to_vec(); + member_path.push(member.name.clone()); + stack.push((member_path, member)); + } + } + let mut stack = Vec::new(); + push(&mut stack, &[], &self.members); + while let Some((path, member)) = stack.pop() { + if let Some(inner) = member.r#type.unpacked_struct() { + push(&mut stack, &path, &inner.members); + continue; + } + offset -= member.width; + leaves.push(Leaf { + path, + r#type: member.r#type.clone(), + offset, + width: member.width, + }); + } + leaves + } +} + +/// The type of an unpacked structure with `fields`, or `None` when a member +/// type has no supported unpacked-structure representation. +pub(in crate::ast) fn native_type( + state: &AnalysisState, + owner: Arc, + fields: Vec<(String, Type)>, + env: &HashMap, +) -> Option { + let members = fields + .into_iter() + .map(|(name, ty)| { + if !ty.unpacked_ranges.is_empty() + || ty.container.is_some() + || matches!(ty.kind, TypeKind::Real | TypeKind::Shortreal) + { + return None; + } + let ty = match ty.unpacked_struct() { + Some(_) => ty, + None => packed_structs::capture_member_type(state, ty, env)?, + }; + let width = match ty.unpacked_struct() { + Some(inner) => inner.width(), + None => casts::expr_type_from_type(state, &ty, env)?.width, + }; + Some(Member { + name, + r#type: ty, + width, + }) + }) + .collect::>>()?; + let aggregate = UnpackedStruct { owner, members }; + let four_state = aggregate.leaves().iter().any(|leaf| { + matches!( + leaf.r#type.kind, + TypeKind::Logic | TypeKind::Reg | TypeKind::Implicit + ) + }); + let width = aggregate.width(); + let mut r#type = Type::new(if four_state { + TypeKind::Logic + } else { + TypeKind::Bit + }); + r#type.packed_ranges.push(PackedRange::new( + ConstExpr::Literal(width.checked_sub(1)?.to_string()), + ConstExpr::Literal("0".into()), + )); + r#type.unpacked_struct = Some(Arc::new(aggregate)); + Some(r#type) +} + +/// The flat value of the variable or member `name` of unpacked structure +/// type: the concatenation of its leaf variables. +pub(in crate::ast) fn whole(name: &str, dims: &PackedDimensions) -> Option { + let aggregate = dims.get(name)?.unpacked_struct.clone()?; + Some(Expr::Concat( + aggregate + .leaves() + .iter() + .map(|leaf| Expr::Ident(leaf.name(name))) + .collect(), + )) +} + +/// The leaf variables that an assignment to the whole variable or member +/// `name` of unpacked structure type writes, first declared first. +pub(in crate::ast) fn targets(name: &str, dims: &PackedDimensions) -> Option> { + let aggregate = dims.get(name)?.unpacked_struct.clone()?; + Some( + aggregate + .leaves() + .iter() + .map(|leaf| LValue::Ident(leaf.name(name))) + .collect(), + ) +} + +/// Join the leading names of a member path that select within a variable of +/// unpacked structure type: they name one of its member variables. +pub(in crate::ast) fn collapse(parts: &mut Vec, dims: &PackedDimensions) { + if parts.len() < 2 + || !dims + .get(&parts[0]) + .is_some_and(|shape| shape.unpacked_struct.is_some()) + { + return; + } + let mut name = parts[0].clone(); + let mut used = 1; + for part in &parts[1..] { + let candidate = format!("{name}.{part}"); + if dims.get(&candidate).is_none() { + break; + } + name = candidate; + used += 1; + } + parts.splice(..used, [name]); +} + +/// Every variable and member of unpacked structure type in a variable +/// `base` of type `aggregate`, outermost first, with its type. +pub(in crate::ast) fn aggregates(base: &str, r#type: &Type) -> Vec<(String, Type)> { + let mut found = Vec::new(); + let mut jobs = vec![(base.to_owned(), r#type.clone())]; + while let Some((name, ty)) = jobs.pop() { + if let Some(aggregate) = ty.unpacked_struct() { + for member in aggregate.members.iter().rev() { + jobs.push((format!("{name}.{}", member.name), member.r#type.clone())); + } + found.push((name, ty)); + } + } + found +} + +/// The unpacked structure type of native expression `id`, judged before it +/// is bound, or `None` when it is not a value of such a type. +pub(in crate::ast) fn native_struct( + id: ExprId, + native: &celox_sv_syntax::Declarations, + tree: &AnalysisContext<'_>, + dims: &PackedDimensions, +) -> Converted>> { + let id = expression_binding::ungroup(id, native); + Ok(match &native.expression(id).kind { + ExprKind::Name(_) | ExprKind::Member { .. } => member_path(id, native) + .map(|mut parts| { + collapse(&mut parts, dims); + parts + }) + .filter(|parts| parts.len() == 1) + .and_then(|parts| dims.get(&parts[0])?.unpacked_struct.clone()), + ExprKind::Conditional { + then_expression, + else_expression, + .. + } => { + let then_type = native_struct(*then_expression, native, tree, dims)?; + let else_type = native_struct(*else_expression, native, tree, dims)?; + then_type.filter(|then_type| { + else_type + .as_ref() + .is_some_and(|else_type| then_type.same_type(else_type)) + }) + } + // A typed assignment pattern, `T'{...}`. + ExprKind::Pattern { + target: Some(_), .. + } if std::ptr::eq(native, tree.declarations()) => { + patterns::typed_shape(id, tree, dims)?.and_then(|shape| shape.unpacked_struct) + } + ExprKind::Call { expression, .. } => { + expression_binding::callable_name(&tree.state, *expression, native) + .and_then(|name| dims.functions.get(&name)) + .and_then(|function| function.return_type.as_ref()) + .and_then(|ty| ty.unpacked_struct().cloned()) + } + _ => None, + }) +} + +/// The names of a native `a.b.c` path, or `None` for another expression. +fn member_path(mut id: ExprId, native: &celox_sv_syntax::Declarations) -> Option> { + let mut parts = Vec::new(); + loop { + match &native.expression(id).kind { + ExprKind::Name(name) => { + parts.push(instances::normalize_identifier(&name.spelling)); + break; + } + ExprKind::Member { + expression, + separator, + name, + } if separator == "." => { + parts.push(instances::normalize_identifier(&name.spelling)); + id = *expression; + } + ExprKind::Group(inner) => id = *inner, + _ => return None, + } + } + parts.reverse(); + Some(parts) +} + +/// The variable that native member path `id` names when it selects within a +/// variable of unpacked structure type. +pub(in crate::ast) fn native_member( + id: ExprId, + native: &celox_sv_syntax::Declarations, + dims: &PackedDimensions, +) -> Option { + let mut parts = member_path(id, native)?; + if parts.len() < 2 { + return None; + } + collapse(&mut parts, dims); + (parts.len() == 1).then(|| parts.pop().expect("one part")) +} + +/// Check that `right` may be assigned to the variable or member `name` of +/// unpacked structure type: an assignment pattern or a value of the same +/// type (IEEE 1800-2023 6.22.3). +pub(in crate::ast) fn check_assignment( + name: &str, + right: ExprId, + tree: &AnalysisContext<'_>, + dims: &PackedDimensions, +) -> Converted<()> { + let target = dims + .get(name) + .and_then(|shape| shape.unpacked_struct.clone()) + .ok_or_else(|| unsupported("unpacked structure target"))?; + check_value(&target, right, tree.declarations(), tree, dims) +} + +/// Check that `value` is an assignment pattern or a value of type `target`. +pub(in crate::ast) fn check_value( + target: &UnpackedStruct, + value: ExprId, + native: &celox_sv_syntax::Declarations, + tree: &AnalysisContext<'_>, + dims: &PackedDimensions, +) -> Converted<()> { + let value = expression_binding::ungroup(value, native); + // An untyped pattern takes the target's type; a typed one, `T'{...}`, + // must name it. + if matches!( + native.expression(value).kind, + ExprKind::Pattern { target: None, .. } + ) { + return Ok(()); + } + if let ExprKind::Conditional { + then_expression, + else_expression, + .. + } = &native.expression(value).kind + { + check_value(target, *then_expression, native, tree, dims)?; + return check_value(target, *else_expression, native, tree, dims); + } + match native_struct(value, native, tree, dims)? { + Some(actual) if actual.same_type(target) => Ok(()), + _ => Err(unsupported( + "assignment to an unpacked structure of a value that is not of its type", + )), + } +} + +/// Reject an operator applied to a whole unpacked structure value: only the +/// equality operators, the conditional operator and assignment take one +/// (IEEE 1800-2023 7.2, 11.2.2). +pub(in crate::ast) fn check_operands( + id: ExprId, + native: &celox_sv_syntax::Declarations, + tree: &AnalysisContext<'_>, + dims: &PackedDimensions, +) -> Converted<()> { + let operands: Vec = match &native.expression(id).kind { + ExprKind::Unary { expression, .. } => vec![*expression], + ExprKind::Binary { + left, + operator, + right, + } => { + if matches!(operator.as_str(), "==" | "!=" | "===" | "!==") { + let left_type = native_struct(*left, native, tree, dims)?; + let right_type = native_struct(*right, native, tree, dims)?; + return match (left_type, right_type) { + (None, None) => Ok(()), + (Some(left), Some(right)) if left.same_type(&right) => Ok(()), + _ => Err(unsupported(format!( + "operator `{operator}` comparing an unpacked structure with a value of another type" + ))), + }; + } + vec![*left, *right] + } + ExprKind::Concatenation(items) => items.clone(), + ExprKind::Replication { expressions, .. } => expressions.clone(), + ExprKind::Cast { expression, .. } => vec![*expression], + ExprKind::Select { expression, .. } => vec![*expression], + _ => return Ok(()), + }; + for operand in operands { + if native_struct(operand, native, tree, dims)?.is_some() { + return Err(unsupported( + "operator, select or cast applied to an unpacked structure", + )); + } + } + Ok(()) +} + +/// The leaf values of a flat value `value` of type `aggregate` when it is +/// made of one part per member (a pattern or a whole variable), with nested +/// structure members split in turn; `None` otherwise. +pub(in crate::ast) fn leaf_parts(aggregate: &UnpackedStruct, value: &Expr) -> Option> { + let Expr::Concat(parts) = value else { + return None; + }; + // A pattern has one part per member; descend into the parts of nested + // structure members, so a string leaf gets its string value. + if parts.len() == aggregate.members.len() { + let mut leaves = Vec::new(); + for (member, part) in aggregate.members.iter().zip(parts) { + match member.r#type.unpacked_struct() { + Some(inner) => match leaf_parts(inner, part) { + Some(inner_parts) => leaves.extend(inner_parts), + // A whole variable member with one leaf is that leaf. + None if inner.leaves().len() == 1 => leaves.push(part.clone()), + None => return None, + }, + None => leaves.push(part.clone()), + } + } + return Some(leaves); + } + // A whole variable is one part per leaf. + (parts.len() == aggregate.leaves().len()).then(|| parts.clone()) +} diff --git a/crates/celox-test-suite/LIMITATIONS.md b/crates/celox-test-suite/LIMITATIONS.md index cfce249e73..9255cac710 100644 --- a/crates/celox-test-suite/LIMITATIONS.md +++ b/crates/celox-test-suite/LIMITATIONS.md @@ -627,6 +627,8 @@ four-state expectations; the assertions remain enabled on all Celox variants. | `sv_icarus_integral_format_string` | execute | icarus | 1 | Icarus 13.0 compiles `$sformatf` with a `bit` vector variable as the format, then `vvp` stops: "$sformatf's first argument must be a string or a register." IEEE 1800-2023 21.3.3 reads the content of an integral format expression as the format; Verilator 5.052 gives the expected output, xezim 0.11.3 prints the format as a value (`sv_integral_format_string` in the xezim review). | | `sv_icarus_string_associative_arrays_and_property_queue_methods` | compile | icarus | 2 | Icarus 13.0 rejects associative arrays with `string` and `bit [63:0]` indices and queue methods called on a class property ("Can't find task push_back in class Log"), which IEEE 1800-2023 7.8 and 7.10 define; Verilator 5.052 and xezim 0.11.3 compile the cases. | | `sv_verilator_associative_index_cast` | execute | verilator | 1 | Verilator 5.052 evaluates an associative index `x + 32'd1` at 32 bits and stores key 0; IEEE 1800-2023 7.8.4 evaluates it as a cast to the `bit [63:0]` index type (6.24.1), giving `64'h100000000`. xezim 0.11.3 also stores key 0 (`sv_associative_index_cast` in its review). [Evidence](verification/repros/sv_associative_index_cast.json). | +| `sv_icarus_unpacked_structs` | compile | icarus | 8 | Icarus 13.0 does not support unpacked structures ("sorry: Unpacked structs not supported."), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default. | +| `sv_verilator_nested_struct_default` | execute | verilator | 1 | Verilator 5.052 applies '{default: 5, ...} to the int member of a nested unpacked structure but leaves its bit [3:0] member 0 ("5 0000 1" in the standalone source; a top-level '{default: 5} sets both). IEEE 1800-2023 10.9.2 applies the default recursively to each member of an unmatched substructure and evaluates it in the context of an assignment to a simple bit vector member, giving 4'b0101. | The `string_format` cases compare `$sformatf`, `$sformat` and `$swrite` with `$display` / `$write` of the same format and arguments. The [Icarus observations](verification/sv/limitations/string-format-icarus.json) diff --git a/crates/celox-test-suite/src/sv/cases/mod.rs b/crates/celox-test-suite/src/sv/cases/mod.rs index e05343c487..a528e737d5 100644 --- a/crates/celox-test-suite/src/sv/cases/mod.rs +++ b/crates/celox-test-suite/src/sv/cases/mod.rs @@ -135,6 +135,10 @@ pub(super) const GROUPS: &[Group] = &[ file: "src/sv/cases/packed_structs.vtest", text: include_str!("packed_structs.vtest"), }, + Group { + file: "src/sv/cases/unpacked_structs.vtest", + text: include_str!("unpacked_structs.vtest"), + }, Group { file: "src/sv/cases/procedural.vtest", text: include_str!("procedural.vtest"), diff --git a/crates/celox-test-suite/src/sv/cases/unpacked_structs.vtest b/crates/celox-test-suite/src/sv/cases/unpacked_structs.vtest new file mode 100644 index 0000000000..507b071581 --- /dev/null +++ b/crates/celox-test-suite/src/sv/cases/unpacked_structs.vtest @@ -0,0 +1,279 @@ +(group unpacked_structs (category types)) + +(case members_whole_copies_and_equality + (source "unpacked_struct_locals.sv" #" +module Top(output bit done); + typedef struct { int level; string name; bit [3:0] l; bit b; } row_t; + typedef struct { row_t r; string tag; } outer_t; + initial begin + row_t a, b; + outer_t o; + $display("def %0d [%s] %b %b", a.level, a.name, a.l, a.b); + a.level = 3; + a.name = "n"; + a.l = 4'b1011; + a.b = 1; + b = a; + b.name = "m"; + $display("%0d %s %b %b | %0d %s", a.level, a.name, a.l, a.b, b.level, b.name); + $display("eq %0d %0d", a == b, a != b); + b.name = "n"; + $display("eq %0d %0d len=%0d", a == b, a === b, b.name.len()); + o.r = b; + o.tag = "t"; + o.r.level++; + o.r.name = {o.r.name, o.tag}; + $display("nest %0d %s %s %0d", o.r.level, o.r.name, o.tag, o.r == a); + b = o.r; + $display("copy %0d %s", b.level, b.name); + done = 1; + end +endmodule +"#) + (top Top) (run_until 0) (assert_eq done 1) + (expect_output "def 0 [] 0000 0\n3 n 1011 1 | 3 m\neq 0 1\neq 1 1 len=1\nnest 4 nt t 0\ncopy 4 nt\n")) + +(case assignment_patterns + (source "unpacked_struct_patterns.sv" #" +module Top(output bit done); + typedef struct { int level; string name; bit [3:0] l; } row_t; + typedef struct { row_t r; string tag; } outer_t; + initial begin + row_t b; + outer_t o; + b = '{level: 7, name: "pat", default: 0}; + $display("named %0d %s %b", b.level, b.name, b.l); + b = '{1, "pos", 4'b0011}; + $display("positional %0d %s %b", b.level, b.name, b.l); + b = row_t'{-2, "typed", 4'hf}; + $display("typed %0d %s %b", b.level, b.name, b.l); + o = '{r: '{default: 0, name: "z"}, tag: "q"}; + $display("nested %0d %s %b %s", o.r.level, o.r.name, o.r.l, o.tag); + o = '{r: b, tag: "w"}; + $display("member %0d %s %s", o.r.level, o.r.name, o.tag); + o = '{default: 5, tag: "d"}; + $display("default %0d %b %s", o.r.level, o.r.l, o.tag); + done = 1; + end +endmodule +"#) + (top Top) (run_until 0) (assert_eq done 1) + (expect_output "named 7 pat 0000\npositional 1 pos 0011\ntyped -2 typed 1111\nnested 0 z 0000 q\nmember -2 typed w\ndefault 5 0101 d\n")) + +(case subroutine_arguments_and_results + (source "unpacked_struct_subroutines.sv" #" +module Top(output bit done); + typedef struct { int level; string name; } row_t; + function automatic string format(row_t r); + string s; + s.itoa(r.level); + return {s, ":", r.name}; + endfunction + function automatic row_t make(int level, string name); + row_t r; + r.level = level; + r.name = name; + return r; + endfunction + function automatic void bump(inout row_t r, output row_t o); + r.level++; + o = '{level: -1, name: {r.name, "!"}}; + if (r.level > 0) return; + o.level = 99; + endfunction + task automatic later(row_t r, output row_t q); + #2 q = r; + q.name = {r.name, "+"}; + endtask + initial begin + row_t a, b; + a = make(5, "five"); + $display("%s %s", format(a), format('{1, "one"})); + bump(a, b); + $display("%s %s", format(a), format(b)); + b = a.level > 5 ? a : b; + $display("%s %0d %0d", format(b), make(2, "x") == make(2, "x"), make(2, "x") == make(2, "y")); + later(a, b); + $display("%0d %s", $time, format(b)); + done = 1; + end +endmodule +"#) + (top Top) (run_until 2) (assert_eq done 1) + (expect_output "5:five 1:one\n6:five -1:five!\n6:five 1 0\n2 6:five+\n")) + +(case initializers_results_and_defaults + (source "unpacked_struct_initializers.sv" #" +module Top(output bit done); + typedef struct { int a; string s; } row_t; + typedef struct { int a; bit [3:0] b; } int_t; + row_t g = '{1, "g"}; + function automatic row_t make(); + make = '{2, "m"}; + endfunction + function automatic row_t make_members(); + make_members.a = 3; + make_members.s = "n"; + endfunction + function automatic row_t bump(inout row_t r); + r.a++; + return r; + endfunction + function automatic row_t twice(inout row_t r); + return bump(r); + endfunction + function automatic int f(row_t r = '{default: 0, s: "d"}); + return r.a + r.s.len(); + endfunction + initial begin + row_t x, y; + $display("g %0d %s", g.a, g.s); + x = make(); + y = make_members(); + $display("make %0d %s %0d %s", x.a, x.s, y.a, y.s); + y = twice(x); + $display("twice %0d %0d", x.a, y.a); + $display("default %0d %0d", f(), f(x)); + repeat (2) begin + static int_t r = '{0, 5}; + $display("static %0d %0d", r.a, r.b); + r.a++; + end + done = 1; + end +endmodule +"#) + (top Top) (run_until 0) (assert_eq done 1) + (expect_output "g 1 g\nmake 2 m 3 n\ntwice 3 3\ndefault 1 4\nstatic 0 5\nstatic 1 5\n")) + +(case results_and_outputs + (source "unpacked_struct_results.sv" #" +module Top(output bit done); + typedef struct { int a; } row_t; + typedef struct { string s; } str_t; + typedef struct { int x, y; } inner_t; + typedef struct { inner_t i; } outer_t; + function automatic row_t make(bit set); + if (set) make.a = 7; + endfunction + function automatic void outs(output str_t r); + r.s = "abc"; + endfunction + function automatic void outz(output row_t r); + if (0) r.a = 1; + endfunction + initial begin + row_t r; + str_t s; + r = make(1); + r = make(0); + $display("make %0d", r.a); + outs(s); + $display("outs %s", s.s); + r.a = 5; + outz(r); + $display("outz %0d", r.a); + repeat (2) begin + static outer_t o = '{'{1, 2}}; + $display("static %0d %0d", o.i.x, o.i.y); + o.i.x++; + end + done = 1; + end +endmodule +"#) + (top Top) (run_until 0) (assert_eq done 1) + (expect_output "make 0\nouts abc\noutz 0\nstatic 1 2\nstatic 2 2\n")) + +(case module_variables + (source "unpacked_struct_module.sv" #" +module Top(input bit clk, input bit [7:0] a, input bit [3:0] b, + output bit [7:0] next_a, output bit [3:0] next_b, + output bit [7:0] stored_a, output bit [3:0] stored_b, output bit same); + typedef struct { bit [7:0] a; bit [3:0] b; } pair_t; + pair_t p, q, c, r; + assign p = '{a: a, b: b}; + always_comb begin + q = p; + q.b = p.b + 1; + end + assign c = '{a: q.a + 1, b: q.b}; + always_ff @(posedge clk) begin + r <= c; + r.b <= c.b ^ 4'hf; + end + assign next_a = c.a; + assign next_b = c.b; + assign stored_a = r.a; + assign stored_b = r.b; + assign same = p == q; +endmodule +"#) + (top Top) + (for (x y) (list (0x10 3) (0xfe 15)) + (modify (set a x) (set b y)) + (assert_eq next_a (& (+ x 1) 0xff)) + (assert_eq next_b (& (+ y 1) 15)) + (assert_eq same 0) + (tick clk) + (assert_eq stored_a (& (+ x 1) 0xff)) + (assert_eq stored_b (^ (& (+ y 1) 15) 15)))) + +(case handle_members_survive_heap_collections + (source "unpacked_struct_handles.sv" #" +module Top(output int n); + class Node; + string text; + function new(string text); + this.text = text; + endfunction + function string get(); + return text; + endfunction + endclass + typedef struct { Node m_type; string m_type_name; } pair_t; + logic clk = 0; + always #1 clk = ~clk; + pair_t kept, last; + Node node; + string number; + initial begin + kept.m_type = new("kept"); + kept.m_type_name = {"na", "me"}; + end + always @(posedge clk) begin + number.itoa(n); + node = new(number); + last = '{m_type: node, m_type_name: {number, "!"}}; + node = null; + n = n + 1; + end + initial #6000 $display("[%s] [%s] [%s] [%s] %0d %0d", kept.m_type.get(), kept.m_type_name, + last.m_type.text, last.m_type_name, kept.m_type != null, n); +endmodule +"#) + (top Top) + (run_until 6000) + (expect_output "[kept] [name] [2999] [2999!] 1 3000\n")) + +(case four_state_members + (four_state) + (source "unpacked_struct_four_state.sv" #" +module Top(output bit done); + typedef struct { time t; logic [3:0] l; int i; bit [1:0] b; } access_t; + access_t a, b; + initial begin + $display("def %0d %b %0d %b", a.t, a.l, a.i, a.b); + a.l = 4'b1z0x; + b = a; + $display("copy %b %0d %0d", b.l, a == b, a === b); + b = '{t: 5, l: 4'bxx11, i: 'x, b: 2'bx1}; + $display("pattern %0d %b %0d %b", b.t, b.l, b.i, b.b); + b = '{default: 0}; + $display("default %0d %b %0d %b", b.t, b.l, b.i, b.b); + done = 1; + end +endmodule +"#) + (top Top) (run_until 0) (assert_eq done 1) + (expect_output "def x xxxx 0 00\ncopy 1z0x x 1\npattern 5 xx11 0 01\ndefault 0 0000 0 00\n")) diff --git a/crates/celox-test-suite/verification/repros/sv_icarus_unpacked_structs.sv b/crates/celox-test-suite/verification/repros/sv_icarus_unpacked_structs.sv new file mode 100644 index 0000000000..d45870ee5e --- /dev/null +++ b/crates/celox-test-suite/verification/repros/sv_icarus_unpacked_structs.sv @@ -0,0 +1 @@ +module top; typedef struct { int a; } s_t; s_t s; initial begin s.a = 1; $display("%0d", s.a); end endmodule diff --git a/crates/celox-test-suite/verification/repros/sv_verilator_nested_struct_default.sv b/crates/celox-test-suite/verification/repros/sv_verilator_nested_struct_default.sv new file mode 100644 index 0000000000..f8e142aee0 --- /dev/null +++ b/crates/celox-test-suite/verification/repros/sv_verilator_nested_struct_default.sv @@ -0,0 +1,9 @@ +module top; + typedef struct { int level; bit [3:0] l; } row_t; + typedef struct { row_t r; int tag; } outer_t; + outer_t o; + initial begin + o = '{default: 5, tag: 1}; + $display("%0d %b %0d", o.r.level, o.r.l, o.tag); + end +endmodule diff --git a/crates/celox-test-suite/verification/repros/sv_xezim_constructor_argument_in_always.sv b/crates/celox-test-suite/verification/repros/sv_xezim_constructor_argument_in_always.sv new file mode 100644 index 0000000000..e9725d4f2b --- /dev/null +++ b/crates/celox-test-suite/verification/repros/sv_xezim_constructor_argument_in_always.sv @@ -0,0 +1,26 @@ +// xezim 0.11.3: an object constructed with `new(s)` in an always procedure +// keeps an empty string property; IEEE 1800-2023 8.7 runs the constructor. +// Expected: "[2] 3". +module top; + class Node; + string text; + function new(string t); + text = t; + endfunction + endclass + Node node, keep; + string number; + int n; + logic clk = 0; + always #1 clk = ~clk; + always @(posedge clk) begin + number.itoa(n); + node = new(number); + keep = node; + n = n + 1; + end + initial begin + #6 $display("[%s] %0d", keep.text, n); + $finish; + end +endmodule diff --git a/crates/celox-test-suite/verification/repros/sv_xezim_struct_default_argument.sv b/crates/celox-test-suite/verification/repros/sv_xezim_struct_default_argument.sv new file mode 100644 index 0000000000..495303a1b4 --- /dev/null +++ b/crates/celox-test-suite/verification/repros/sv_xezim_struct_default_argument.sv @@ -0,0 +1,14 @@ +// xezim 0.11.3: a structure assignment pattern as the default of a +// subroutine argument loses its named members (IEEE 1800-2023 13.5.3, +// 10.9.2). Expected: "1 d". +module top; + typedef struct { int a; string s; } row_t; + function automatic int f(row_t r = '{default: 0, s: "d"}); + $write("%0d ", r.s.len()); + return r.a; + endfunction + initial begin + void'(f()); + begin row_t r; r = '{default: 0, s: "d"}; $display("%s", r.s); end + end +endmodule diff --git a/crates/celox-test-suite/verification/repros/sv_xezim_struct_output_default.sv b/crates/celox-test-suite/verification/repros/sv_xezim_struct_output_default.sv new file mode 100644 index 0000000000..4d449b1605 --- /dev/null +++ b/crates/celox-test-suite/verification/repros/sv_xezim_struct_output_default.sv @@ -0,0 +1,16 @@ +// xezim 0.11.3: an automatic output argument of unpacked structure type that +// the function does not write is copied out as X for a 2-state member; IEEE +// 1800-2023 13.5.2 and 6.8 give the formal its type's default, 0. +// Expected: "0". +module top; + typedef struct { int a; } row_t; + function automatic void outz(output row_t r); + if (0) r.a = 1; + endfunction + initial begin + row_t r; + r.a = 5; + outz(r); + $display("%0d", r.a); + end +endmodule diff --git a/crates/celox-test-suite/verification/repros/sv_xezim_unpacked_structs.sv b/crates/celox-test-suite/verification/repros/sv_xezim_unpacked_structs.sv new file mode 100644 index 0000000000..3bea89716c --- /dev/null +++ b/crates/celox-test-suite/verification/repros/sv_xezim_unpacked_structs.sv @@ -0,0 +1,26 @@ +// xezim 0.11.3 reproductions of the unpacked-structure defects retained for +// the unpacked_structs cases (IEEE 1800-2023 7.2, 10.9.2, 11.4.11). +// Expected: "cond 6 a", "member 6 a w", "p 10 3 q 10 4". +module top; + typedef struct { int level; string name; } row_t; + typedef struct { row_t r; string tag; } outer_t; + typedef struct { bit [7:0] a; bit [3:0] b; } pair_t; + row_t a, b; + outer_t o; + pair_t p, q; + bit [7:0] x = 8'h10; + assign p = '{a: x, b: 4'h3}; + always_comb begin + q = p; + q.b = p.b + 1; + end + initial begin + a = '{6, "a"}; + b = '{1, "b"}; + b = a.level > 5 ? a : b; + $display("cond %0d %s", b.level, b.name); + o = '{r: a, tag: "w"}; + $display("member %0d %s %s", o.r.level, o.r.name, o.tag); + #1 $display("p %h %h q %h %h", p.a, p.b, q.a, q.b); + end +endmodule diff --git a/crates/celox-test-suite/verification/sv/icarus.json b/crates/celox-test-suite/verification/sv/icarus.json index fe9e438501..c10f9fe0ae 100644 --- a/crates/celox-test-suite/verification/sv/icarus.json +++ b/crates/celox-test-suite/verification/sv/icarus.json @@ -27,7 +27,8 @@ "icarus/tagged_unions.json", "icarus/timing.json", "icarus/types.json", - "icarus/unions.json" + "icarus/unions.json", + "icarus/unpacked_structs.json" ], "exclude_stronger_than_sv": false, "include_ignored": false, diff --git a/crates/celox-test-suite/verification/sv/icarus/unpacked_structs.json b/crates/celox-test-suite/verification/sv/icarus/unpacked_structs.json new file mode 100644 index 0000000000..6b919f07e5 --- /dev/null +++ b/crates/celox-test-suite/verification/sv/icarus/unpacked_structs.json @@ -0,0 +1,188 @@ +{ + "cases": [ + { + "category": "Types", + "detail": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "expectation": "Simulation", + "known_issue": { + "category": "simulator_compile_limitation", + "evidence": [ + "verification/repros/sv_icarus_unpacked_structs.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "id": "sv_icarus_unpacked_structs", + "observed_version": "Celox SV frontend; Verilator 5.052 / Icarus 13.0", + "phase": "compile", + "reason": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "upstream": [] + }, + "name": "unpacked_structs::assignment_patterns", + "phase": "skip", + "status": "ignored", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "expectation": "Simulation", + "known_issue": { + "category": "simulator_compile_limitation", + "evidence": [ + "verification/repros/sv_icarus_unpacked_structs.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "id": "sv_icarus_unpacked_structs", + "observed_version": "Celox SV frontend; Verilator 5.052 / Icarus 13.0", + "phase": "compile", + "reason": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "upstream": [] + }, + "name": "unpacked_structs::four_state_members", + "phase": "skip", + "status": "ignored", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "expectation": "Simulation", + "known_issue": { + "category": "simulator_compile_limitation", + "evidence": [ + "verification/repros/sv_icarus_unpacked_structs.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "id": "sv_icarus_unpacked_structs", + "observed_version": "Celox SV frontend; Verilator 5.052 / Icarus 13.0", + "phase": "compile", + "reason": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "upstream": [] + }, + "name": "unpacked_structs::handle_members_survive_heap_collections", + "phase": "skip", + "status": "ignored", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "expectation": "Simulation", + "known_issue": { + "category": "simulator_compile_limitation", + "evidence": [ + "verification/repros/sv_icarus_unpacked_structs.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "id": "sv_icarus_unpacked_structs", + "observed_version": "Celox SV frontend; Verilator 5.052 / Icarus 13.0", + "phase": "compile", + "reason": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "upstream": [] + }, + "name": "unpacked_structs::initializers_results_and_defaults", + "phase": "skip", + "status": "ignored", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "expectation": "Simulation", + "known_issue": { + "category": "simulator_compile_limitation", + "evidence": [ + "verification/repros/sv_icarus_unpacked_structs.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "id": "sv_icarus_unpacked_structs", + "observed_version": "Celox SV frontend; Verilator 5.052 / Icarus 13.0", + "phase": "compile", + "reason": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "upstream": [] + }, + "name": "unpacked_structs::members_whole_copies_and_equality", + "phase": "skip", + "status": "ignored", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "expectation": "Simulation", + "known_issue": { + "category": "simulator_compile_limitation", + "evidence": [ + "verification/repros/sv_icarus_unpacked_structs.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "id": "sv_icarus_unpacked_structs", + "observed_version": "Celox SV frontend; Verilator 5.052 / Icarus 13.0", + "phase": "compile", + "reason": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "upstream": [] + }, + "name": "unpacked_structs::module_variables", + "phase": "skip", + "status": "ignored", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "expectation": "Simulation", + "known_issue": { + "category": "simulator_compile_limitation", + "evidence": [ + "verification/repros/sv_icarus_unpacked_structs.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "id": "sv_icarus_unpacked_structs", + "observed_version": "Celox SV frontend; Verilator 5.052 / Icarus 13.0", + "phase": "compile", + "reason": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "upstream": [] + }, + "name": "unpacked_structs::results_and_outputs", + "phase": "skip", + "status": "ignored", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "expectation": "Simulation", + "known_issue": { + "category": "simulator_compile_limitation", + "evidence": [ + "verification/repros/sv_icarus_unpacked_structs.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "id": "sv_icarus_unpacked_structs", + "observed_version": "Celox SV frontend; Verilator 5.052 / Icarus 13.0", + "phase": "compile", + "reason": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "upstream": [] + }, + "name": "unpacked_structs::subroutine_arguments_and_results", + "phase": "skip", + "status": "ignored", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + } + ] +} diff --git a/crates/celox-test-suite/verification/sv/limitations.json b/crates/celox-test-suite/verification/sv/limitations.json index 28a1966ca9..9f1345b58d 100644 --- a/crates/celox-test-suite/verification/sv/limitations.json +++ b/crates/celox-test-suite/verification/sv/limitations.json @@ -2969,5 +2969,46 @@ "string_format::sformatf_with_associative_index" ] } + }, + { + "id": "sv_icarus_unpacked_structs", + "category": "simulator_compile_limitation", + "phase": "compile", + "observed_version": "Celox SV frontend; Verilator 5.052 / Icarus 13.0", + "reason": "Icarus 13.0 does not support unpacked structures (\"sorry: Unpacked structs not supported.\"), which IEEE 1800-2023 7.2 defines; the standalone source reproduces the diagnostic. Verilator passes all but the nested default pattern, see sv_verilator_nested_struct_default.", + "upstream": [], + "evidence": [ + "verification/repros/sv_icarus_unpacked_structs.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "cases": { + "icarus": [ + "unpacked_structs::assignment_patterns", + "unpacked_structs::four_state_members", + "unpacked_structs::handle_members_survive_heap_collections", + "unpacked_structs::initializers_results_and_defaults", + "unpacked_structs::members_whole_copies_and_equality", + "unpacked_structs::module_variables", + "unpacked_structs::results_and_outputs", + "unpacked_structs::subroutine_arguments_and_results" + ] + } + }, + { + "id": "sv_verilator_nested_struct_default", + "category": "simulator_disagreement", + "phase": "execute", + "observed_version": "Verilator 5.052", + "reason": "Verilator 5.052 applies '{default: 5, ...} to the int member of a nested unpacked structure but leaves its bit [3:0] member 0 (\"5 0000 1\" in the standalone source; a top-level '{default: 5} sets both). IEEE 1800-2023 10.9.2 applies the default recursively to each member of an unmatched substructure and evaluates it in the context of an assignment to a simple bit vector member, giving 4'b0101.", + "upstream": [], + "evidence": [ + "verification/repros/sv_verilator_nested_struct_default.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "cases": { + "verilator": [ + "unpacked_structs::assignment_patterns" + ] + } } ] diff --git a/crates/celox-test-suite/verification/sv/verilator.json b/crates/celox-test-suite/verification/sv/verilator.json index 182e769d1f..2e2c18d926 100644 --- a/crates/celox-test-suite/verification/sv/verilator.json +++ b/crates/celox-test-suite/verification/sv/verilator.json @@ -27,7 +27,8 @@ "verilator/system_functions.json", "verilator/tagged_unions.json", "verilator/timing.json", - "verilator/types.json" + "verilator/types.json", + "verilator/unpacked_structs.json" ], "exclude_stronger_than_sv": false, "include_ignored": false, diff --git a/crates/celox-test-suite/verification/sv/verilator/unpacked_structs.json b/crates/celox-test-suite/verification/sv/verilator/unpacked_structs.json new file mode 100644 index 0000000000..1344517bc8 --- /dev/null +++ b/crates/celox-test-suite/verification/sv/verilator/unpacked_structs.json @@ -0,0 +1,104 @@ +{ + "cases": [ + { + "category": "Types", + "detail": "Verilator 5.052 applies '{default: 5, ...} to the int member of a nested unpacked structure but leaves its bit [3:0] member 0 (\"5 0000 1\" in the standalone source; a top-level '{default: 5} sets both). IEEE 1800-2023 10.9.2 applies the default recursively to each member of an unmatched substructure and evaluates it in the context of an assignment to a simple bit vector member, giving 4'b0101.", + "expectation": "Simulation", + "known_issue": { + "category": "simulator_disagreement", + "evidence": [ + "verification/repros/sv_verilator_nested_struct_default.sv", + "LIMITATIONS.md#systemverilog-suite" + ], + "id": "sv_verilator_nested_struct_default", + "observed_version": "Verilator 5.052", + "phase": "execute", + "reason": "Verilator 5.052 applies '{default: 5, ...} to the int member of a nested unpacked structure but leaves its bit [3:0] member 0 (\"5 0000 1\" in the standalone source; a top-level '{default: 5} sets both). IEEE 1800-2023 10.9.2 applies the default recursively to each member of an unmatched substructure and evaluates it in the context of an assignment to a simple bit vector member, giving 4'b0101.", + "upstream": [] + }, + "name": "unpacked_structs::assignment_patterns", + "phase": "skip", + "status": "ignored", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "four-state expectations", + "expectation": "Simulation", + "name": "unpacked_structs::four_state_members", + "phase": "compile", + "status": "unsupported", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::handle_members_survive_heap_collections", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::initializers_results_and_defaults", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::members_whole_copies_and_equality", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::module_variables", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::results_and_outputs", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::subroutine_arguments_and_results", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + } + ] +} diff --git a/crates/celox-test-suite/verification/sv/xezim.json b/crates/celox-test-suite/verification/sv/xezim.json index 1d89248a43..ff2b063c49 100644 --- a/crates/celox-test-suite/verification/sv/xezim.json +++ b/crates/celox-test-suite/verification/sv/xezim.json @@ -36,7 +36,8 @@ "xezim/tagged_unions.json", "xezim/timing.json", "xezim/types.json", - "xezim/unions.json" + "xezim/unions.json", + "xezim/unpacked_structs.json" ], "exclude_stronger_than_sv": false, "include_ignored": false, diff --git a/crates/celox-test-suite/verification/sv/xezim/unpacked_structs.json b/crates/celox-test-suite/verification/sv/xezim/unpacked_structs.json new file mode 100644 index 0000000000..736d734292 --- /dev/null +++ b/crates/celox-test-suite/verification/sv/xezim/unpacked_structs.json @@ -0,0 +1,92 @@ +{ + "cases": [ + { + "category": "Types", + "detail": "unpacked_structs::assignment_patterns at 61:3: expect_output: expected [110, 97, 109, 101, 100, 32, 55, 32, 112, 97, 116, 32, 48, 48, 48, 48, 10, 112, 111, 115, 105, 116, 105, 111, 110, 97, 108, 32, 49, 32, 112, 111, 115, 32, 48, 48, 49, 49, 10, 116, 121, 112, 101, 100, 32, 45, 50, 32, 116, 121, 112, 101, 100, 32, 49, 49, 49, 49, 10, 110, 101, 115, 116, 101, 100, 32, 48, 32, 122, 32, 48, 48, 48, 48, 32, 113, 10, 109, 101, 109, 98, 101, 114, 32, 45, 50, 32, 116, 121, 112, 101, 100, 32, 119, 10, 100, 101, 102, 97, 117, 108, 116, 32, 53, 32, 48, 49, 48, 49, 32, 100, 10], got [110, 97, 109, 101, 100, 32, 55, 32, 112, 97, 116, 32, 48, 48, 48, 48, 10, 112, 111, 115, 105, 116, 105, 111, 110, 97, 108, 32, 49, 32, 112, 111, 115, 32, 48, 48, 49, 49, 10, 116, 121, 112, 101, 100, 32, 45, 50, 32, 116, 121, 112, 101, 100, 32, 49, 49, 49, 49, 10, 110, 101, 115, 116, 101, 100, 32, 48, 32, 122, 32, 48, 48, 48, 48, 32, 113, 10, 109, 101, 109, 98, 101, 114, 32, 48, 32, 32, 119, 10, 100, 101, 102, 97, 117, 108, 116, 32, 53, 32, 48, 49, 48, 49, 32, 100, 10]", + "expectation": "Simulation", + "name": "unpacked_structs::assignment_patterns", + "phase": "execute", + "status": "mismatch", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::four_state_members", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "unpacked_structs::handle_members_survive_heap_collections at 257:3: expect_output: expected [91, 107, 101, 112, 116, 93, 32, 91, 110, 97, 109, 101, 93, 32, 91, 50, 57, 57, 57, 93, 32, 91, 50, 57, 57, 57, 33, 93, 32, 49, 32, 51, 48, 48, 48, 10], got [91, 107, 101, 112, 116, 93, 32, 91, 110, 97, 109, 101, 93, 32, 91, 32, 93, 32, 91, 50, 57, 57, 57, 33, 93, 32, 49, 32, 51, 48, 48, 48, 10]", + "expectation": "Simulation", + "name": "unpacked_structs::handle_members_survive_heap_collections", + "phase": "execute", + "status": "mismatch", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "unpacked_structs::initializers_results_and_defaults at 147:3: expect_output: expected [103, 32, 49, 32, 103, 10, 109, 97, 107, 101, 32, 50, 32, 109, 32, 51, 32, 110, 10, 116, 119, 105, 99, 101, 32, 51, 32, 51, 10, 100, 101, 102, 97, 117, 108, 116, 32, 49, 32, 52, 10, 115, 116, 97, 116, 105, 99, 32, 48, 32, 53, 10, 115, 116, 97, 116, 105, 99, 32, 49, 32, 53, 10], got [103, 32, 49, 32, 103, 10, 109, 97, 107, 101, 32, 50, 32, 109, 32, 51, 32, 110, 10, 116, 119, 105, 99, 101, 32, 51, 32, 51, 10, 100, 101, 102, 97, 117, 108, 116, 32, 48, 32, 52, 10, 115, 116, 97, 116, 105, 99, 32, 48, 32, 53, 10, 115, 116, 97, 116, 105, 99, 32, 49, 32, 53, 10]", + "expectation": "Simulation", + "name": "unpacked_structs::initializers_results_and_defaults", + "phase": "execute", + "status": "mismatch", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::members_whole_copies_and_equality", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "unpacked_structs::module_variables at 215:5: assert_eq next_a: expected 0x11, got 0x0", + "expectation": "Simulation", + "name": "unpacked_structs::module_variables", + "phase": "execute", + "status": "mismatch", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "unpacked_structs::results_and_outputs at 186:3: expect_output: expected [109, 97, 107, 101, 32, 48, 10, 111, 117, 116, 115, 32, 97, 98, 99, 10, 111, 117, 116, 122, 32, 48, 10, 115, 116, 97, 116, 105, 99, 32, 49, 32, 50, 10, 115, 116, 97, 116, 105, 99, 32, 50, 32, 50, 10], got [109, 97, 107, 101, 32, 48, 10, 111, 117, 116, 115, 32, 97, 98, 99, 10, 111, 117, 116, 122, 32, 120, 10, 115, 116, 97, 116, 105, 99, 32, 49, 32, 50, 10, 115, 116, 97, 116, 105, 99, 32, 50, 32, 50, 10]", + "expectation": "Simulation", + "name": "unpacked_structs::results_and_outputs", + "phase": "execute", + "status": "mismatch", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "unpacked_structs::subroutine_arguments_and_results at 103:3: expect_output: expected [53, 58, 102, 105, 118, 101, 32, 49, 58, 111, 110, 101, 10, 54, 58, 102, 105, 118, 101, 32, 45, 49, 58, 102, 105, 118, 101, 33, 10, 54, 58, 102, 105, 118, 101, 32, 49, 32, 48, 10, 50, 32, 54, 58, 102, 105, 118, 101, 43, 10], got [53, 58, 102, 105, 118, 101, 32, 49, 58, 111, 110, 101, 10, 54, 58, 102, 105, 118, 101, 32, 45, 49, 58, 102, 105, 118, 101, 33, 10, 45, 49, 58, 102, 105, 118, 101, 33, 32, 49, 32, 48, 10, 50, 32, 54, 58, 102, 105, 118, 101, 43, 10]", + "expectation": "Simulation", + "name": "unpacked_structs::subroutine_arguments_and_results", + "phase": "execute", + "status": "mismatch", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + } + ] +} diff --git a/crates/celox-test-suite/verification/xezim-review.json b/crates/celox-test-suite/verification/xezim-review.json index b087305827..a22d1b0a5c 100644 --- a/crates/celox-test-suite/verification/xezim-review.json +++ b/crates/celox-test-suite/verification/xezim-review.json @@ -762,6 +762,30 @@ "sv_associative_index_cast": { "title": "An associative index is evaluated at its own width instead of as a cast to the index type", "standard": "IEEE 1800-2023 7.8.4, 6.24.1" + }, + "sv_rest-X80": { + "title": "A conditional operator whose operands are unpacked structures yields the false operand", + "standard": "IEEE 1800-2023 11.4.11, 7.2" + }, + "sv_rest-X81": { + "title": "A structure assignment pattern item that is an unpacked structure value sets the member to its default", + "standard": "IEEE 1800-2023 10.9.2" + }, + "sv_rest-X82": { + "title": "A whole unpacked-structure assignment in always_comb leaves the target members at their defaults", + "standard": "IEEE 1800-2023 7.2, 9.2.2.2" + }, + "sv_rest-X83": { + "title": "An object constructed with `new(s)` in an always procedure keeps an empty string property", + "standard": "IEEE 1800-2023 8.7, 9.2.2.1" + }, + "sv_rest-X85": { + "title": "A structure assignment pattern as the default of a subroutine argument loses its named members", + "standard": "IEEE 1800-2023 13.5.3, 10.9.2" + }, + "sv_rest-X88": { + "title": "An automatic output argument of unpacked structure type that is not written is copied out as X for a 2-state member", + "standard": "IEEE 1800-2023 13.5.2, 6.8" } }, "cases": [ @@ -4547,6 +4571,54 @@ "classification": "xezim_bug", "bug": "sv_associative_index_cast", "note": "With int unsigned x = 32'hffffffff and string wide[bit [63:0]], wide[x + 32'd1] = ... stores key 0 in xezim 0.11.3 (prints 0 1); 7.8.4 evaluates the index as a cast to bit [63:0], so the key is 64'h100000000 (expected 1 0). See verification/repros/sv_associative_index_cast.json." + }, + { + "suite": "sv", + "name": "unpacked_structs::assignment_patterns", + "status": "mismatch", + "classification": "xezim_bug", + "bug": "sv_rest-X81", + "note": "o = '{r: b, tag: \"w\"} with a row_t variable b prints \"member 0 w\": xezim stores the default for the structure-valued member instead of b's members (-2, \"typed\"). Reproduced by verification/repros/sv_xezim_unpacked_structs.sv." + }, + { + "suite": "sv", + "name": "unpacked_structs::subroutine_arguments_and_results", + "status": "mismatch", + "classification": "xezim_bug", + "bug": "sv_rest-X80", + "note": "b = a.level > 5 ? a : b with a.level = 6 keeps b (\"-1:five!\") instead of copying a (\"6:five\"). Reproduced by verification/repros/sv_xezim_unpacked_structs.sv." + }, + { + "suite": "sv", + "name": "unpacked_structs::module_variables", + "status": "mismatch", + "classification": "xezim_bug", + "bug": "sv_rest-X82", + "note": "always_comb begin q = p; q.b = p.b + 1; end leaves q.a = 0, so c.a = q.a + 1 and next_a read 0 instead of a + 1 (0x11). Reproduced by verification/repros/sv_xezim_unpacked_structs.sv." + }, + { + "suite": "sv", + "name": "unpacked_structs::handle_members_survive_heap_collections", + "status": "mismatch", + "classification": "xezim_bug", + "bug": "sv_rest-X83", + "note": "node = new(number) in always @(posedge clk) gives an object whose text property is empty, so last.m_type.text prints \"[ ]\" instead of \"[2999]\"; the same happens without the structure. Reproduced by verification/repros/sv_xezim_constructor_argument_in_always.sv." + }, + { + "suite": "sv", + "name": "unpacked_structs::initializers_results_and_defaults", + "status": "mismatch", + "classification": "xezim_bug", + "bug": "sv_rest-X85", + "note": "f() with the default '{default: 0, s: \"d\"} sees an empty s and returns 0 instead of 1; the same pattern assigned to a variable keeps s. Reproduced by verification/repros/sv_xezim_struct_default_argument.sv." + }, + { + "suite": "sv", + "name": "unpacked_structs::results_and_outputs", + "status": "mismatch", + "classification": "xezim_bug", + "bug": "sv_rest-X88", + "note": "outz(r) does not write its output formal, so r.a must become the int default 0; xezim prints \"outz x\". Reproduced by verification/repros/sv_xezim_struct_output_default.sv." } ] } diff --git a/crates/celox/tests/frontends/systemverilog/unpacked_structs.rs b/crates/celox/tests/frontends/systemverilog/unpacked_structs.rs new file mode 100644 index 0000000000..95f1c6b75c --- /dev/null +++ b/crates/celox/tests/frontends/systemverilog/unpacked_structs.rs @@ -0,0 +1,207 @@ +//! Unpacked structures (IEEE 1800-2023 7.2). + +use super::*; + +sv_backends! { + fn unpacked_struct_members_whole_copies_and_equality(sim) { + @case "unpacked_structs::members_whole_copies_and_equality"; + } + fn unpacked_struct_assignment_patterns(sim) { + @case "unpacked_structs::assignment_patterns"; + } + fn unpacked_struct_subroutine_arguments_and_results(sim) { + @case "unpacked_structs::subroutine_arguments_and_results"; + } + fn unpacked_struct_initializers_results_and_defaults(sim) { + @case "unpacked_structs::initializers_results_and_defaults"; + } + fn unpacked_struct_results_and_outputs(sim) { + @case "unpacked_structs::results_and_outputs"; + } + + fn constructors_in_structure_patterns(sim) { + @setup { + let source = r#"module Top(output bit ok); + class Node; + string n; + function new(); + n = "made"; + endfunction + endclass + typedef struct { Node n; int k; } handle_t; + initial begin + handle_t h; + h = '{n: new(), k: 1}; + ok = h.n.n == "made" && h.k == 1; + end + endmodule"#; + } + @build celox::Simulation::from_sv_sources(vec![(source, Path::new("struct.sv"))], "Top"); + sim.run_until(0).unwrap(); + let ok = sim.signal("ok"); + assert_eq!(sim.get(ok), BigUint::from(1u8)); + } + fn static_task_structure_arguments_are_shared(sim) { + @setup { + let source = r#"`timescale 1ns/1ns + module Top(output int first, output int second); + typedef struct { int a; string s; } row_t; + task show(input row_t r, output int seen); + #2 seen = r.a * 10 + r.s.len(); + endtask + initial show('{1, "x"}, first); + initial #1 show('{2, "yy"}, second); + endmodule"#; + } + @build celox::Simulation::from_sv_sources(vec![(source, Path::new("struct.sv"))], "Top"); + sim.run_until(4).unwrap(); + let (first, second) = (sim.signal("first"), sim.signal("second")); + assert_eq!(sim.get(first), BigUint::from(22u8)); + assert_eq!(sim.get(second), BigUint::from(22u8)); + } + fn static_nested_string_initializers(sim) { + @setup { + let source = r#"module Top(output int n); + typedef struct { string s; } in_t; + typedef struct { in_t i; } out_t; + initial repeat (2) begin + static out_t o = '{'{"abc"}}; + n = n * 10 + o.i.s.len(); + o.i.s = {o.i.s, "d"}; + end + endmodule"#; + } + @build celox::Simulation::from_sv_sources(vec![(source, Path::new("struct.sv"))], "Top"); + sim.run_until(0).unwrap(); + let n = sim.signal("n"); + assert_eq!(sim.get(n), BigUint::from(34u8)); + } + fn explicit_static_structures_of_repeating_processes(sim) { + @setup { + let source = r#"`timescale 1ns/1ns + module Top(output int out); + typedef struct { int a; string s; } row_t; + int source = 5; + logic clk = 0; + always #1 clk = ~clk; + always @(posedge clk) begin + static row_t r = '{a: source, s: "x"}; + r.a++; + out = r.a * 10 + r.s.len(); + end + endmodule"#; + } + @build celox::Simulation::from_sv_sources(vec![(source, Path::new("struct.sv"))], "Top"); + sim.run_until(4).unwrap(); + let out = sim.signal("out"); + assert_eq!(sim.get(out), BigUint::from(71u8)); + } + fn unpacked_struct_module_variables(sim) { + @case "unpacked_structs::module_variables"; + } + fn unpacked_struct_handle_members_survive_heap_collections(sim) { + @case "unpacked_structs::handle_members_survive_heap_collections"; + } + fn unpacked_struct_four_state_members(sim) { + @case "unpacked_structs::four_state_members"; + } +} + +#[test] +fn unsupported_and_illegal_uses_are_rejected() { + let types = "typedef struct { int a; bit b; } i_t; typedef struct { int a; string s; } s_t;"; + for (body, want) in [ + ("i_t v; initial v = 5;", "not of its type"), + ("i_t v; int x; initial x = v;", "not of its type"), + ( + "function int f(int x); return x; endfunction i_t v; initial $display(\"%0d\", f(v));", + "not of its type", + ), + ( + "typedef struct { int a; bit b; } j_t; typedef struct { i_t m; } o_t; o_t o; initial o = '{m: j_t'{1, 0}};", + "not of its type", + ), + ("i_t v; s_t w; initial v = w;", "not of its type"), + ( + "class A; endclass class B; endclass typedef struct { A node; } r_t; r_t r; B b; initial r = '{node: b};", + "assignment pattern member", + ), + ( + "typedef struct { int a; bit b; } j_t; i_t v; initial v = j_t'{1, 0};", + "not of its type", + ), + ( + "typedef struct { int a; bit b; } j_t; function int f(i_t r); return r.a; endfunction j_t w; initial $display(\"%0d\", f(w));", + "not of its type", + ), + ( + "function int f(i_t r); return r.a; endfunction initial $display(\"%0d\", f(5));", + "not of its type", + ), + ("function i_t f(); return 5; endfunction", "not of its type"), + ( + "i_t v; int x; initial x = v + 1;", + "applied to an unpacked structure", + ), + ( + "i_t v; initial $display(\"%0d\", v[3]);", + "applied to an unpacked structure", + ), + ( + "i_t v; s_t w; bit x; initial x = v == w;", + "comparing an unpacked structure", + ), + ( + "i_t v; initial v = '{1, 2, 3};", + "3 items for 2 structure members", + ), + ("i_t v; initial v.nope = 1;", "structure member `v.nope`"), + ( + "i_t v; initial v += v;", + "operator `+=` on an unpacked structure", + ), + ( + "parameter i_t P = '{1, 0};", + "parameter of unpacked structure type", + ), + ("i_t q[$];", "dynamic container of unpacked structures"), + ( + "class C; i_t v; endclass", + "class property of unpacked structure type", + ), + ( + "function automatic void f(ref i_t r); r.a++; endfunction i_t v; initial f(v);", + "ref argument of `f` of unpacked structure type", + ), + ("i_t v[2];", "array of unpacked structures"), + ( + "typedef struct packed { i_t x; } p_t; p_t v;", + "unsupported packed struct member", + ), + ( + "typedef union { int a; bit b; } u_t; u_t u;", + "unpacked struct, union", + ), + ( + "function i_t g(); return '{1, 0}; endfunction function void f(); static i_t v = g(); endfunction", + "initializer of a static variable of unpacked structure type", + ), + ] { + let source = format!("module Top; {types} {body} endmodule"); + let Err(error) = Simulator::from_sv_sources(vec![(&source, Path::new("struct.sv"))], "Top") + .build_cranelift() + else { + panic!("`{body}` must be rejected"); + }; + let error = error.to_string(); + assert!(error.contains(want), "`{body}`: {error}"); + } + let source = "module Top(output i_t o); typedef struct { int a; } i_t; endmodule"; + let Err(error) = + Simulator::from_sv_sources(vec![(source, Path::new("struct.sv"))], "Top").build_cranelift() + else { + panic!("an unpacked structure port is unsupported"); + }; + let error = error.to_string(); + assert!(error.contains("port of unpacked structure type"), "{error}"); +} diff --git a/crates/celox/tests/systemverilog.rs b/crates/celox/tests/systemverilog.rs index e9875cc9f8..3063e01184 100644 --- a/crates/celox/tests/systemverilog.rs +++ b/crates/celox/tests/systemverilog.rs @@ -261,3 +261,5 @@ mod containers; #[path = "frontends/systemverilog/concurrent_assertions.rs"] mod concurrent_assertions; +#[path = "frontends/systemverilog/unpacked_structs.rs"] +mod unpacked_structs; diff --git a/docs/guide/systemverilog-reference.md b/docs/guide/systemverilog-reference.md index f0d706c6d4..f911290ad0 100644 --- a/docs/guide/systemverilog-reference.md +++ b/docs/guide/systemverilog-reference.md @@ -27,10 +27,10 @@ synthesis and is tested at the design level. | Area | Supported | | --- | --- | -| Types | `logic`, `bit`, `reg`, packed vectors, multidimensional packed arrays with ascending or descending ranges and arbitrary constant bounds, packed structs, hard and soft packed untagged unions, enums (with implicit values) and their methods `first`, `last`, `next`, `prev`, `num` and `name` on any enum-typed expression except an element of a packed array of enums ([#2417](https://github.com/celox-sim/celox/issues/2417)), `typedef`, unpacked arrays used as memories, signed and unsigned | +| Types | `logic`, `bit`, `reg`, packed vectors, multidimensional packed arrays with ascending or descending ranges and arbitrary constant bounds, packed structs, unpacked structs (see [unpacked structures](#unpacked-structures)), hard and soft packed untagged unions, enums (with implicit values) and their methods `first`, `last`, `next`, `prev`, `num` and `name` on any enum-typed expression except an element of a packed array of enums ([#2417](https://github.com/celox-sim/celox/issues/2417)), `typedef`, unpacked arrays used as memories, signed and unsigned | | Expressions | arithmetic including `**`, logic, shift, comparison, reduction, concatenation and replication, `?:`, `inside`, `==?` / `!=?`, casts (`N'(x)`, `signed'(x)`, `T'(x)`), `$signed` / `$unsigned` | | Selects | constant and run-time bit selects and indexed part-selects (`[i]`, `[i +: W]`, `[i -: W]`), in reads and writes, in either declaration direction | -| Patterns | assignment patterns for packed structs, packed arrays and unpacked arrays (`'{a, b}`, `'{x: a, default: 0}`, `'{n{a}}`, `T'{...}`) | +| Patterns | assignment patterns for packed and unpacked structs, packed arrays and unpacked arrays (`'{a, b}`, `'{x: a, default: 0}`, `'{n{a}}`, `T'{...}`) | | Dynamic containers | one-dimensional dynamic arrays, queues (including bounded queues), and associative arrays with integral or string keys; element access, value assignment, equality, `foreach` and built-in container methods. See [dynamic containers](#dynamic-containers) | | Strings | `string` variables, function arguments and results: assignment of strings, string literals and casts (`string'(x)`), `==`, `!=` and ordering comparisons, concatenation and replication (with a run-time count), character reads and writes (`s[i]`), and the methods `len`, `getc`, `putc`, `toupper`, `tolower`, `compare`, `icompare`, `substr`, `atoi` / `atohex` / `atooct` / `atobin` and `itoa` / `hextoa` / `octtoa` / `bintoa`; `%s` and format-less display of string values; text formatted by `$sformatf` / `$sformat` / `$swrite`. See [strings](#strings) | | Classes | classes declared in a module, a package or the compilation unit: properties of 2-state and 4-state integral types up to 64 bits (4-state properties start as X and keep X/Z bits), `string` and class handle types, plus dynamic arrays, queues and associative arrays, with initializers; `new` with and without arguments, implicit and explicit constructors; methods (`function` and `task`, also `extern` with an out-of-block body) called through a handle or unqualified inside the class; `this`, `null`, handle assignment and comparison; inheritance of properties and methods, with base constructors called by `super.new(...)` or `extends Base(...)`; `super.` members; virtual methods, abstract (`virtual`) classes and pure virtual methods; classes that implement interface classes and method calls through interface class handles; `$cast` of class handles, as a function and as a task; static properties and methods; `C::` scope resolution of static members and `C::new`; arrays of handles; parameterized classes with value and type parameters, parameters read through a handle or `C#(...)::`, typedefs of classes and interface classes and `C::T`; named constructor arguments; shallow copies (`new h`). See [classes](#classes) | @@ -325,6 +325,19 @@ Every construct above is covered by tests that compare the result with a software model. The shared Veryl conformance suite also runs against the SystemVerilog emitted by Veryl. +### Unpacked structures {#unpacked-structures} + +Unpacked structures (IEEE 1800-2023 7.2) may have integral, enumeration, +`string`, class handle, packed structure and nested unpacked structure +members, each keeping its own 2-state or 4-state type. Variables of such a +type may be module variables, procedural locals, and subroutine arguments of +any direction and results. Members are read and written like variables of +their type; a whole structure is assigned and compared with `==`, `!=`, +`===` and `!==` against a value of the same type, chosen with `?:`, or built +from an assignment pattern (`'{a, b}`, `'{m: v, default: d}`, `T'{...}`, with +`default` applied to the members of nested structures). Other operators, +selects and casts of a whole structure are rejected. + ## Unsupported constructs {#what-is-not-supported} Celox reports an `Unsupported` error naming the construct instead of ignoring @@ -348,7 +361,9 @@ constructs without a dedicated issue point to the frontend roadmap, [#88](https: `bind`. - Declarations: variable declaration initializers of members of an interface array, ANSI port default values, tagged unions, unpacked unions, packed union assignment patterns, internal nets without a driver ([#460](https://github.com/celox-sim/celox/issues/460)), - unpacked structs and `real`. + `real`, and unpacked structs as ports, nets, parameters, class properties, + array or container elements, or with members that are arrays, `real`, or + have default values. - Sequential processes: `iff` qualifiers in `always_ff` event lists ([#452](https://github.com/celox-sim/celox/issues/452)), and in `always_ff` edge operands other than a plain signal ([#464](https://github.com/celox-sim/celox/issues/464)), multi-bit edge signals, and several edges without an asynchronous reset. diff --git a/docs/guide/systemverilog.md b/docs/guide/systemverilog.md index 1c8b140baa..e3c4710914 100644 --- a/docs/guide/systemverilog.md +++ b/docs/guide/systemverilog.md @@ -100,7 +100,7 @@ Common reasons a design needs changes are: and `iff` in `always_ff` event controls ([#452](https://github.com/celox-sim/celox/issues/452)). - Nonblocking latch assignments, multiple drivers and tri-state buses. Inferred latches in `always_comb` require `inferred_latch = "warn"` or `"allow"` in the common `[diagnostics]` configuration. - Non-ANSI module ports, wildcard `.*` connections, gate primitives and `bind`. -- `real`, unpacked structs and several interface features. +- `real`, unpacked structs in ports, class properties and containers, and several interface features. The [complete limitations list](./systemverilog-reference#what-is-not-supported) includes narrower restrictions and tracking issues. Unsupported constructs diff --git a/docs/ja/guide/systemverilog-reference.md b/docs/ja/guide/systemverilog-reference.md index 2673da9aaa..7dbd8f3ead 100644 --- a/docs/ja/guide/systemverilog-reference.md +++ b/docs/ja/guide/systemverilog-reference.md @@ -25,11 +25,11 @@ outline: [2, 3] | 領域 | サポート内容 | | --- | --- | -| 型 | `logic`、`bit`、`reg`、packed ベクタ、任意の定数境界と昇順・降順の多次元 packed 配列、packed 構造体、hard / soft packed untagged union、enum(暗黙値を含む)と enum 型の任意の式(enum の packed 配列の要素を除く、[#2417](https://github.com/celox-sim/celox/issues/2417))に対するメソッド `first`・`last`・`next`・`prev`・`num`・`name`、`typedef`、メモリとして使う unpacked 配列、符号付き・符号なし | +| 型 | `logic`、`bit`、`reg`、packed ベクタ、任意の定数境界と昇順・降順の多次元 packed 配列、packed 構造体、unpacked 構造体([unpacked 構造体](#unpacked-structures)を参照)、hard / soft packed untagged union、enum(暗黙値を含む)と enum 型の任意の式(enum の packed 配列の要素を除く、[#2417](https://github.com/celox-sim/celox/issues/2417))に対するメソッド `first`・`last`・`next`・`prev`・`num`・`name`、`typedef`、メモリとして使う unpacked 配列、符号付き・符号なし | | 式 | `**` を含む算術、論理、シフト、比較、リダクション、連結・複製、`?:`、`inside`、`==?` / `!=?`、キャスト(`N'(x)`、`signed'(x)`、`T'(x)`)、`$signed` / `$unsigned` | | 選択 | 定数・実行時のビット選択と indexed part-select(`[i]`、`[i +: W]`、`[i -: W]`)。読み書きの両方、宣言の向きによらず | -| パターン | packed 構造体・packed 配列・unpacked 配列の assignment pattern(`'{a, b}`、`'{x: a, default: 0}`、`'{n{a}}`、`T'{...}`) | -| クラス | モジュール・package・コンパイル単位で宣言したクラス。64 ビット以下の 2 値整数型・`string`・クラスハンドル型のプロパティ(初期値つきも可)、引数あり・なしの `new`、暗黙と明示のコンストラクタ、ハンドル経由またはクラス内から修飾なしで呼ぶメソッド(`function` と `task`、本体をクラス外に書く `extern` も可)、`this`、`null`、ハンドルの代入と比較、プロパティとメソッドの継承(基底クラスのコンストラクタは `super.new(...)` か `extends Base(...)` で呼ぶ)、`super.` によるメンバ参照、virtual メソッド、抽象クラス(`virtual class`)と pure virtual メソッド、interface クラスを実装するクラスと interface クラスのハンドルを通したメソッド呼び出し、関数とタスクとしてのクラスハンドルの `$cast`、static プロパティとメソッド、static メンバと `C::new` の `C::` によるスコープ解決、ハンドルの配列、値パラメータと型パラメータを持つパラメータ付きクラス、ハンドルや `C#(...)::` を通したパラメータの参照、クラスと interface クラスの typedef と `C::T`、名前付きのコンストラクタ引数、浅いコピー(`new h`)。[クラス](#classes)を参照 | +| パターン | packed・unpacked 構造体、packed 配列、unpacked 配列の assignment pattern(`'{a, b}`、`'{x: a, default: 0}`、`'{n{a}}`、`T'{...}`) | +| クラス | モジュール・package・コンパイル単位で宣言したクラス。64 ビット以下の 2 値・4 値整数型(4 値のプロパティは X で始まり、X/Z ビットを保持します)・`string`・クラスハンドル型のプロパティ(初期値つきも可)、引数あり・なしの `new`、暗黙と明示のコンストラクタ、ハンドル経由またはクラス内から修飾なしで呼ぶメソッド(`function` と `task`、本体をクラス外に書く `extern` も可)、`this`、`null`、ハンドルの代入と比較、プロパティとメソッドの継承(基底クラスのコンストラクタは `super.new(...)` か `extends Base(...)` で呼ぶ)、`super.` によるメンバ参照、virtual メソッド、抽象クラス(`virtual class`)と pure virtual メソッド、interface クラスを実装するクラスと interface クラスのハンドルを通したメソッド呼び出し、関数とタスクとしてのクラスハンドルの `$cast`、static プロパティとメソッド、static メンバと `C::new` の `C::` によるスコープ解決、ハンドルの配列、値パラメータと型パラメータを持つパラメータ付きクラス、ハンドルや `C#(...)::` を通したパラメータの参照、クラスと interface クラスの typedef と `C::T`、名前付きのコンストラクタ引数、浅いコピー(`new h`)。[クラス](#classes)を参照 | | 文字列 | `string` 型の変数、関数の引数と戻り値。文字列・文字列リテラル・キャスト(`string'(x)`)の代入、`==` / `!=` と大小比較、連結と複製(実行時の回数も可)、文字の読み書き(`s[i]`)、メソッド `len`、`getc`、`putc`、`toupper`、`tolower`、`compare`、`icompare`、`substr`、`atoi` / `atohex` / `atooct` / `atobin`、`itoa` / `hextoa` / `octtoa` / `bintoa`。`%s` と書式なしの表示。[文字列](#strings)を参照 | | パラメータ | 整数のパラメータ、128ビットを超える幅と X/Z を保持するパラメータ上書き、assignment pattern で与える unpacked 配列型・packed 構造体型のパラメータ(定数テーブル)と unpacked 配列のパターンによる上書き | @@ -204,13 +204,24 @@ seedを使って解を探索し、成功時だけ対象プロパティをまと 上記の各構文は、ソフトウェアモデルと結果を比較するテストで確認しています。Veryl が出力する SystemVerilog に対しても、共有の Veryl 適合性スイートを実行しています。 +### unpacked 構造体 {#unpacked-structures} + +unpacked 構造体(IEEE 1800-2023 7.2)のメンバには、整数型、列挙型、`string`、 +クラスハンドル、packed 構造体、入れ子の unpacked 構造体を使え、各メンバは自身の +2 値・4 値の型を保ちます。この型の変数は、モジュール変数、手続き内のローカル変数、 +任意の方向のサブルーチン引数と戻り値にできます。メンバはその型の変数と同じように +読み書きできます。構造体全体は、同じ型の値との代入と `==`・`!=`・`===`・`!==` による +比較、`?:` による選択、assignment pattern(`'{a, b}`、`'{m: v, default: d}`、 +`T'{...}`。`default` は入れ子の構造体のメンバにも適用)による構築ができます。 +構造体全体へのその他の演算子、選択、キャストは拒否されます。 + ## 未対応の構文 {#what-is-not-supported} 未対応の構文は、無視せずに、その構文名を示す `Unsupported` エラーを返します。エラーには その構文を追跡する issue の番号が含まれます。専用の issue がない構文は、フロントエンドの ロードマップ [#88](https://github.com/celox-sim/celox/issues/88) を指します。 -- [対応範囲](#classes)外のクラスの機能:クラスハンドル以外の変数への `$cast`、クラス内で宣言した列挙定数、クラスパラメータの値としての列挙型・構造体型・共用体型と名前付きの 4 値・文字列定数、package のクラスをモジュール内クラス型で特殊化する機能、4 値・`real`・配列・64 ビットを超えるプロパティ。時間待ちを含む task の static ローカル宣言、再帰呼び出し。 +- [対応範囲](#classes)外のクラスの機能:クラスハンドル以外の変数への `$cast`、クラス内で宣言した列挙定数、クラスパラメータの値としての列挙型・構造体型・共用体型と名前付きの 4 値・文字列定数、package のクラスをモジュール内クラス型で特殊化する機能、`real`・配列・64 ビットを超えるプロパティ、4 値プロパティの乱数化と制約。時間待ちを含む task の static ローカル宣言、再帰呼び出し。 - `inout` / `ref` / 式を使う modport、virtual interface など。 ANSI ヘッダーのポートは、クロック入力やパラメータ依存の幅も含めて modport 経由で渡せます。 interface 自体と入れ子の interface は対応しています。型パラメータはインスタンスごとに解決し、modport ポート経由でも伝搬します。 @@ -225,7 +236,7 @@ SystemVerilog に対しても、共有の Veryl 適合性スイートを実行 - `always_comb` の暗黙のラッチ推論はデフォルトで拒否します。`celox.toml` の `[diagnostics]` で `inferred_latch = "warn"` または `"allow"` を指定すると許可できます。通常の `always @*` と明示的な `always_latch` にはこの設定は不要です。 - ポートとインスタンス:non-ANSI 形式のポート式(`module m({a, b}, c[3:0], .p(d))` など、[#1953](https://github.com/celox-sim/celox/issues/1953))、`ref` ポート([#427](https://github.com/celox-sim/celox/issues/427))、 ワイルドカード接続 `.*`([#442](https://github.com/celox-sim/celox/issues/442))、ゲートプリミティブ([#457](https://github.com/celox-sim/celox/issues/457))、`bind`。 -- 宣言:interface 配列のメンバーの変数宣言の初期化子、ANSI ポートの既定値、tagged union、unpacked union、packed union の assignment pattern、ドライバを持たない内部ネット([#460](https://github.com/celox-sim/celox/issues/460))、unpacked 構造体、`real`。 +- 宣言:interface 配列のメンバーの変数宣言の初期化子、ANSI ポートの既定値、tagged union、unpacked union、packed union の assignment pattern、ドライバを持たない内部ネット([#460](https://github.com/celox-sim/celox/issues/460))、`real`、ポート・ネット・パラメータ・クラスプロパティ・配列やコンテナの要素としての unpacked 構造体、配列・`real`・既定値を持つメンバ。 - 順序回路のプロセス:`always_ff` のイベントリストの `iff` 修飾([#452](https://github.com/celox-sim/celox/issues/452))、および `always_ff` での 単純な信号以外のエッジオペランド([#464](https://github.com/celox-sim/celox/issues/464))、複数ビットのエッジ信号、 非同期リセットを持たない複数のエッジ。 diff --git a/docs/ja/guide/systemverilog.md b/docs/ja/guide/systemverilog.md index cb65905103..401048927f 100644 --- a/docs/ja/guide/systemverilog.md +++ b/docs/ja/guide/systemverilog.md @@ -94,7 +94,7 @@ X/Z を扱う場合は builder に `.four_state(true)` を指定します。 `always_ff` のイベント制御の `iff`([#452](https://github.com/celox-sim/celox/issues/452))。 - ラッチ内のノンブロッキング代入、複数ドライバ、トライステートバス。`always_comb` の暗黙のラッチ推論は 共通の `[diagnostics]` 設定で `inferred_latch = "warn"` または `"allow"` を指定して許可します。 - 非 ANSI モジュールポート、ワイルドカード `.*` 接続、ゲートプリミティブ、`bind`。 -- `real`、unpacked 構造体、interface の一部の機能。 +- `real`、ポート・クラスプロパティ・コンテナの unpacked 構造体、interface の一部の機能。 [制約の一覧](./systemverilog-reference#what-is-not-supported)に、より細かな条件と追跡 issue をまとめています。 未対応の構文は、構文名と追跡 issue を含む診断を返します。