A function declaration creates a nested scope whose name is visible throughout
its containing scope. Check that a later function named `T` hides an outer
typedef while elaborating an earlier signal, a class property, and a typed
constructor.
These consumers take different paths after type lookup. Verify that each
reports the failed lookup without asserting or dereferencing the missing type.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that an identifier that can denote either a type or a value selects a
later variable with `-gno-strict-net-var-declaration`. Check that a name in a
declaration that requires a data type still uses the earlier visible typedef.
Signed-off-by: Lars-Peter Clausen <[email protected]>
`typeref_t` stores the `typedef_t` selected by the parser. This binds a type
identifier before the elaborated scope is available and can select the wrong
declaration. Function and task declarations are visible throughout their
scope, so a later subroutine named `T` in the current scope hides an earlier
`T` type in an outer scope even when the type reference appears before the
subroutine declaration. Parser-time lookup can not handle this correctly.
For example:
typedef int T;
module test;
T value;
function T;
T = 0;
endfunction
endmodule
The declaration of `value` must be rejected because the local function named
`T` hides the outer typedef. The same applies to a task named `T`.
Type lookup can also depend on information that is not available to the parser.
For example, a parameterized class can extend a type parameter and inherit `T`
from the selected base class:
class Derived #(type B = DefaultBase) extends B;
T value;
endclass
Parameterized classes are not supported yet, but resolving named types during
elaboration is a prerequisite for supporting this case.
Replace `typeref_t` with `type_identifier_t`, which owns a `PEIdent`. Construct
the identifier in the parser, then resolve it through
`PEIdent::elaborate_type()` and `symbol_search()` while elaborating the data
type. Parser-time type lookup is only used to select grammar paths.
`pform_new_ident()` preserves wildcard import activation for the identifier
expression.
Enforce declaration ordering when an identifier occurs in a grammar position
that requires a data type, even when compatibility options relax it for
ordinary variable lookup. A later variable therefore does not hide an outer
type in a declaration, and a later type declaration can not satisfy an earlier
use. Keep ordinary lookup when an identifier can be either a type or a value,
such as an argument to `$bits()`. Add
`SYMBOL_SEARCH_STRICT_DECLARATION_ORDER` and a required-type elaboration context
for the former case.
For example:
typedef logic [7:0] T;
module test;
T value;
localparam int A = $bits(value);
localparam int B = $bits(T);
integer T;
endmodule
The declaration of `value` uses the outer typedef, so `A` is 8. With relaxed
variable declaration ordering, the ambiguous reference in `$bits(T)` uses the
later integer, so `B` is 32.
Check a constant's declaration position before `get_parameter()` elaborates it
on demand when declaration order is enforced. This keeps a later declaration in
`parameter T T` from recursively selecting itself as the type.
Recover a failed deferred lookup in `type_identifier_t` with a scalar logic type
after reporting the error. This keeps the existing non-null expectations of
type consumers unchanged.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that a class can derive from a forward declared base class that is
defined later, both directly and through a typedef alias. Cover a multi-level
hierarchy, base constructor chaining, and inherited property layout.
Check that a local forward declaration hides an outer class with the same name.
Check forward declared base classes in package scopes and in separate instances
of the same module. Check that inheritance cycles, undefined forward
declarations, and non-class base types are rejected.
Run the tests through the native and vlog95 backends.
Signed-off-by: Lars-Peter Clausen <[email protected]>
SystemVerilog allows a forward declared class to be used as the base of a
class that is declared before the base class definition.
typedef class B;
class C extends B;
endclass
class B;
endclass
The base type is currently elaborated while creating the derived class. At
that point the base class has not been added to the scope yet and the base
type does not resolve to a class.
Create all classes first and bind their base classes in a second pass. Reject
inheritance cycles before attaching the base class.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that `SCOPE.value` resolves to a forward-referenced generate block when
`SCOPE` also names an imported type. The lexer can only see the type at the
point of the reference, so this exercises parsing a `TYPE_IDENTIFIER` as the
first hierarchy component.
Check that a compilation-unit type does not prevent a same-named inherited
class property from being used as a procedural l-value or expression.
Check that empty and queue-bound indices on such hierarchy components are
rejected during parsing instead of reaching elaboration.
Signed-off-by: Lars-Peter Clausen <[email protected]>
The lexer returns `TYPE_IDENTIFIER` when an identifier matches a visible
typedef. It does not know about hierarchy scopes declared later, so an
imported type can prevent a forward-referenced generate block with the same
name from being used as the first hierarchy component:
package p;
typedef int SCOPE;
endpackage
import p::*;
module test;
initial SCOPE.value = 1;
if (1) begin : SCOPE
int value;
end
endmodule
The same applies to inherited class properties, which are not visible to the
parser:
typedef int value;
class Base;
int value;
endclass
class Derived extends Base;
function int get;
return value;
endfunction
endclass
Accept `TYPE_IDENTIFIER` as the first hierarchy component and build it through
the same path as `IDENTIFIER`. This applies the existing hierarchy-index
validation before lookup and keeps malformed components out of elaboration.
Named types in expression-like contexts are already resolved through
identifier elaboration, so remove their separate `type_value` alternatives and
use the common hierarchy expression path. Keep atomic types in `type_value`.
This removes the grammar overlap without adding dedicated parameter and system
argument rules.
These identifiers can now reach continuous assignment l-value elaboration.
Use the common type diagnostic there. The implicit-net lookup checks typedefs,
so invalid type uses in continuous assignments, gate terminals, and module
port connections remain rejected rather than silently becoming nets.
This only permits hierarchy names to shadow visible type names. A hierarchical
path that resolves to a type remains rejected during elaboration.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that local, imported, and package-scoped typedefs resolve as declaration
types and as `$bits()` arguments, including attached dimensions and function
return and argument types.
Check lexical visibility before and after a local typedef declaration and an
explicit import. Earlier references resolve to outer typedefs and later
references resolve to the newly visible types.
Signed-off-by: Lars-Peter Clausen <[email protected]>
For-loop variable declarations currently match an optional `var` rule
before the data type. If `TYPE_IDENTIFIER` is also allowed to start a
hierarchy identifier, the parser can either shift it as an lvalue or
reduce the empty `K_var_opt` before a declaration.
Fold the optional keyword into `for_decl_data_type` with separate
alternatives for a data type with and without `var`. This keeps
declaration behavior unchanged and prepares for hierarchy identifiers
shadowing typedef names without introducing that shift/reduce conflict.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that a type identifier is rejected rather than declared as an implicit
net when used in a continuous assignment, primitive terminal, or ordered
module port connection. Cover local typedefs, explicit imports, and activated
wildcard imports.
These contexts are currently rejected by the grammar. Keep the tests in place
when type and hierarchy identifiers start using the same parser path.
Signed-off-by: Lars-Peter Clausen <[email protected]>
`PEIdent::declare_implicit_nets()` checks visible nets, parameters, genvars,
events, and enum constants before creating an implicit net, but does not check
typedefs. Type identifiers are currently filtered by the grammar before they
can reach this code, but this is not true when type and hierarchy identifier
parsing use the same path.
Check local and imported typedefs while searching the lexical scopes. Stop the
type search when another local declaration hides an outer typedef.
Signed-off-by: Lars-Peter Clausen <[email protected]>
Check that package tasks can be called as statements both with and
without an argument list. Use a caller variable that shares its name
with a package typedef to verify that call arguments are parsed in the
caller context.
Also check package-scoped void functions and void casts of non-void
functions. Cover class and queue methods whose receiver is a package
member as well.
Signed-off-by: Lars-Peter Clausen <[email protected]>
SystemVerilog allows package tasks and functions to be called as
statements through package-qualified names, for example:
p::run();
`subroutine_call` currently accepts ordinary hierarchical and
class-qualified names, but not `package_scope`, so the parser rejects
these calls.
Accept a package scope before the hierarchical subroutine name. Leave
package lexer mode before parsing arguments so argument names are
classified in the caller context, then retain the package on `PCallTask`
for elaboration.
Also retain the package when a package member is used as a method
receiver. This allows calls such as `p::object.run()` to resolve the
object in the package rather than the caller scope.
Signed-off-by: Lars-Peter Clausen <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
`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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
`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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>