Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions conformance/sv-tests/expected.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 23 additions & 5 deletions crates/celox-frontend-sv/src/lowering/ff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions crates/celox-sv-analyzer/src/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ fn analyze_source_impl(source: ast::Source) -> Result<ir::Ir, AnalyzerError> {
let signals = module
.signals()
.iter()
// A variable of unpacked structure type is its leaf variables.
.filter(|signal| !signal.is_aggregate())
Comment thread
tignear marked this conversation as resolved.
.map(|signal| {
ir::Signal::new(
signal.name().to_string(),
Expand Down
31 changes: 30 additions & 1 deletion crates/celox-sv-analyzer/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -2248,6 +2263,8 @@ pub struct Type {
class: Option<Arc<str>>,
/// 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<Arc<unpacked_structs::UnpackedStruct>>,
}

/// The declaration that names an enumeration, struct or union type, which
Expand Down Expand Up @@ -2291,6 +2308,7 @@ impl Type {
class: None,
container: None,
nominal: Nominal::default(),
unpacked_struct: None,
}
}

Expand All @@ -2307,6 +2325,7 @@ impl Type {
signed_element_depth: None,
class: None,
container: None,
unpacked_struct: None,
nominal: Nominal::default(),
}
}
Expand All @@ -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()));
}
}
Expand All @@ -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<unpacked_structs::UnpackedStruct>> {
self.unpacked_struct.as_ref()
}

/// The class of an object handle type.
pub fn class(&self) -> Option<&str> {
self.class.as_deref()
Expand Down Expand Up @@ -3090,6 +3116,9 @@ struct VariableDimensions {
members: Vec<packed_structs::PackedMember>,
/// See [`Type::signed_element_depth`].
signed_element_depth: Option<usize>,
/// A variable of unpacked structure type: the names of its members
/// are variables (see [`unpacked_structs`]).
unpacked_struct: Option<Arc<unpacked_structs::UnpackedStruct>>,
}

type VariablePackedDimensions = HashMap<String, VariableDimensions>;
Expand Down
1 change: 1 addition & 0 deletions crates/celox-sv-analyzer/src/ast/bound_patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ impl BoundPattern {
packed: Vec::new(),
signed: false,
signed_element_depth: None,
unpacked_struct: None,
..shape.clone()
};
width
Expand Down
8 changes: 6 additions & 2 deletions crates/celox-sv-analyzer/src/ast/bound_patterns/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)) =>
Expand Down
3 changes: 3 additions & 0 deletions crates/celox-sv-analyzer/src/ast/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,9 @@ fn field(
env: &HashMap<String, i128>,
offset: u32,
) -> Converted<ObjectField> {
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)),
Expand Down
15 changes: 15 additions & 0 deletions crates/celox-sv-analyzer/src/ast/comb_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions crates/celox-sv-analyzer/src/ast/containers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
32 changes: 31 additions & 1 deletion crates/celox-sv-analyzer/src/ast/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down
4 changes: 4 additions & 0 deletions crates/celox-sv-analyzer/src/ast/dimensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
},
);
}
Expand Down Expand Up @@ -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(),
},
);
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
},
)
})
Expand Down
30 changes: 30 additions & 0 deletions crates/celox-sv-analyzer/src/ast/expression_binding/direct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Comment thread
tignear marked this conversation as resolved.
// 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)?;
Comment thread
tignear marked this conversation as resolved.
} 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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading