From 23214db48279c86a7e292f9aa84d67f23637777c Mon Sep 17 00:00:00 2001 From: tignear Date: Wed, 28 Oct 2026 17:30:26 +0900 Subject: [PATCH] feat(sv): support class properties of unpacked structure type A class property of an unpacked structure type was rejected. Its leaf members are now laid out as ordinary properties named by their member path (`orig.m_type`), so a `string` or class handle member is a handle property the collector traces, and a 4-state member keeps its X/Z bits. The class records the property and its structure-typed members (`Class::aggregates`) for lookup and hiding. `h.p.m` and an unqualified `p.m` inside a method resolve through the member path to the leaf property, including `h.p.m.q` through a handle member. A whole property `h.p` reads as the concatenation of its leaf loads; assigning it, from a pattern, a variable, another property, a conditional or a call, stores each leaf, evaluating the value once. Declaration initializers run in the constructor in declaration order, and a shallow copy copies the members. Methods returning a structure are now recognised when checking an assignment's type. Static, `rand` and `const` properties of an unpacked structure type stay unsupported with dedicated diagnostics. Icarus 13.0 still lacks unpacked structures; xezim 0.11.3 formats string members of such properties as packed values, which the review retains. Change-Id: I8d7ebe18fdf58ac33c7b1f2f76b7e27bf598b542 --- crates/celox-sv-analyzer/src/ast/classes.rs | 109 +++++++++- .../src/ast/expression_binding.rs | 10 +- .../src/ast/expression_binding/direct.rs | 20 ++ .../src/ast/expression_binding/objects.rs | 203 +++++++++++++++--- crates/celox-sv-analyzer/src/ast/inlining.rs | 88 ++++++++ .../celox-sv-analyzer/src/ast/procedural.rs | 175 ++++++++++++++- .../src/ast/unpacked_structs.rs | 23 +- crates/celox-test-suite/LIMITATIONS.md | 2 +- .../src/sv/cases/unpacked_structs.vtest | 172 +++++++++++++++ .../sv_xezim_unpacked_struct_properties.sv | 23 ++ .../sv/icarus/unpacked_structs.json | 92 ++++++++ .../verification/sv/limitations.json | 4 + .../sv/verilator/unpacked_structs.json | 44 ++++ .../sv/xezim/unpacked_structs.json | 44 ++++ .../verification/xezim-review.json | 32 +++ .../systemverilog/unpacked_structs.rs | 132 +++++++++++- docs/guide/systemverilog-reference.md | 14 +- docs/guide/systemverilog.md | 2 +- docs/ja/guide/systemverilog-reference.md | 8 +- docs/ja/guide/systemverilog.md | 2 +- 20 files changed, 1133 insertions(+), 66 deletions(-) create mode 100644 crates/celox-test-suite/verification/repros/sv_xezim_unpacked_struct_properties.sv diff --git a/crates/celox-sv-analyzer/src/ast/classes.rs b/crates/celox-sv-analyzer/src/ast/classes.rs index fd0d179b66..a7bdf10cb3 100644 --- a/crates/celox-sv-analyzer/src/ast/classes.rs +++ b/crates/celox-sv-analyzer/src/ast/classes.rs @@ -58,6 +58,22 @@ impl Property { } } +/// A property of unpacked structure type, or a member of one that is itself +/// an unpacked structure. It is not laid out: its leaf members are +/// properties named by their member path (see `unpacked_structs`). +#[derive(Debug, Clone, PartialEq, Eq)] +pub(super) struct AggregateProperty { + pub name: String, + pub owner: String, + pub local: bool, + pub protected: bool, + pub r#type: Type, + pub initializer: Option, + /// How many properties precede it: a later declaration of the same + /// name hides an earlier one. + pub position: usize, +} + /// A static property of a class: one variable of the module that declares /// the class, shared by all of its objects (IEEE 1800-2023 8.9). #[derive(Debug, Clone, PartialEq, Eq)] @@ -123,6 +139,8 @@ pub(super) struct Class { pub properties: Vec, /// How many of `properties` the bases declare. pub base_properties: usize, + /// Properties of unpacked structure type of the class and its bases. + pub aggregates: Vec, /// Static properties of the class and its bases. pub statics: Vec, /// Methods visible in the class, overrides replacing inherited ones. @@ -195,10 +213,42 @@ impl Class { /// The property `name`; one the class declares hides one of a base /// (IEEE 1800-2023 8.14). pub fn property(&self, name: &str) -> Option<&Property> { - self.properties + let (index, property) = self + .properties + .iter() + .enumerate() + .rev() + .find(|(_, property)| property.name == name)?; + (!self.hidden(name, index + 1, index)).then_some(property) + } + + /// The property or member of unpacked structure type `name`. + pub fn aggregate(&self, name: &str) -> Option<&AggregateProperty> { + let aggregate = self + .aggregates .iter() .rev() - .find(|property| property.name == name) + .find(|aggregate| aggregate.name == name)?; + (!self.hidden(name, aggregate.position, aggregate.position)).then_some(aggregate) + } + + /// Whether a later declaration hides the property or member `name`: a + /// property from position `properties` on, or a property of unpacked + /// structure type after position `aggregates`, of that name or of a + /// structure that contains it (`p` for `p.a`). + fn hidden(&self, name: &str, properties: usize, aggregates: usize) -> bool { + let paths = name + .match_indices('.') + .map(|(end, _)| &name[..end]) + .chain([name]) + .collect::>(); + self.properties + .iter() + .enumerate() + .any(|(index, property)| index >= properties && paths.contains(&property.name.as_str())) + || self.aggregates.iter().any(|aggregate| { + aggregate.position > aggregates && paths.contains(&aggregate.name.as_str()) + }) } pub fn static_property(&self, name: &str) -> Option<&StaticProperty> { @@ -746,6 +796,10 @@ pub(super) fn class( .map(|base| base.properties.clone()) .unwrap_or_default(); let base_properties = properties.len(); + let mut aggregates = base + .as_ref() + .map(|base| base.aggregates.clone()) + .unwrap_or_default(); let mut statics = base .as_ref() .map(|base| base.statics.clone()) @@ -782,6 +836,55 @@ pub(super) fn class( return Err(unsupported("const dynamic container class property")); } let property_name = instances::normalize_identifier(&variable.name.spelling); + if let Some(aggregate) = ty.unpacked_struct() { + if property.qualifiers.is_static { + return Err(unsupported( + "static class property of unpacked structure type", + )); + } + if property.qualifiers.rand || property.qualifiers.randc { + return Err(unsupported( + "random class property of unpacked structure type", + )); + } + if property.declaration.constant { + return Err(unsupported( + "const class property of unpacked structure type", + )); + } + for (aggregate_name, aggregate_type) in + unpacked_structs::aggregates(&property_name, &ty) + { + aggregates.push(AggregateProperty { + initializer: (aggregate_name == property_name) + .then_some(variable.value) + .flatten(), + name: aggregate_name, + owner: name.to_owned(), + local: property.qualifiers.local, + protected: property.qualifiers.protected, + r#type: aggregate_type, + position: properties.len(), + }); + } + for leaf in aggregate.leaves() { + let field = field(&tree.state, &leaf.r#type, env, size)?; + size = field.offset + field.storage(); + properties.push(Property { + name: leaf.name(&property_name), + owner: name.to_owned(), + constant: false, + local: property.qualifiers.local, + protected: property.qualifiers.protected, + r#type: leaf.r#type, + field, + rand: false, + randc: false, + initializer: None, + }); + } + continue; + } if property.qualifiers.is_static { statics.retain(|known| known.name != property_name); statics.push(StaticProperty { @@ -946,6 +1049,7 @@ pub(super) fn class( is_interface: false, properties, base_properties, + aggregates, statics, methods, declarations: Vec::new(), @@ -1066,6 +1170,7 @@ fn interface_class( constraints: Vec::new(), properties: Vec::new(), base_properties: 0, + aggregates: Vec::new(), statics: Vec::new(), methods, declarations, diff --git a/crates/celox-sv-analyzer/src/ast/expression_binding.rs b/crates/celox-sv-analyzer/src/ast/expression_binding.rs index 730ee1d4d0..2fb1a538e7 100644 --- a/crates/celox-sv-analyzer/src/ast/expression_binding.rs +++ b/crates/celox-sv-analyzer/src/ast/expression_binding.rs @@ -18,12 +18,14 @@ pub(super) use direct::{ }; pub(super) use lvalues::{net_lvalue, variable_lvalue}; pub(super) use objects::{ - Cast as ObjectCast, Receiver as ObjectReceiver, cast as object_cast, + Cast as ObjectCast, Receiver as ObjectReceiver, + aggregate_property as object_aggregate_property, cast as object_cast, check_access as check_member_access, check_assignment as check_handle_assignment, constraint_mode_call as object_constraint_mode_call, constructor as object_constructor, - method as object_method, named_class, native_class as object_native_class, - property as object_property, rand_mode_call as object_rand_mode_call, - receiver as object_receiver, static_property as object_static_property, this as object_this, + method as object_method, method_result as object_method_result, named_class, + native_class as object_native_class, property as object_property, + rand_mode_call as object_rand_mode_call, receiver as object_receiver, + static_property as object_static_property, this as object_this, }; pub(super) use strings::{ call as string_call, method as string_method, native_string, string_value, 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 54e4c83558..530288d7e7 100644 --- a/crates/celox-sv-analyzer/src/ast/expression_binding/direct.rs +++ b/crates/celox-sv-analyzer/src/ast/expression_binding/direct.rs @@ -485,6 +485,16 @@ pub(in crate::ast) fn bind_value( values.push(value); continue; } + if let Some((aggregate, class, _)) = + objects::aggregate_property(tree, id, native, dimensions)? + { + values.push(objects::aggregate_load( + &aggregate, + &class, + &objects::this(), + )?); + continue; + } if let Some(value) = objects::unqualified_property(tree, dimensions, &name)? { values.push(value); @@ -935,6 +945,16 @@ pub(in crate::ast) fn bind_value( ); continue; } + ExprKind::Member { .. } + if objects::aggregate_property(tree, id, native, dimensions)?.is_some() => + { + let (aggregate, class, receiver) = + objects::aggregate_property(tree, id, native, dimensions)? + .expect("checked by the guard"); + let handle = objects::receiver(tree, receiver, native, dimensions)?; + values.push(objects::aggregate_load(&aggregate, &class, &handle)?); + continue; + } ExprKind::Member { .. } if objects::property(tree, id, native, dimensions)?.is_some() => { 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 f436e4d63e..dc66344bd0 100644 --- a/crates/celox-sv-analyzer/src/ast/expression_binding/objects.rs +++ b/crates/celox-sv-analyzer/src/ast/expression_binding/objects.rs @@ -86,8 +86,10 @@ pub(in crate::ast) fn native_class( name, } = &expression.kind && separator == "." + // Otherwise a member of a property of unpacked structure type, found + // as a property below. + && let Some(owner) = native_class(tree, *receiver, native, dimensions) { - let owner = native_class(tree, *receiver, native, dimensions)?; let member = instances::normalize_identifier(&name.spelling); let receiver = (!is_super(*receiver, native)).then_some(*receiver); if let Some((property, _)) = @@ -393,40 +395,10 @@ pub(in crate::ast) fn property( native: &Declarations, dimensions: &PackedDimensions, ) -> Converted)>> { - let (class_name, name, receiver) = match &native.expression(ungroup(id, native)).kind { - ExprKind::Member { - expression, - separator, - name, - } if separator == "." => match native_class(tree, *expression, native, dimensions) { - Some(class) => ( - class, - name, - (!is_super(*expression, native)).then_some(*expression), - ), - None => return Ok(None), - }, - ExprKind::Name(name) => { - if dimensions - .get(&instances::normalize_identifier(&name.spelling)) - .is_some() - { - return Ok(None); - } - match scope_class(dimensions) { - Some(class) => (class, name, None), - None => return Ok(None), - } - } - _ => return Ok(None), + let Some((class, name, receiver)) = member_chain(tree, id, native, dimensions)? else { + return Ok(None); }; - property_in( - tree, - dimensions, - &class_name, - &instances::normalize_identifier(&name.spelling), - receiver, - ) + property_of(tree, dimensions, &class, &name, receiver) } /// The instance property `name` of class `class_name`, read through @@ -442,6 +414,18 @@ fn property_in( return Ok(None); } let class = class(tree, dimensions, class_name)?; + property_of(tree, dimensions, &class, name, receiver) +} + +/// The instance property `name` of `class`, read through `receiver` as +/// [`property`] returns it. +fn property_of( + tree: &AnalysisContext<'_>, + dimensions: &PackedDimensions, + class: &Class, + name: &str, + receiver: Option, +) -> Converted)>> { let Some(property) = class.property(name) else { if let Some(property) = class.static_property(name) { check_access( @@ -472,6 +456,122 @@ fn property_in( Ok(Some((property.clone(), receiver))) } +/// The property of unpacked structure type, or member of one, that native +/// expression `id` names, with the receiver as for [`property`]. +pub(in crate::ast) fn aggregate_property( + tree: &AnalysisContext<'_>, + id: ExprId, + native: &Declarations, + dimensions: &PackedDimensions, +) -> Converted, Option)>> { + let Some((class, name, receiver)) = member_chain(tree, id, native, dimensions)? else { + return Ok(None); + }; + let Some(aggregate) = class.aggregate(&name).cloned() else { + return Ok(None); + }; + check_access( + tree, + dimensions, + &aggregate.owner, + &aggregate.name, + aggregate.local, + aggregate.protected, + )?; + if receiver.is_none() && dimensions.get(THIS).is_none() { + return Err(unsupported(format!( + "non-static property `{}` in a static method", + aggregate.name + ))); + } + Ok(Some((aggregate, class, receiver))) +} + +/// The flat value of property `aggregate` of `class` in the object +/// `handle`: the concatenation of its leaf properties. +pub(in crate::ast) fn aggregate_load( + aggregate: &classes::AggregateProperty, + class: &Class, + handle: &Expr, +) -> Converted { + let leaves = aggregate + .r#type + .unpacked_struct() + .expect("an unpacked structure property") + .leaves(); + // Each member is read through the receiver, so it must not change: a + // subroutine call, pop or `$urandom` would be made once per member. + if leaves.len() > 1 && inlining::effectful(handle) { + return Err(unsupported( + "whole unpacked structure property read through a receiver with a side effect", + )); + } + Ok(Expr::Concat( + leaves + .iter() + .map(|leaf| { + let property = class + .property(&leaf.name(&aggregate.name)) + .ok_or_else(|| unsupported("unpacked structure property member"))?; + Ok(load(property, handle.clone())) + }) + .collect::>>()?, + )) +} + +/// The class, property path and receiver of `h.p`, `h.p.m`, or an +/// unqualified `p`/`p.m` inside a method of its class. A path names a +/// member of a property of unpacked structure type. +fn member_chain( + tree: &AnalysisContext<'_>, + id: ExprId, + native: &Declarations, + dimensions: &PackedDimensions, +) -> Converted, String, Option)>> { + let mut names = Vec::new(); + let mut current = ungroup(id, native); + let (class_name, receiver) = loop { + match &native.expression(current).kind { + ExprKind::Member { + expression, + separator, + name, + } if separator == "." => { + names.push(instances::normalize_identifier(&name.spelling)); + if let Some(class) = native_class(tree, *expression, native, dimensions) { + break ( + class, + (!is_super(*expression, native)).then_some(*expression), + ); + } + current = ungroup(*expression, native); + } + ExprKind::Name(name) => { + let name = instances::normalize_identifier(&name.spelling); + if dimensions.get(&name).is_some() { + return Ok(None); + } + names.push(name); + match scope_class(dimensions) { + Some(class) => break (class, None), + None => return Ok(None), + } + } + _ => return Ok(None), + } + }; + names.reverse(); + // A process handle has no properties (IEEE 1800-2023 9.7). + if class_name == super::super::processes::CLASS { + return Ok(None); + } + Ok(Some(( + class(tree, dimensions, &class_name)?, + names.join("."), + receiver, + ))) +} + /// The handle of the object that `receiver` of [`property`] refers to. pub(in crate::ast) fn receiver( tree: &AnalysisContext<'_>, @@ -1323,3 +1423,38 @@ pub(super) fn randomize_with( arg_order: Vec::new(), }) } + +/// The declared result type of the method that native callee expression +/// `id` names (`h.m`, `C::m`, or unqualified inside a method), when known. +pub(in crate::ast) fn method_result( + tree: &AnalysisContext<'_>, + id: ExprId, + native: &Declarations, + dimensions: &PackedDimensions, +) -> Converted> { + let (class_name, name) = match &native.expression(ungroup(id, native)).kind { + ExprKind::Member { + expression, + separator, + name, + } => match separator.as_str() { + "." => (native_class(tree, *expression, native, dimensions), name), + "::" => (named_class(tree, *expression, native, dimensions)?, name), + _ => return Ok(None), + }, + ExprKind::Name(name) + if dimensions + .get(&instances::normalize_identifier(&name.spelling)) + .is_none() => + { + (scope_class(dimensions), name) + } + _ => return Ok(None), + }; + let Some(class_name) = class_name.filter(|name| name != super::super::processes::CLASS) else { + return Ok(None); + }; + Ok(class(tree, dimensions, &class_name)? + .method(&instances::normalize_identifier(&name.spelling)) + .and_then(|method| method.result.clone().flatten())) +} diff --git a/crates/celox-sv-analyzer/src/ast/inlining.rs b/crates/celox-sv-analyzer/src/ast/inlining.rs index da4b901db5..8329653334 100644 --- a/crates/celox-sv-analyzer/src/ast/inlining.rs +++ b/crates/celox-sv-analyzer/src/ast/inlining.rs @@ -145,6 +145,94 @@ pub(super) fn expr_signedness_with_return_types( } } +/// Whether a call of `callee` only reads: a container element, size or +/// membership, an object property, a string operation or an enumeration +/// lookup. +fn reads_only(callee: &Callee) -> bool { + match callee { + Callee::String(_) | Callee::Lookup { .. } => true, + Callee::Object(op) => matches!( + **op, + ObjectOp::Load(_) + | ObjectOp::Container { + op: ContainerOp::Get | ContainerOp::Size | ContainerOp::Exists, + .. + } + ), + _ => false, + } +} + +/// Whether evaluating `expr` calls anything that does not only read (see +/// [`reads_only`]), also in the positions of a select. +pub(super) fn effectful(expr: &Expr) -> bool { + fn constant(expr: &ConstExpr) -> bool { + match expr { + ConstExpr::Function { callee, args, .. } => { + !reads_only(callee) || args.iter().any(constant) + } + ConstExpr::SelectRange { expr, msb, lsb, .. } => { + constant(expr) || constant(msb) || constant(lsb) + } + ConstExpr::Select { expr, bit } => constant(expr) || constant(bit), + ConstExpr::Resize { expr, .. } | ConstExpr::Unary { expr, .. } => constant(expr), + ConstExpr::Binary { left, right, .. } => constant(left) || constant(right), + ConstExpr::Mux { + condition, + then_expr, + else_expr, + } => constant(condition) || constant(then_expr) || constant(else_expr), + ConstExpr::Literal(_) | ConstExpr::Ident(_) => false, + } + } + let mut pending = vec![expr]; + while let Some(expr) = pending.pop() { + match expr { + Expr::Call { callee, args, .. } => { + if !reads_only(callee) { + return true; + } + pending.extend(args.iter().flatten()); + } + Expr::Select { expr, msb, lsb, .. } => { + if constant(msb) || constant(lsb) { + return true; + } + pending.push(expr) + } + Expr::Resize { expr, .. } | Expr::Unary { expr, .. } => pending.push(expr), + Expr::Concat(parts) | Expr::RepeatConcat { parts, .. } => pending.extend(parts), + Expr::Binary { left, right, .. } => { + pending.push(left); + pending.push(right); + } + Expr::Mux { + condition, + then_expr, + else_expr, + } => { + pending.push(condition); + pending.push(then_expr); + pending.push(else_expr); + } + Expr::Inside { expr, items } => { + pending.push(expr); + for item in items { + match item { + InsideItem::Value(value) => pending.push(value), + InsideItem::Range { low, high } => { + pending.push(low); + pending.push(high); + } + } + } + } + Expr::Ident(_) | Expr::Literal(_) => {} + } + } + false +} + /// Whether `expr` calls a subroutine, which may have side effects. pub(super) fn calls_subroutine(expr: &Expr) -> bool { fn constant(expr: &ConstExpr) -> bool { diff --git a/crates/celox-sv-analyzer/src/ast/procedural.rs b/crates/celox-sv-analyzer/src/ast/procedural.rs index cb235b2722..e194c29f6d 100644 --- a/crates/celox-sv-analyzer/src/ast/procedural.rs +++ b/crates/celox-sv-analyzer/src/ast/procedural.rs @@ -341,8 +341,20 @@ impl<'s, 't, 'a> BodyBuilder<'s, 't, 'a> { /// it (IEEE 1800-2023 8.9). fn enter_class_scope(&mut self, class: &classes::Class) { self.dims.class_scope = Some(class.name.clone()); - for property in &class.properties { - let entry = self.take_binding(&property.name, None, 0); + // The name of a property of unpacked structure type, whose members + // are properties named by their member paths, is hidden too. + let aggregates = class + .aggregates + .iter() + .filter(|aggregate| !aggregate.name.contains('.')) + .map(|aggregate| aggregate.name.as_str()); + for name in class + .properties + .iter() + .map(|property| property.name.as_str()) + .chain(aggregates) + { + let entry = self.take_binding(name, None, 0); self.scopes .last_mut() .expect("a body always has a scope") @@ -1193,6 +1205,11 @@ impl<'s, 't, 'a> BodyBuilder<'s, 't, 'a> { "native assignment operator mode disagrees".into(), )); } + if let Some(stmts) = + self.aggregate_property_assignment(operator, *nonblocking, *left, *right)? + { + return Ok(stmts); + } Ok(vec![self.assignment_value( if *nonblocking { "=" } else { operator }, *nonblocking, @@ -2040,7 +2057,14 @@ impl<'s, 't, 'a> BodyBuilder<'s, 't, 'a> { } Some(ForInitialization::Assignments(values)) => { for value in values { - init.push(self.assignment_value("=", false, (value.left, value.right))?); + match self.aggregate_property_assignment("=", false, value.left, value.right)? { + Some(stmts) => init.extend(stmts), + None => init.push(self.assignment_value( + "=", + false, + (value.left, value.right), + )?), + } } } } @@ -2050,6 +2074,18 @@ impl<'s, 't, 'a> BodyBuilder<'s, 't, 'a> { .transpose()?; let mut steps = Vec::new(); for step in &header.steps { + if let ForStep::Assignment { + left, + operator, + right, + .. + } = step + && let Some(stmts) = + self.aggregate_property_assignment(operator, false, *left, *right)? + { + steps.extend(stmts); + continue; + } steps.push(match step { ForStep::Assignment { left, operator, right, .. } => self.assignment_value(operator, false, (*left, *right))?, ForStep::Expression(id) => { @@ -3285,7 +3321,9 @@ impl BodyBuilder<'_, '_, '_> { .collect(), }); } - for property in class.own_properties() { + let own = class.base_properties..class.properties.len(); + for (index, property) in own.clone().zip(class.own_properties()) { + body.extend(self.aggregate_initializers(class, this, index)?); let Some(initializer) = property.initializer else { continue; }; @@ -3328,6 +3366,135 @@ impl BodyBuilder<'_, '_, '_> { vec![Expr::Ident(this.to_owned()), value], ))); } + body.extend(self.aggregate_initializers(class, this, own.end)?); + Ok(body) + } + + /// The initializers of the properties of unpacked structure type that + /// `class` declares before its property `position`. + fn aggregate_initializers( + &mut self, + class: &classes::Class, + this: &str, + position: usize, + ) -> Converted> { + let mut body = Vec::new(); + for aggregate in &class.aggregates { + if aggregate.owner != class.name || aggregate.position != position { + continue; + } + if let Some(initializer) = aggregate.initializer { + body.extend(self.aggregate_store( + aggregate, + class, + Expr::Ident(this.to_owned()), + initializer, + )?); + } + } + Ok(body) + } + + /// An assignment to a whole class property of unpacked structure type, + /// `h.p = v`: a store of each of its leaf properties. + fn aggregate_property_assignment( + &mut self, + op: &str, + nonblocking: bool, + left: celox_sv_syntax::ExprId, + right: celox_sv_syntax::ExprId, + ) -> Converted>> { + let native = self.tree.declarations(); + let Some((aggregate, class, receiver)) = + expression_binding::object_aggregate_property(self.tree, left, native, &self.dims)? + else { + return Ok(None); + }; + if nonblocking { + return Err(unsupported("nonblocking assignment to a class property")); + } + if op != "=" { + return Err(unsupported(format!( + "operator `{op}` on an unpacked structure" + ))); + } + let mut handle = + expression_binding::object_receiver(self.tree, receiver, native, &self.dims)?; + self.rename_expr(&mut handle); + self.aggregate_store(&aggregate, &class, handle, right) + .map(Some) + } + + /// Store the value of native `right` into each leaf property of + /// `aggregate` of the object `handle`. + fn aggregate_store( + &mut self, + aggregate: &classes::AggregateProperty, + class: &classes::Class, + handle: Expr, + right: celox_sv_syntax::ExprId, + ) -> Converted> { + let native = self.tree.declarations(); + let structure = aggregate + .r#type + .unpacked_struct() + .expect("an unpacked structure property") + .clone(); + unpacked_structs::check_value(&structure, right, native, self.tree, &self.dims)?; + let mut value = expression_binding::bind_value( + right, + native, + self.tree, + &self.dims, + Some(&dimensions_from_type(&aggregate.r#type)), + )?; + self.rename_expr(&mut value); + let leaves = structure.leaves(); + let mut body = Vec::new(); + // Evaluate the whole value, which may read the members it replaces, + // and the object it is stored in once, before any member is written. + let mut flat = aggregate.r#type.clone(); + flat.unpacked_struct = None; + let temp = self.declare("$aggregate", flat); + body.push(Stmt::Local { + name: temp.clone(), + init: None, + }); + body.push(Stmt::Assign { + lhs: LValue::Ident(temp.clone()), + rhs: value, + nonblocking: false, + }); + let handle = if inlining::calls_subroutine(&handle) { + let receiver = self.declare("$receiver", classes::handle(&class.name)); + body.push(Stmt::Local { + name: receiver.clone(), + init: None, + }); + body.push(Stmt::Assign { + lhs: LValue::Ident(receiver.clone()), + rhs: handle, + nonblocking: false, + }); + Expr::Ident(receiver) + } else { + handle + }; + let parts = leaves.iter().map(|leaf| Expr::Select { + expr: Box::new(Expr::Ident(temp.clone())), + msb: ConstExpr::Literal((leaf.offset + leaf.width - 1).to_string()), + lsb: ConstExpr::Literal(leaf.offset.to_string()), + signed: false, + }); + for (leaf, part) in leaves.iter().zip(parts) { + let property = class + .property(&leaf.name(&aggregate.name)) + .ok_or_else(|| unsupported("unpacked structure property member"))?; + body.push(Stmt::Eval(classes::object_call( + system_functions::ObjectOp::Store(property.field), + vec![handle.clone(), part], + ))); + } Ok(body) } } diff --git a/crates/celox-sv-analyzer/src/ast/unpacked_structs.rs b/crates/celox-sv-analyzer/src/ast/unpacked_structs.rs index 0b7bb83d67..1262333ff4 100644 --- a/crates/celox-sv-analyzer/src/ast/unpacked_structs.rs +++ b/crates/celox-sv-analyzer/src/ast/unpacked_structs.rs @@ -239,13 +239,19 @@ pub(in crate::ast) fn native_struct( ) -> Converted>> { let id = expression_binding::ungroup(id, native); Ok(match &native.expression(id).kind { - ExprKind::Name(_) | ExprKind::Member { .. } => member_path(id, native) + ExprKind::Name(_) | ExprKind::Member { .. } => match 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()), + .and_then(|parts| dims.get(&parts[0])?.unpacked_struct.clone()) + { + Some(aggregate) => Some(aggregate), + // A class property of unpacked structure type. + None => expression_binding::object_aggregate_property(tree, id, native, dims)? + .and_then(|(aggregate, _, _)| aggregate.r#type.unpacked_struct().cloned()), + }, ExprKind::Conditional { then_expression, else_expression, @@ -265,11 +271,16 @@ pub(in crate::ast) fn native_struct( } if std::ptr::eq(native, tree.declarations()) => { patterns::typed_shape(id, tree, dims)?.and_then(|shape| shape.unpacked_struct) } + // A method of the scope class hides a module function of its name, + // as call binding resolves it. 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()) + match expression_binding::object_method_result(tree, *expression, native, dims)? { + Some(ty) => ty.unpacked_struct().cloned(), + None => 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, }) diff --git a/crates/celox-test-suite/LIMITATIONS.md b/crates/celox-test-suite/LIMITATIONS.md index 9255cac710..70155d7e06 100644 --- a/crates/celox-test-suite/LIMITATIONS.md +++ b/crates/celox-test-suite/LIMITATIONS.md @@ -627,7 +627,7 @@ 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_icarus_unpacked_structs` | compile | icarus | 12 | 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 diff --git a/crates/celox-test-suite/src/sv/cases/unpacked_structs.vtest b/crates/celox-test-suite/src/sv/cases/unpacked_structs.vtest index 507b071581..ebf1175bad 100644 --- a/crates/celox-test-suite/src/sv/cases/unpacked_structs.vtest +++ b/crates/celox-test-suite/src/sv/cases/unpacked_structs.vtest @@ -277,3 +277,175 @@ 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")) + +(case class_properties + (source "unpacked_struct_properties.sv" #" +module Top(output bit done); + class Node; + string n; + function new(string n); + this.n = n; + endfunction + endclass + typedef struct { Node m_type; string m_type_name; } pair_t; + typedef struct { time read_time; int unsigned read_count; bit [3:0] l; } access_t; + class Override; + string path; + pair_t orig; + pair_t ovrd = '{null, "init"}; + access_t acc; + function new(string p, string on, Node ot); + path = p; + this.orig.m_type_name = on; + this.orig.m_type = ot; + endfunction + function bit pair_matches(pair_t m, Node t); + return m.m_type == t && m.m_type_name != ""; + endfunction + function void touch(time now); + acc.read_count++; + acc.read_time = now; + endfunction + endclass + initial begin + Node a; + Override o, c; + pair_t tp; + access_t x; + a = new("A"); + o = new("p", "A", a); + $display("%s %s %s [%s]", o.orig.m_type_name, o.orig.m_type.n, o.ovrd.m_type_name, o.path); + tp = o.orig; + tp.m_type_name = "changed"; + $display("%s %s %0d %0d", o.orig.m_type_name, tp.m_type_name, o.pair_matches(o.orig, a), o.orig == tp); + o.ovrd = tp; + o.ovrd.m_type = null; + $display("%s %0d %0d", o.ovrd.m_type_name, o.ovrd.m_type == null, o.orig.m_type == null); + $display("acc %0d %0d %b", o.acc.read_time, o.acc.read_count, o.acc.l); + o.touch(5); + o.acc.l = 4'b1011; + c = new o; + c.acc.read_count = 9; + x = c.acc; + $display("acc %0d %0d %b | %0d %0d %b", o.acc.read_time, o.acc.read_count, o.acc.l, x.read_time, x.read_count, x.l); + o.acc = '{default: 0}; + $display("acc %0d %0d %b", o.acc.read_time, o.acc.read_count, o.acc.l); + done = 1; + end +endmodule +"#) + (top Top) (run_until 0) (assert_eq done 1) + (expect_output "A A init [p]\nA changed 1 0\nchanged 1 0\nacc 0 0 0000\nacc 5 1 1011 | 5 9 1011\nacc 0 0 0000\n")) + +(case class_property_assignment_order + (source "unpacked_struct_property_order.sv" #" +module Top(output bit done); + typedef struct { int a; int b; } pair_t; + class Holder; + pair_t p; + endclass + Holder items[3]; + int i; + function automatic int next_index(); + i++; + return i; + endfunction + initial begin + Holder h; + h = new; + h.p.a = 1; + h.p.b = 2; + h.p = '{h.p.b, h.p.a}; + $display("%0d %0d", h.p.a, h.p.b); + foreach (items[k]) begin + items[k] = new; + items[k].p = '{k, k * 10}; + end + i = 0; + items[next_index()].p = '{7, 8}; + $display("%0d %0d %0d %0d %0d", items[1].p.a, items[1].p.b, items[2].p.a, items[2].p.b, i); + done = 1; + end +endmodule +"#) + (top Top) (run_until 0) (assert_eq done 1) + (expect_output "2 1\n7 8 2 20 1\n")) + +(case class_property_four_state_members + (four_state) + (source "unpacked_struct_property_states.sv" #" +module Top(output bit done); + typedef struct { time t; int i; logic [3:0] l; } access_t; + class Holder; + access_t acc; + endclass + Holder h; + access_t copy; + initial begin + h = new; + $display("%0d %0d %b", h.acc.t, h.acc.i, h.acc.l); + h.acc.l = 4'b1z0x; + copy = h.acc; + $display("%b %0d", copy.l, copy === h.acc); + done = 1; + end +endmodule +"#) + (top Top) (run_until 0) (assert_eq done 1) + (expect_output "x 0 xxxx\n1z0x 1\n")) + +(case class_properties_inheritance_and_collections + (source "unpacked_struct_property_heap.sv" #" +module Top(output int n); + class Node; + string text; + endclass + typedef struct { Node m_type; string m_type_name; } pair_t; + class Base; + pair_t orig; + function pair_t get(); + return orig; + endfunction + function bit same(pair_t p); + return orig == p; + endfunction + function void set(string s); + orig = '{m_type: null, m_type_name: s}; + endfunction + endclass + class Derived extends Base; + pair_t ovrd; + function void swap(); + pair_t t; + t = orig; + orig = ovrd; + ovrd = t; + endfunction + endclass + logic clk = 0; + always #1 clk = ~clk; + Derived d; + string number; + always @(posedge clk) begin + number.itoa(n); + d.set({number, "!"}); + d.ovrd.m_type = new; + d.ovrd.m_type.text = number; + n = n + 1; + end + initial begin + pair_t g; + d = new; + d.set("x"); + g = d.get(); + $display("%s %0d", g.m_type_name, d.same(g)); + d.ovrd.m_type_name = "o"; + d.swap(); + $display("%s %s", d.orig.m_type_name, d.ovrd.m_type_name); + #6000 $display("[%s] [%s] %0d", d.orig.m_type_name, d.ovrd.m_type.text, n); + end +endmodule +"#) + (top Top) + (run_until 6000) + (expect_output "x 1\no x\n[2999!] [2999] 3000\n")) diff --git a/crates/celox-test-suite/verification/repros/sv_xezim_unpacked_struct_properties.sv b/crates/celox-test-suite/verification/repros/sv_xezim_unpacked_struct_properties.sv new file mode 100644 index 0000000000..745b6e0d9e --- /dev/null +++ b/crates/celox-test-suite/verification/repros/sv_xezim_unpacked_struct_properties.sv @@ -0,0 +1,23 @@ +// xezim 0.11.3: a string member of a class property of unpacked structure +// type prints with %s as a wide packed value padded with spaces, and a +// property of an object held in a handle member reads empty (IEEE 1800-2023 +// 7.2, 8.4, 21.2.1.7). Expected: "[A] [A]". +module top; + class Node; + string n; + endclass + typedef struct { Node m_type; string m_type_name; } pair_t; + class Holder; + pair_t orig; + endclass + initial begin + Holder h; + Node a; + h = new; + a = new; + a.n = "A"; + h.orig.m_type = a; + h.orig.m_type_name = "A"; + $display("[%s] [%s]", h.orig.m_type_name, h.orig.m_type.n); + end +endmodule diff --git a/crates/celox-test-suite/verification/sv/icarus/unpacked_structs.json b/crates/celox-test-suite/verification/sv/icarus/unpacked_structs.json index 6b919f07e5..49cfe0947a 100644 --- a/crates/celox-test-suite/verification/sv/icarus/unpacked_structs.json +++ b/crates/celox-test-suite/verification/sv/icarus/unpacked_structs.json @@ -23,6 +23,98 @@ "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::class_properties", + "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::class_properties_inheritance_and_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::class_property_assignment_order", + "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::class_property_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.", diff --git a/crates/celox-test-suite/verification/sv/limitations.json b/crates/celox-test-suite/verification/sv/limitations.json index 9f1345b58d..cf96d93d2d 100644 --- a/crates/celox-test-suite/verification/sv/limitations.json +++ b/crates/celox-test-suite/verification/sv/limitations.json @@ -2984,6 +2984,10 @@ "cases": { "icarus": [ "unpacked_structs::assignment_patterns", + "unpacked_structs::class_properties", + "unpacked_structs::class_properties_inheritance_and_collections", + "unpacked_structs::class_property_assignment_order", + "unpacked_structs::class_property_four_state_members", "unpacked_structs::four_state_members", "unpacked_structs::handle_members_survive_heap_collections", "unpacked_structs::initializers_results_and_defaults", diff --git a/crates/celox-test-suite/verification/sv/verilator/unpacked_structs.json b/crates/celox-test-suite/verification/sv/verilator/unpacked_structs.json index 1344517bc8..e2b3c12639 100644 --- a/crates/celox-test-suite/verification/sv/verilator/unpacked_structs.json +++ b/crates/celox-test-suite/verification/sv/verilator/unpacked_structs.json @@ -23,6 +23,50 @@ "tag_reasons": {}, "tags": [] }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::class_properties", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::class_properties_inheritance_and_collections", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::class_property_assignment_order", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "four-state expectations", + "expectation": "Simulation", + "name": "unpacked_structs::class_property_four_state_members", + "phase": "compile", + "status": "unsupported", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, { "category": "Types", "detail": "four-state expectations", diff --git a/crates/celox-test-suite/verification/sv/xezim/unpacked_structs.json b/crates/celox-test-suite/verification/sv/xezim/unpacked_structs.json index 736d734292..9da0008949 100644 --- a/crates/celox-test-suite/verification/sv/xezim/unpacked_structs.json +++ b/crates/celox-test-suite/verification/sv/xezim/unpacked_structs.json @@ -11,6 +11,50 @@ "tag_reasons": {}, "tags": [] }, + { + "category": "Types", + "detail": "unpacked_structs::class_properties at 338:3: expect_output: expected [65, 32, 65, 32, 105, 110, 105, 116, 32, 91, 112, 93, 10, 65, 32, 99, 104, 97, 110, 103, 101, 100, 32, 49, 32, 48, 10, 99, 104, 97, 110, 103, 101, 100, 32, 49, 32, 48, 10, 97, 99, 99, 32, 48, 32, 48, 32, 48, 48, 48, 48, 10, 97, 99, 99, 32, 53, 32, 49, 32, 49, 48, 49, 49, 32, 124, 32, 53, 32, 57, 32, 49, 48, 49, 49, 10, 97, 99, 99, 32, 48, 32, 48, 32, 48, 48, 48, 48, 10], got [32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 65, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 105, 110, 105, 116, 32, 91, 112, 93, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 65, 32, 99, 104, 97, 110, 103, 101, 100, 32, 49, 32, 48, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 99, 104, 97, 110, 103, 101, 100, 32, 49, 32, 48, 10, 97, 99, 99, 32, 120, 32, 48, 32, 48, 48, 48, 48, 10, 97, 99, 99, 32, 53, 32, 49, 32, 49, 48, 49, 49, 32, 124, 32, 53, 32, 57, 32, 49, 48, 49, 49, 10, 97, 99, 99, 32, 48, 32, 48, 32, 48, 48, 48, 48, 10]", + "expectation": "Simulation", + "name": "unpacked_structs::class_properties", + "phase": "execute", + "status": "mismatch", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "unpacked_structs::class_properties_inheritance_and_collections at 451:3: expect_output: expected [120, 32, 49, 10, 111, 32, 120, 10, 91, 50, 57, 57, 57, 33, 93, 32, 91, 50, 57, 57, 57, 93, 32, 51, 48, 48, 48, 10], got [120, 32, 49, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 111, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 120, 10, 91, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 50, 57, 57, 57, 33, 93, 32, 91, 50, 57, 57, 57, 93, 32, 51, 48, 48, 48, 10]", + "expectation": "Simulation", + "name": "unpacked_structs::class_properties_inheritance_and_collections", + "phase": "execute", + "status": "mismatch", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "unpacked_structs::class_property_assignment_order at 372:3: expect_output: expected [50, 32, 49, 10, 55, 32, 56, 32, 50, 32, 50, 48, 32, 49, 10], got [50, 32, 50, 10, 49, 32, 49, 48, 32, 55, 32, 50, 48, 32, 51, 10]", + "expectation": "Simulation", + "name": "unpacked_structs::class_property_assignment_order", + "phase": "execute", + "status": "mismatch", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, + { + "category": "Types", + "detail": "", + "expectation": "Simulation", + "name": "unpacked_structs::class_property_four_state_members", + "phase": "execute", + "status": "passed", + "stronger_than_sv": false, + "tag_reasons": {}, + "tags": [] + }, { "category": "Types", "detail": "", diff --git a/crates/celox-test-suite/verification/xezim-review.json b/crates/celox-test-suite/verification/xezim-review.json index a22d1b0a5c..44e8a8d4db 100644 --- a/crates/celox-test-suite/verification/xezim-review.json +++ b/crates/celox-test-suite/verification/xezim-review.json @@ -786,6 +786,14 @@ "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" + }, + "sv_rest-X84": { + "title": "A string member of a class property of unpacked structure type prints with %s as a space-padded packed value, and a property of the object in a handle member reads empty", + "standard": "IEEE 1800-2023 7.2, 8.4, 21.2.1.7" + }, + "sv_rest-X89": { + "title": "A whole class property of unpacked structure type is assigned member by member: a pattern reading the property sees members already written, and a receiver with a side-effecting index is evaluated per member", + "standard": "IEEE 1800-2023 7.2, 10.9.2, 11.4.1" } }, "cases": [ @@ -4619,6 +4627,30 @@ "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." + }, + { + "suite": "sv", + "name": "unpacked_structs::class_properties", + "status": "mismatch", + "classification": "xezim_bug", + "bug": "sv_rest-X84", + "note": "o.orig.m_type_name and o.ovrd.m_type_name print as space-padded wide values and o.orig.m_type.n prints empty; the time member also starts at X in xezim's 4-state run of this 2-state case. Reproduced by verification/repros/sv_xezim_unpacked_struct_properties.sv." + }, + { + "suite": "sv", + "name": "unpacked_structs::class_properties_inheritance_and_collections", + "status": "mismatch", + "classification": "xezim_bug", + "bug": "sv_rest-X84", + "note": "d.orig.m_type_name and d.ovrd.m_type_name print as space-padded wide values (\"o x\" and \"[2999!]\" expected). Reproduced by verification/repros/sv_xezim_unpacked_struct_properties.sv." + }, + { + "suite": "sv", + "name": "unpacked_structs::class_property_assignment_order", + "status": "mismatch", + "classification": "xezim_bug", + "bug": "sv_rest-X89", + "note": "h.p = '{h.p.b, h.p.a} prints \"2 2\" instead of \"2 1\", and items[next_index()].p = '{7, 8} calls next_index() once per member, writing items[1].p.a and items[2].p.b, with i = 3 instead of 1." } ] } diff --git a/crates/celox/tests/frontends/systemverilog/unpacked_structs.rs b/crates/celox/tests/frontends/systemverilog/unpacked_structs.rs index 95f1c6b75c..99cf93ba15 100644 --- a/crates/celox/tests/frontends/systemverilog/unpacked_structs.rs +++ b/crates/celox/tests/frontends/systemverilog/unpacked_structs.rs @@ -19,6 +19,64 @@ sv_backends! { @case "unpacked_structs::results_and_outputs"; } + fn structure_properties_hide_module_variables(sim) { + @setup { + let source = r#"module Top(output int value); + typedef struct { int a; int b; } pair_t; + int p; + class C; + pair_t p; + function int first(); + p.a = 7; + return p.a; + endfunction + endclass + initial begin + C c; + c = new; + p = 3; + value = c.first() * 10 + p; + end + endmodule"#; + } + @build celox::Simulation::from_sv_sources(vec![(source, Path::new("struct.sv"))], "Top"); + sim.run_until(0).unwrap(); + let value = sim.signal("value"); + assert_eq!(sim.get(value), BigUint::from(73u8)); + } + + fn structure_properties_in_for_headers_and_method_precedence(sim) { + @setup { + let source = r#"module Top(output int value); + typedef struct { int a; int b; } pair_t; + function int get(); + return 5; + endfunction + class H; + pair_t p; + function pair_t get(); + return '{4, 2}; + endfunction + function int run(); + int n; + for (p = '{0, 0}; p.a < 3; p = '{p.a + 1, p.b + 2}) n++; + p = get(); + return n * 100 + p.a * 10 + p.b; + endfunction + endclass + initial begin + H h; + h = new; + value = h.run(); + end + endmodule"#; + } + @build celox::Simulation::from_sv_sources(vec![(source, Path::new("struct.sv"))], "Top"); + sim.run_until(0).unwrap(); + let value = sim.signal("value"); + assert_eq!(sim.get(value), BigUint::from(342u16)); + } + fn constructors_in_structure_patterns(sim) { @setup { let source = r#"module Top(output bit ok); @@ -96,6 +154,36 @@ sv_backends! { let out = sim.signal("out"); assert_eq!(sim.get(out), BigUint::from(71u8)); } + fn redeclared_structure_properties_hide_their_members(sim) { + @setup { + let source = r#"module Top(output int value); + typedef struct { int a; int b; } pair_t; + typedef struct { int a; } one_t; + class Base; + pair_t p; + endclass + class Derived extends Base; + one_t p; + function int sum(); + return p.a * 100 + super.p.a * 10 + super.p.b; + endfunction + endclass + initial begin + Derived d; + Base b; + d = new; + b = d; + d.p.a = 1; + b.p = '{2, 3}; + value = d.sum() * 10 + d.p.a; + end + endmodule"#; + } + @build celox::Simulation::from_sv_sources(vec![(source, Path::new("struct.sv"))], "Top"); + sim.run_until(0).unwrap(); + let value = sim.signal("value"); + assert_eq!(sim.get(value), BigUint::from(1231u16)); + } fn unpacked_struct_module_variables(sim) { @case "unpacked_structs::module_variables"; } @@ -105,6 +193,18 @@ sv_backends! { fn unpacked_struct_four_state_members(sim) { @case "unpacked_structs::four_state_members"; } + fn unpacked_struct_class_properties(sim) { + @case "unpacked_structs::class_properties"; + } + fn unpacked_struct_class_property_assignment_order(sim) { + @case "unpacked_structs::class_property_assignment_order"; + } + fn unpacked_struct_class_property_four_state_members(sim) { + @case "unpacked_structs::class_property_four_state_members"; + } + fn unpacked_struct_class_properties_inheritance_and_collections(sim) { + @case "unpacked_structs::class_properties_inheritance_and_collections"; + } } #[test] @@ -166,13 +266,41 @@ fn unsupported_and_illegal_uses_are_rejected() { ), ("i_t q[$];", "dynamic container of unpacked structures"), ( - "class C; i_t v; endclass", - "class property of unpacked structure type", + "class C; static i_t v; endclass", + "static class property of unpacked structure type", + ), + ( + "class C; rand i_t v; endclass", + "random class property of unpacked structure type", + ), + ( + "class C; i_t v; endclass C c; initial begin c = new; c.v += c.v; end", + "operator `+=` on an unpacked structure", + ), + ( + "class C; i_t v; endclass C c; i_t w; initial begin c = new; c.v <= w; end", + "nonblocking assignment to a class property", + ), + ( + "class C; i_t v; endclass C cs[2]; int k; i_t w; function int f(); k = k + 1; return k; endfunction initial w = cs[f()].v;", + "whole unpacked structure property read through a receiver with a side effect", + ), + ( + "class C; i_t v; endclass C cs[$]; int k[$]; i_t w; initial w = cs[k.pop_front()].v;", + "whole unpacked structure property read through a receiver with a side effect", ), ( "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", ), + ( + "class B; i_t p; endclass class D extends B; int p; endclass D d; int x; initial begin d = new; x = d.p.a; end", + "structure member `d.p.a`", + ), + ( + "class B; i_t p; endclass typedef struct { int a; } one_t; class D extends B; one_t p; endclass D d; bit x; initial begin d = new; x = d.p.b; end", + "structure member `d.p.b`", + ), ("i_t v[2];", "array of unpacked structures"), ( "typedef struct packed { i_t x; } p_t; p_t v;", diff --git a/docs/guide/systemverilog-reference.md b/docs/guide/systemverilog-reference.md index f911290ad0..96887cf304 100644 --- a/docs/guide/systemverilog-reference.md +++ b/docs/guide/systemverilog-reference.md @@ -33,7 +33,7 @@ synthesis and is tested at the design level. | 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) | +| 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, unpacked structures of those types (see [unpacked structures](#unpacked-structures)), 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) | | Parameters | integral parameters and overrides preserving widths beyond 128 bits and X/Z values, and parameters of unpacked array or packed struct type given by an assignment pattern (constant tables), including unpacked-array pattern overrides | ### Processes and control flow @@ -330,8 +330,8 @@ SystemVerilog emitted by Veryl. 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 +type may be module variables, procedural locals, class properties, 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 @@ -344,7 +344,7 @@ Celox reports an `Unsupported` error naming the construct instead of ignoring it. The error carries the number of the issue that tracks the construct; constructs without a dedicated issue point to the frontend roadmap, [#88](https://github.com/celox-sim/celox/issues/88). -- Class features beyond those [listed](#classes): `$cast` to a variable that is not a class handle, enumeration constants declared in a class, enumeration, struct and union types and named four-state or string constants as class parameter values, module-local classes as type arguments to package-class specializations, properties that are `real`, arrays or wider than 64 bits, and randomization or constraints over 4-state properties. Timed tasks with static local declarations and recursive calls remain unsupported. +- Class features beyond those [listed](#classes): `$cast` to a variable that is not a class handle, enumeration constants declared in a class, enumeration, struct and union types and named four-state or string constants as class parameter values, module-local classes as type arguments to package-class specializations, properties that are `real`, arrays or wider than 64 bits, bit and part selects and packed structure members of a property (also of a member of a property of unpacked structure type), properties as `output` or `inout` arguments, and randomization or constraints over 4-state properties. Timed tasks with static local declarations and recursive calls remain unsupported. - Behavioral and verification constructs: intra-assignment timing controls (`a = #1 b`), delayed continuous assignments ([#444](https://github.com/celox-sim/celox/issues/444)), named events and `->`, `wait fork` / `disable fork`, temporal concurrent assertions beyond the Boolean subset below, `force` / `release`. @@ -361,9 +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)), - `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. + `real`, and unpacked structs as ports, nets, parameters, static, `rand` or + `const` 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 e3c4710914..06cf2f8791 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 in ports, class properties and containers, and several interface features. +- `real`, unpacked structs in ports 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 7dbd8f3ead..a7db579646 100644 --- a/docs/ja/guide/systemverilog-reference.md +++ b/docs/ja/guide/systemverilog-reference.md @@ -29,7 +29,7 @@ outline: [2, 3] | 式 | `**` を含む算術、論理、シフト、比較、リダクション、連結・複製、`?:`、`inside`、`==?` / `!=?`、キャスト(`N'(x)`、`signed'(x)`、`T'(x)`)、`$signed` / `$unsigned` | | 選択 | 定数・実行時のビット選択と indexed part-select(`[i]`、`[i +: W]`、`[i -: W]`)。読み書きの両方、宣言の向きによらず | | パターン | 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)を参照 | +| クラス | モジュール・package・コンパイル単位で宣言したクラス。64 ビット以下の 2 値・4 値整数型(4 値のプロパティは X で始まり、X/Z ビットを保持します)・`string`・クラスハンドル型と、それらをメンバとする unpacked 構造体([unpacked 構造体](#unpacked-structures)を参照)のプロパティ(初期値つきも可)、引数あり・なしの `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 配列のパターンによる上書き | @@ -208,7 +208,7 @@ SystemVerilog に対しても、共有の Veryl 適合性スイートを実行 unpacked 構造体(IEEE 1800-2023 7.2)のメンバには、整数型、列挙型、`string`、 クラスハンドル、packed 構造体、入れ子の unpacked 構造体を使え、各メンバは自身の -2 値・4 値の型を保ちます。この型の変数は、モジュール変数、手続き内のローカル変数、 +2 値・4 値の型を保ちます。この型の変数は、モジュール変数、手続き内のローカル変数、クラスプロパティ、 任意の方向のサブルーチン引数と戻り値にできます。メンバはその型の変数と同じように 読み書きできます。構造体全体は、同じ型の値との代入と `==`・`!=`・`===`・`!==` による 比較、`?:` による選択、assignment pattern(`'{a, b}`、`'{m: v, default: d}`、 @@ -221,7 +221,7 @@ unpacked 構造体(IEEE 1800-2023 7.2)のメンバには、整数型、列 その構文を追跡する issue の番号が含まれます。専用の issue がない構文は、フロントエンドの ロードマップ [#88](https://github.com/celox-sim/celox/issues/88) を指します。 -- [対応範囲](#classes)外のクラスの機能:クラスハンドル以外の変数への `$cast`、クラス内で宣言した列挙定数、クラスパラメータの値としての列挙型・構造体型・共用体型と名前付きの 4 値・文字列定数、package のクラスをモジュール内クラス型で特殊化する機能、`real`・配列・64 ビットを超えるプロパティ、4 値プロパティの乱数化と制約。時間待ちを含む task の static ローカル宣言、再帰呼び出し。 +- [対応範囲](#classes)外のクラスの機能:クラスハンドル以外の変数への `$cast`、クラス内で宣言した列挙定数、クラスパラメータの値としての列挙型・構造体型・共用体型と名前付きの 4 値・文字列定数、package のクラスをモジュール内クラス型で特殊化する機能、`real`・配列・64 ビットを超えるプロパティ、プロパティ(unpacked 構造体型のプロパティのメンバを含む)のビット選択・部分選択と packed 構造体メンバ、`output` / `inout` 引数としてのプロパティ、4 値プロパティの乱数化と制約。時間待ちを含む task の static ローカル宣言、再帰呼び出し。 - `inout` / `ref` / 式を使う modport、virtual interface など。 ANSI ヘッダーのポートは、クロック入力やパラメータ依存の幅も含めて modport 経由で渡せます。 interface 自体と入れ子の interface は対応しています。型パラメータはインスタンスごとに解決し、modport ポート経由でも伝搬します。 @@ -236,7 +236,7 @@ unpacked 構造体(IEEE 1800-2023 7.2)のメンバには、整数型、列 - `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))、`real`、ポート・ネット・パラメータ・クラスプロパティ・配列やコンテナの要素としての unpacked 構造体、配列・`real`・既定値を持つメンバ。 +- 宣言:interface 配列のメンバーの変数宣言の初期化子、ANSI ポートの既定値、tagged union、unpacked union、packed union の assignment pattern、ドライバを持たない内部ネット([#460](https://github.com/celox-sim/celox/issues/460))、`real`、ポート・ネット・パラメータ・static / `rand` / `const` のクラスプロパティ・配列やコンテナの要素としての 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 401048927f..62648c5478 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 を含む診断を返します。