Check a type cast with an inline packed dimension and reject dimensions
following a type identifier in a cast target. Use a dimensioned typedef in
the reject test to distinguish dimensions in the type from dimensions after
the identifier.
Check that an error reported while elaborating a cast-target type is emitted
only once when width checking and expression elaboration reuse the cached
target information. Also check that each module instance resolves a
type-parameter cast target using its own parameter value.
Check that typed dynamic-array elaboration constructs the explicit cast
target rather than the enclosing expression type.
Run the tests through the native and vlog95 backends.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
A SystemVerilog cast target can be either a type or a constant size
expression. Currently the parser commits to `PECastType` or `PECastSize`
based on how the target identifier is classified while parsing. This is too
early for identifiers whose meaning is only known after symbol lookup. As a
result, corner cases are handled incorrectly when parser-time classification
does not match the result of elaboration. For example, the parser can not
decide whether an identifier inherited from a base class is a type or a
constant size expression. Supporting the inherited lookup is separate, but
the cast target must remain unresolved until elaboration for that lookup to
be used.
Parse both forms through `expr_primary_or_typename` and represent them with
one `PECast`. Keep atomic types wrapped in `PETypename` and preserve named
targets as `PEIdent`.
Resolve the target during elaboration. First use `test_type()` to distinguish
a type target from a constant size expression. For a type target, use a
contextual `elaborate_type()` call to resolve the type. Diagnose dimensions
after a type identifier directly from `PEIdent::elaborate_type()` when it is
used as a cast target. A failed type elaboration returns `nullptr` and does
not fall back to interpreting the target as a size expression. Dimensions
contained in the named type remain valid.
Cache the resolved target information during width checking because ordinary
expression elaboration needs the same information. Tag the cache with the
`NetScope` and recompute it when the scope changes since type parameters can
give the same cast expression a different target type in each instance.
Typed elaboration can bypass width checking, so resolve into a local value
when no matching cache is available. This avoids modifying the parsed
expression from a const elaboration method while still avoiding repeated
diagnostics between the normal width checking and expression elaboration
phases.
Share the type and size conversion paths between width-based and typed
elaboration. Use the explicit cast target when constructing a dynamic array
instead of the enclosing expression type. Own the cast target and operand
with `std::unique_ptr`.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that packed dimensions are preserved when local and
package-qualified identifiers resolve to types in type parameter values and
system function arguments. Verify that symbolic dimensions are evaluated in
the scope of each module instance.
Check the diagnostics for unsized and queue suffixes, indexed part selects,
and packed dimensions applied to an unpacked named type.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that lookup chooses the nearest visible type or value when an
identifier can denote either. Cover lexical ordering, inherited properties,
and a bracket suffix that must remain a value select after lookup.
Check typed expression and l-value contexts where a type must be rejected
instead of binding an outer value. Check that a hierarchical type reference
is rejected with the type-specific diagnostic.
Check that probing an invalid hierarchical type parameter does not elaborate
its signal prefix and report a false circular dependency.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Named types in expression-like contexts are currently converted to
`PETypename` by the parser. This commits to the type interpretation before
symbol lookup, although the same syntax can represent an ordinary identifier.
Keep atomic types as `PETypename`, but parse local and package-qualified type
identifier candidates as `PEIdent`. Preserve any bracket suffixes so they can
be interpreted as packed dimensions for a type or as selects for a value.
Add `PExpr::test_type()` for non-elaborating type detection and
`PExpr::elaborate_type()` to return the elaborated type. Use these interfaces
for `$bits()`, `$sizeof()` and type parameters. Callers test the
interpretation before elaborating it, so a null type reports an elaboration
failure without incorrectly falling back to value elaboration.
Type testing is normally followed by elaboration, and both operations need
the same symbol lookup. Let `PEIdent::test_type()` cache the result and its
declaration scope, keyed by the lookup scope. Keep `elaborate_type()` const and
perform a local lookup when type testing was skipped or the cached result is
from another scope. This avoids repeated lookup without reusing a
scope-dependent result.
Search the complete identifier path before deciding whether it names a type.
Stop lookup at signal placeholders so they still hide matching types in outer
scopes without being elaborated by the type probe.
The LRM section 6.18 does not allow hierarchical references to type
identifiers, so reject a dotted type reference after lookup and report where
the type was declared. This distinguishes an invalid `test.T` type reference
from a path that does not name a type.
Use the unified symbol lookup result to choose the type or value
interpretation. Report an error when a type reaches an expression or l-value
context. This preserves normal lexical shadowing while deferring only the
syntactic decision.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
`symbol_search()` currently uses a boolean argument to allow forward
references to the terminal name. Additional lookup options would require more
boolean arguments and make call sites difficult to read.
Replace the boolean with a bitmask and use a named flag at the existing
forward-reference call sites. Add a second flag that stops lookup at an
unelaborated signal instead of elaborating it. Stopping the lookup makes the
signal continue to hide matching symbols in outer scopes.
Forward-reference permission applies only to the terminal name, while signal
elaboration suppression applies to the complete path. This allows callers
that only need to classify a name to avoid triggering signal elaboration
during lookup.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that a visible type prevents lookup from continuing to an outer value
for function and task calls and implicit `.name` and wildcard `.*` port
connections. Check that types are rejected as disable targets, named events,
procedural l-values, and expressions elaborated with a required type.
Also check the opposite lexical-order case where a typedef declared after a
reference does not hide an outer named event or variable. Use diagnostic gold
files for each negative case that emits the type-specific error.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
SystemVerilog places user-defined types and data identifiers in the same
namespace. Symbol lookup cannot currently return a type, so a value lookup can
skip a nearer typedef and incorrectly bind a value in an outer scope when an
ambiguous identifier is resolved during elaboration.
Add typedef results to `symbol_search()` and check visible typedefs before
continuing to enclosing scopes. Use the lexical visibility limit selected by
the caller. This is normally the reference position, while function and task
call lookup uses the end of the current scope as required by the LRM section
26.3.
Let callers that require a value or another non-type symbol diagnose the type
result and report its declaration location. This includes function and task
calls, expressions with and without a required type, procedural l-values,
named-event triggers, and disable targets. Callers that only probe for a
particular kind of symbol, including port-connection matching, ignore a type
result. Also ensure scope-only callers do not confuse the typedef's owning scope
with the resolved object.
This prepares expression elaboration to defer the type-versus-value decision
while preserving lexical shadowing.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check separately that a local variable, named event, and parameter hide a
compilation-unit task used as a disable target.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Disable targets currently use `Design::find_scope()`, which searches only
scope names. A task or named block from an outer scope is therefore selected
even when a variable, named event, or parameter in a closer scope has the
same name.
The LRM section 3.13 places tasks, named blocks, parameters, named events, and
variables in the same local name space. Use normal symbol lookup and require
the result to be a scope. This lets a closer non-scope object hide the outer
disable target and reports an error for the non-scope result.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that a closer variable, named event, or parameter hides a task or
function used as a statement. Also check object method lookup and recursive
function calls used as statements.
Check that a later compilation-unit task or function takes precedence over a
matching subroutine in an enclosing instance. Check that different instances
of the same module resolve task calls through their respective enclosing
instances. Check ordinary lexical ordering for a task-call receiver declared
before or after the reference.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Task and function calls used as statements currently use
`Design::find_task()` and `Design::find_function()`, which only search for
scopes of the requested type. A closer variable, named event, or parameter is
skipped. This can call a hidden task or function, and can make the non-void
discarded-return path resolve a different symbol and abort.
Use one `symbol_search()` for both task lookup and the function fallback so
all declarations in the shared name space participate in lookup. Accept a
function scope or the return variable of a recursive function call, while
preserving the existing method fallback.
Allow the terminal task or function name to be declared later while resolving
a receiver prefix at the call position. Use the call position when the method
fallback searches for the receiver as well.
Remove the now unused `Design::find_task()` and `Design::find_function()`
helpers.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that same-named scopes in the enclosing instance hierarchy do not
interfere with lookup of compilation-unit variables, parameters, named events,
and imported variables. Cover expressions, procedural l-values, implicit and
wildcard port connections, and lookup from module and nested function scopes.
Check task and function calls, including method receivers and chained calls.
Verify that receiver prefixes follow ordinary lexical ordering while direct,
imported, and chained compilation-unit function names can be declared after
their references. Check the enclosing-instance fallback when the compilation
unit has no match.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
`symbol_search()` currently searches the enclosing instance hierarchy before
the compilation-unit scope for ordinary names. An enclosing instance scope can
therefore hide an earlier compilation-unit variable or object, including when
the object is the prefix of a dotted reference.
The LRM section 3.12.1 requires a common lookup order. First search the local
lexical scopes through the design unit. Next search the compilation-unit scope
up to the reference position. If there is no match, resume at the saved
instantiation parent and continue through the instance hierarchy. Disable data
object lookup when resuming so only enclosing scope names can match.
Task and function names follow the same traversal, but the LRM section 23.8.1
lets the terminal subroutine name search the complete compilation unit. Use a
separate terminal lexical limit for this. Resolve a prefix such as `object` in
`object.func()` at the reference position while allowing a forward declaration
of `func`.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The scope traversal clears `start_scope` after switching to the compilation
unit and uses the null value to prevent a second compilation-unit search.
This gives the scope used for path evaluation two separate purposes.
Track whether the compilation-unit scope has been searched with a separate
flag. Keep the existing instance-before-compilation-unit traversal order.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The scope traversal tracks whether it has crossed a module boundary and
uses the inverse of that state to decide whether objects are visible.
Track object visibility directly instead. This keeps the existing behavior
while making the module-boundary rule and later traversal changes easier to
follow.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Child scopes and interface aliases are matched directly in the scope
traversal. This obscures the code that decides which scope to search next.
Move the child scope and interface alias lookup into a helper. Keep the
existing lookup order and error handling unchanged so the traversal can be
restructured separately.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The object lookup for each scope is embedded in the scope traversal. This
makes the traversal order and the module boundary handling difficult to
follow.
Move net, event, parameter, class property, and placeholder lookup into a
helper. Use separate found, not found, and failure results so the existing
lookup behavior is preserved. This prepares the scope traversal to be
changed independently.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that a class property cannot share a name with another property, a
function, a task, a parameter, a type, or an enum named constant. Check
duplicate properties in both separate and comma-separated declarations. The
latter guards the shared declaration type ownership that previously caused a
crash.
Exercise properties in both declaration orders so both member registration
paths are covered.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The LRM requires an identifier to name only one item within a scope. Class
properties are currently stored only in `class_type_t::properties`, outside
the common local symbol table. As a result, declarations such as:
class C;
int value;
function int value;
return 0;
endfunction
endclass
are accepted. A second property in a separate declaration silently replaces
the first property map entry. A repeated name in a comma-separated declaration
such as `int value, value` can crash because both declarators share the same raw
type.
Make `prop_info_t` a named item and register each property in the class local
symbol table. Check the declaration before taking ownership of its type or
initializer, since comma-separated properties share the raw declaration type.
Use `emplace()` for the property map so an accepted property cannot replace an
existing entry.
Properties now take part in the common symbol lookup. Give them a distinct
symbol type so duplicate declaration diagnostics identify them as class
properties, and remove the separate property check from type-identifier
classification.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Consolidating variable dimensions and hierarchy indices into a shared
grammar makes all index component forms parse in either context.
Check that `+:` and `-:` indexed part selects are rejected as dimensions,
and that `[]` and `[$:N]` are rejected as hierarchy indices. These forms
were previously excluded by the context-specific grammar rules.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Variable declaration dimensions and hierarchical identifier indices both
use bracketed components, but are parsed by separate grammar rules.
Consolidate them into a shared `index_component` rule and convert the
components once the surrounding context is known.
This prepares for accepting a `TYPE_IDENTIFIER` token as an ordinary name
in a hierarchical path. A path component can have the same spelling as a
typedef visible in the current scope. In the path it is an ordinary
identifier. The hierarchical reference does not override or replace the
typedef.
The shared rule parses component forms that are not valid in both contexts.
Preserve the previous restrictions by explicitly rejecting indexed part
selects in variable dimensions and empty or bounded-queue components in
hierarchical identifiers. Track the location of each component so these
errors point to the invalid suffix.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that source `null` expressions in `[null]` and `[null:N]` are
rejected rather than treated as the `$` marker for unbounded and bounded
queue dimensions.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Queue dimensions use a `PENull` expression as an internal marker for `$`.
This also represents a source `null` expression, so declarations such as:
integer value[null:2];
are accepted and elaborated as bounded queues.
Use a dedicated `PEQueueDimension` marker for queue dimensions. This lets
source `null` expressions follow normal range expression validation while
preserving the existing representation of dynamic and fixed dimensions.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that a bare inherited non-void function call used as a statement emits
the required discarded-return warning and executes the function.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The LRM section 13.4.1 permits a non-void function call to be used as a
statement and requires a warning when the return value is implicitly
discarded. A bare call to an inherited class function currently emits that
warning and then aborts.
Method lookup finds the inherited function through the implicit `this`
receiver. The discarded-result path then rebuilds a `PECallFunction` from the
original bare name, losing the receiver and leaving the expression without a
type.
Pass the resolved method path to the discarded-result path so the rebuilt call
remains qualified by `this`.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that a `const static` class property with a declaration
initializer is accepted and has the initialized value. Also check that
subsequent assignments through unqualified and object-member lookup are
rejected.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Static property declaration initializers are elaborated as standalone
initialization processes instead of through a class constructor. The
const-property bookkeeping therefore does not see the initializer and
reports an initialized `const static` property as missing initialization.
Record whether each parsed property has a declaration initializer and
mark an initialized static const property when its signal is created.
Also mark the signal as const and its declaration assignment as an
initializer. This permits that initial assignment while rejecting later
writes. Uninitialized static const properties remain errors.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
A named block inside a class constructor has its own scope. The const property
assignment check currently tests the name of this immediate scope and rejects
the assignment because it is not named `new` or `new@`.
Find the containing class method before deciding whether the assignment is in
the constructor. Assignments from nested blocks in other methods remain
invalid.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that an inherited property does not offset initialization bookkeeping
for a const property assigned in the derived class constructor.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Property indices include the properties inherited from base classes.
`netclass_t::get_prop_initialized()` currently uses this combined index
without removing the inherited property count when accessing the local
property table. This can read past the table and treat a derived const
property as already initialized.
Subtract the inherited property count, matching the other property accessors.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that an unqualified method call from a named block uses the implicit
`this` signal from the containing class method. The compiler currently looks
for the signal in the named-block scope and crashes.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
An unqualified method call inside a named block is elaborated from the block
scope. When the call resolves to another method in the same class, receiver
construction looks for the implicit `this` signal directly in that scope:
function int call_method();
begin : nested
return get_value();
end
endfunction
The signal belongs to the containing method scope, so the lookup returns null
and the compiler dereferences it.
Find the containing method first and obtain the implicit `this` signal from
that scope.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check separately that `$unit::` references to a variable, named event, and
parameter declared later in the compilation unit are rejected.
Also check that preceding compilation-unit items remain visible and that a
later function name remains visible as permitted by the LRM section 3.12.1
exception.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The LRM section 3.12.1 says that `$unit::` only disambiguates a name in
the compilation-unit scope and does not allow a reference to an item declared
later. Currently every bound scope is exempt from declaration-order checks, so
this incorrectly resolves `value`:
module test;
initial $display("%0d", $unit::value);
endmodule
integer value;
Apply the existing variable, named event, and parameter declaration-order
checks when lookup is bound to a compilation-unit scope. Other hierarchical,
package, and imported lookups remain bound without comparing unrelated lexical
positions. Task and function lookup remains unchanged, as required by the
exception in the same LRM section.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that a reference before an inner enum declaration resolves to a
matching enum named constant in the outer scope, while a reference after the
declaration resolves to the inner constant.
Also check that `-gno-strict-parameter-declaration` continues to allow a
reference to a later enum named constant and identifies it correctly in the
declaration-after-use warning.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Enum named constants use the parameter lookup path, including its lexical
declaration-order check. Their declaration position is currently reported as
zero, however, so a later enum named constant can incorrectly hide a matching
constant from an outer scope.
Attach the parsed enum item's source location to the elaborated enum constant
and use its lexical position during lookup. Have the position lookup also
identify enum named constants so relaxed-mode warnings do not call them
parameters. Update the position after a warning to retain the existing warning
suppression behavior.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that a bare delay identifier before a wildcard package import resolves
an outer constant. Check that a following bare delay activates the import and
resolves the package constant.
Run the test through the native and vlog95 backends.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Bare identifiers used as unparenthesized delays construct `PEIdent` directly.
This bypasses wildcard package import activation, so a delay reference after
an import can resolve to an outer declaration instead of the imported name.
Create the identifier through `pform_new_ident()` so the import becomes
visible at the reference's lexical position.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that value parameters, `localparam` constants, scalar variables,
unpacked arrays, named events, and enum literals imported from a package in
another compilation unit are visible in the importing module. Exercise both
explicit and wildcard imports.
Give the package declarations larger unit-local lexical positions than the
references in the importing unit so that comparing these unrelated positions
exposes the bug.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Package imports already resolve names declared in packages from other
compilation units. After `NetScope::find_import()` finds such an import,
however, `symbol_search()` continues with an unbound lookup. It then compares
the declaration position in the package with the reference position in the
importing unit. Lexical positions are local to a compilation unit, so these
unrelated values can make a legal imported variable, named event, or parameter
appear to be used before its declaration.
Set `scope_is_bound` after `NetScope::find_import()` has checked that the
import is visible at the reference. The import already identifies the package
that declares the name, so binding the search avoids the invalid cross-unit
comparison and prevents lookup from continuing outside that package.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The `prefix_scope` flag indicates that symbol lookup has been bound to a
specific scope. It controls whether lookup can continue outside that scope
and whether declaration ordering must be checked.
Rename it to `scope_is_bound` to describe the lookup state instead of how the
scope was selected. This allows other scope bindings to reuse it.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that an implicit `.name` port connection before a wildcard import
resolves an outer declaration, while a connection after the import
resolves the declaration from the package.
Run the test through the native and vlog95 backends.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The LRM section 23.3.2.4 says that an implicit `.name` port
connection is a reference that can make a name from a preceding wildcard
package import visible. Currently `.name` creates its `PEIdent` directly
and skips the package import reference bookkeeping, so the imported name
can not be resolved.
Allow `pform_new_ident()` to suppress implicit net creation and use it for
`.name`. This keeps import activation, `PEIdent` construction, and source
location handling in one place while preserving the existing behavior
that prevents `.name` from creating an implicit net.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check separately that a module-body specparam declaration following a
reference is rejected and that a specparam declared in a specify block
remains visible before its declaration.
Run both tests through the native and vlog95 backends.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The LRM section 6.20.5 permits `specparam` declarations both in a
module body and in a `specify` block. It states:
A specify parameter declared outside a specify block shall be declared
before it is referenced.
Currently all specparams retain a declaration position of zero, making a
later module-body declaration visible to an earlier expression:
module test;
wire value;
assign value = delay;
specparam delay = 1;
endmodule
Record the source position for module-body specparams. Track when the
parser is inside a `specify` block so the outside-block restriction is
not applied there.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Check that class copy construction in a nested scope activates a wildcard
package import and resolves the imported source. Check that construction in
the enclosing scope still resolves the same-named local source.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The source expression of a class copy constructor currently bypasses
`pform_new_ident()`. An identifier made visible by a wildcard package import
is therefore not activated and can not be resolved during elaboration.
Create the source expression through `pform_new_ident()`, matching ordinary
identifier expressions and preserving the source location.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>