Enhance the lists of identifiers and declaration assignments generated
by the parser to associate each identifier with its lexical_pos. Also do
this for single items in complex parser rules where the location passed
to the pform is not the location of the identifier.
In addition to providing positional arguments for task and functions
SystemVerilog allows to bind arguments by name. This is similar to how
module ports can be bound by name.
```
task t(int a, int b); ... endtask
...
t(.b(1), .a(2));
```
Extend the parser and elaboration stage to be able to handle this. During
elaboration the named argument list is transformed into a purely positional
list so that later stages like synthesis do not have to care about the
names.
For system functions and tasks all arguments must be unnamed, otherwise an
error will be reported.
In addition to functions and tasks arguments can also be bound by name for
the various different ways of invoking a class constructor.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
SystemVerilog allows to declare const variables. These variables are
read-only and can not be assigned a value after their declaration. It is
only possible to assign an initial value as an initializer.
E.g.
```
const int x = 10;
x = 20; // Error
```
The LRM requires that for variable declarations with static storage the
initializer is a constant expression with the extension that other const
variables are also allowed. const variables with automatic storage can
be initialized by any expression.
Checking if an expression contains only const variables requires a bit more
work to implement. So for now be more lenient that what the standard
requires and allow arbitrary expressions to initialize const variables even
for those with static storage.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
By default an identifier that has been imported into a package is not
available for imports by other packages. Only imports that have been
exported can be imported again. E.g.
```
package P1;
int x;
endpackage
package P2;
import P1::x;
export P1::x;
endpackage
module test;
import P2::x; // This will only work if x has been exported.
endmodule
```
Exports follow the same syntax as imports and allow both export of specific
identifiers or wildcard export. Export supports the special `*::*` target,
which will export all imported items.
Add support for handling package exports.
There is one special cases that needs to be considered. Usually when using
wildcard imports from multiple packages it is an error if there multiple
import candidates for an identifier. With exports it is possible that there
are multiple candidates through different packets, but they all refer to
the same identifier. In this case it does not create a conflict. E.g.
```
package P1;
int x;
endpackage
package P2;
import P1::x;
export P1::x;
endpackage
package P3;
import P1::*;
import P2::*;
int y = x; // No import conflict
endpackage
```
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Up to 1800-2017 the grammar in the LRM allowed an optional lifetime
qualifier for class declarations. Icarus supports this and uses this as the
default lifetime for methods of the class. But the LRM never specified what
this qualifier should do actually actually. Starting with 1800-2023 the
qualifier will be removed from the grammar[1].
Furthermore the LRM states that methods of a class are supposed to have
automatic storage and static storage is forbidden.
This currently works in Icarus for the most part since the liftime attached
to class methods is ignored during elaboration in most places. Where it
does not work is for variable initializers where it results in broken code
being generated and vvp crashes at runtime. E.g.
```
class C;
task t;
int x = 10;
endtask
endclass
```
Keep the optional lifetime qualifier for classes in the grammar for now, to
ensure backwards compatibility in case somebody is actually using it. But
ignore it and print a warning if it is specified.
In addition set the default lifetime for all classes to automatic. This
makes sure that variable initialization in classes works as expected.
[1] https://accellera.mantishub.io/view.php?id=3561
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
It is not allowed to create objects of virtual classes. Currently the
virtual keyword is accepted by the parser, but otherwise ignored.
Keep track of whether a class is virtual and generate an error when the
class new operator is used for a virtual type.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
SystemVerilog supports type parameters. These are similar to value
parameters, but they allow to pass a type to a module or similar when
instantiating it.
E.g.
```
module A #(parameter type T = int);
endmodule
module B;
A #(.T(real)) i_a;
endmodule
```
Add support for handling type parameters.
For the vlog95 and vhdl backends type parameters, similar to typedefs, get
replaced with their actual value. For modules with non-local type
parameters for each module instance a unique module or architecture is
generated with the actual type.
Querying type parameters through VPI is not yet supported.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
SystemVerilog supports forward type declarations. This allows to declare a
type identifier and use it, e.g. in a signal declaration, before declaring
what the actual type is. The type still needs to be fully defined
eventually in the same scope as its forward type declaration. E.g.
```
typedef T;
T x;
typedef int T;
```
The forward type definition can also contain the kind of the type it is
going to be. E.g struct, union, class, etc. The LRM calls this the basic
type. If the actual type is not of the basic type specified in the forward
type declaration this is an error. E.g.
```
typedef struct T;
typedef int T; // Error, int is not a struct
```
It is legal to have more than one forward type declaration for the same
type name, as long as the basic type is the compatible. It is even legal to
have a forward type declaration after the actual type has already been
declared. E.g.
```
typedef T;
typedef int T;
typedef T;
```
Implement support for forward type definitions as part of the new
typedef_t. The basic type will be attached to the typedef_t.
The compatibility of the basic type for multiple forward type declarations
will be checked in the parser. The compatibility of the basic type to the
actual type will be checked during elaboration, once the actual type is
known.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Currently typedefs are just a pointer to a data_type_t.
Currently typedefs are implemented by setting the name field of a
data_type_t when a typedef of the type is declared. This works mostly, but
there are some corner cases that can't be supported.
E.g. a typedef of a typedef does not work as it overwrites the name field
of the same data_type_t multiple times.
Forward typedefs can also not be supported since forward typedefs allow to
reference a type before it has been declared.
There are also some problems with type identifier references from a
higher-level scope if there is a type identifier in the current scope with
the same name, but it is declared after the type identifier has been
referenced. E.g. in the following x should be a vector fo width 8, but it
will be a vector of width 4, because while the right type is used it is
elaborated in the wrong scope.
```
localparam A = 8;
typedef logic [A-1:0] T;
module M;
localparam A = 4;
T x;
typedef int T;
endmodule
```
Furthermore typedefs used for the type of ports are elaborated in the wrong
scope.
To handle these corner case issues introduce a data_type_t for typedefs.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
SystemVerilog supports using compressed assignment operators for the genvar
for loop variable update.
Add support for this in a similar way as increment/decrement operators by
transforming the statement to its uncompressed equivalent. E.g. `x += y`
gets transformed to `x = x + y`.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
If the type of a port is an array type it currently always gets evaluated
in the scope where the port is declared.
But if the type is a typedef it might be declared in a different scope and
must be evaluated in that scope. E.g. the following will declare an array
port with 10 entries and an element type of a 5 bit vector, while it should
declare one with 4 entries and an element type of a 2 bit vector.
```
localparam A = 2;
localparam B = 4;
typedef [A-1:0] T[B];
module test (
T x
);
localparam A = 5;
localparam B = 10;
endmodule
```
This is in part due to array types being given special handling. This was
necessary before because each base type required slightly different
handling and so the base type had to be extracted from the array type.
This has now been consolidated and all data types are treated the same.
The only exception is the vector type which still needs special handling to
support separate definition of port direction and type.
As a result it is possible to remove the special handling of the array
type. This solves the problem of evaluating the type in the wrong scope.
Some special handling needs to be retained though to be able to
differentiate between array dimensions that are part of a type and array
dimensions that are part of port declaration. This is again necessary to
correctly support separate definition of port direction and type. E.g. in
the example below port `x` and `y` get treated slightly differently, even
though the resulting signals will be identical.
```
typedef logic [7:0] T[1:0];
...
input T x;
input [7:0] y[1:0];
```
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The parser used to have behavior that was dependent on the
`ivl_variable_type_t` of a signal. It also used the `ivl_variable_type_t`
of a signal to decide whether a signal can be re-declared as part of a
non-ANSI port declaration.
Neither of these is done anymore and most of the reference to
`ivl_variable_type_t` can be removed from the parser. The only thing it is
still needed for is to decide whether a vector type is 4-state or 2-state.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
`pform_make_task_ports()` has code very similar to `pform_set_net_range()`.
Use that helper function instead of duplicating the code.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The last user of the `make_range_from_width()` was removed in commit
f6042033d0 ("Correctly handle separate port type declaration for
`integer` and `time`").
Remove the function itself as well.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The custom `svector` class is essentially a subset of `std::vector`. There
is no inherent advantage to using `svector`. Both have the same memory
footprint.
`svector` was designed to be of static size, but there are a few places in
the parser where it has to grow at runtime. Handling this becomes a bit
easier by switching to `std::vector` since it is possible to use its
methods which take care of resizing the vector.
This also allows to remove the unused parameter of the `lgate` struct
constructor, which was only needed for compatibility with `svector`.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The lgate struct has its own fields for tracking file and line number,
while everything else that has this information attached inherits from the
LineInfo class.
Make lgate also inherit from LineInfo for consistency.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The `pform_set_data_type()` function is used to set the data type as well
as attributes on a list of signals. Currently the signals are passed as a
list of signal names and then the function looks up the actual signals from
the names.
Refactor the code to directly pass a list of signals. This will allow to
skip the look-up by name.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
`pform_set_reg_idx()` is always called right after `pform_makewire()`. Move
it into the function. This avoids the extra lookup that
`pform_set_reg_idx()` does to get the PWire from the name.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
There are a few parameters for `pform_makewire()` and related functions
that always get passed the same value.
* port_type is always NetNet::NOT_A_PORT
* attr is always 0
Remove these parameters.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The `pform_set_param_from_type()` function is not used. The last user was
removed in commit 16646c547c ("Rework parsing of parameter types").
Remove the function itself.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
There are a couple of different functions for the different data types that
are called when the type of a signal is set. But they all effectively do
the same.
Consolidate this code by moving the common code into the main
pform_set_data_type() function.
This allows to remove most of the type specific functions and eliminates
some duplicated code. It ensures consistent and data type independent
behavior at the parser level. Something that will be required to eventually
support type parameters.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Currently there is a mix of passing line information either as `struct
vlltype` or as a separate `const char *file` and `unsigned lineno`.
For consistency always use the struct vlltype variant.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
There is a function prototype for `pform_make_reals()`, but the function
is never declared nor used. Remove it.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
While a variable can have any data type the data type for nets is quite
restricted.
The SystemVerilog LRM section 6.7.1 ("Net declarations with built-in net
types") requires that the data type of a wire is either a 4-state packed or
a unpacked struct or unpacked array of 4-state packed types.
As an extension to this iverilog allows real data type for wires as well as
2-state packed types.
Add a check that reports an error if a net with any other type is declared.
In addition in Verilog a net can not have an explicit data type at all. It
can only have a packed dimension and a signed flag. As an extension to this
Icarus also allows wires to be of `real` data type.
Note that in Verilog mode the data type is checked in the parser since only
the parser knows whether the data type is an implicit type (`input reg
[7:0]` and `input [7:0] x` elaborate the same). But for SystemVerilog the
type is checked during elaboration since due to forward typedefs and type
parameters the type is not necessarily known in the parser.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
SystemVerilog allows unpacked array dimensions on non-ANSI style task and
function ports.
To support this refactor pform_make_task_ports() to accept a of
pform_port_t, which in addition to the identifier name also allows to
specify per port unpacked dimensions.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Tasks and functions support two types of port declarations. Either ANSI
style, in parenthesis after the task name, or non-ANSI style, as
declaration statements in the task body.
In the current implementation SystemVerilog types are only accept for ANSI
style port declarations, while non-ANSI style only accept Verilog types
(reg, integer, time, real).
Add support for SystemVerilog data types for non-ansi style ports.
This also makes the parsing rules simpler since we can use `data_type` to
match all data types and don't need a explicit rule for each supported data
type.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
SystemVerilog allows `parameter` and `localparam` to declare constants
within a class scope. E.g.
```SystemVerilog
class C;
localparam A = 10;
endclass
```
In this context both declare a local parameter that can not be overwritten.
Supporting this can be achieved for the most part by adding a parser
sub-rule in class declaration rule. In addition some extra support code is
needed to mark the parameter as non-overridable.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Variables don't have a delay or strength and the NetNet::Type is known. Add
a small wrapper around pform_makewire() that can be used to create
variables. This will allow to reduce the boilerplate code for variable
declarations.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
There are a few places where pform_makewire() is used and attributes can be
attached to the created net or variable. At the moment pform_makewire()
doesn't allow to specify the attributes, and they either get dropped
silently or with a warning.
Add support for passing the attributes to pform_makewire() which will then
pass it on to pform_set_data_type() to attach it to the declared net or
variable.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
There are currently two very similar implementations of pform_makewire().
One that takes a `net_decl_assign_t`, the other a `std::list<decl_assignment_t*>`.
The one that takes a `std::list<decl_assignment_t*>` is a superset of the
other. It can handle both wires and variables, while the other can only
handle wires.
Update the parser to generate a `std::list<decl_assignment_t*>` for wire
declarations. This allows to remove one of the two functions.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Most things that can be declared in a module can also be declared in a
generate block.
But there are a few exceptions that can not be declared in generate block
* module, program or interface declaration
* specify block or specparam
* timeunit
Some of these currently work while some of them trigger an assertion and
cause and application crash.
Add checks to make sure that all of them these are reported as an error and
do not cause a crash.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
SystemVerilog allows to omit the default value of a parameter declared in a
parameter port list. In this case the parameter must be overridden for
every module instance. This is defined in section 6.20.1 ("Parameter
declaration syntax") of the LRM (1800-2017).
In addition a module that has a parameter without a default value does not
qualify for automatic toplevel module selection.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
When encountering a construct that requires SystemVerilog in most cases an
error message is generated when SystemVerilog is not enabled and parsing
simply continues.
Factor the checking and generating of the error message into a helper
function. This slightly reduces boiler plate code and gives consistent
error messages.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The code for handling parameter and localparameter declarations is very
similar. Consolidate this into a single helper function.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
When creating an enum type it must be added to the scope where it is
declared so it can later be elaborated and the enum and its names can be
referenced in expressions.
In addition the names of the enum must be added to the lexor scope so that
name collisions are detected and can be reported as errors.
This is done with pform_put_enum_type_in_scope() function.
At the moment the function is called from two different places
* When adding a typedef of a enum type
* When creating a signal of a enum type
In addition the enum_type_t is added to a class scope `enum_sets` when
declaring a enum property in a class. But this only makes sure that the
enum gets elaborated, its names are not added to the lexor scope.
This works fine for the most part, but breaks for a few corner cases.
E.g. it is possible to declare a enum type as part of the subtype of
another packed type such as structs or packed arrays. E.g.
```
struct packed {
enum {
A
} e;
} s;
```
This is not covered by either of the cases above and neither do the names
of the enum get added to the lexor scope, nor is the enum type elaborated.
Another corner case that is currently not working is declaring a class
property where the type is a typedef of a enum that is declared outside of
the class. In this case the enum is elaborated again inside the class
scope. E.g. the below is supposed to work, but fails with an already
declared symbol error.
```
typedef enum {
A
} e_t;
class C;
typedef enum {
A
} e1;
e_t e2;
endclass
```
In addition since for enums declared in classes they are only added to
`enum_sets`, but names are not added to the lexor scope, it is possible to
declare a different symbol in the class scope with the same name.
E.g. the following elaborates fine
```
class C;
enum {
A
} e;
typedef int A;
endclass
```
To fix this call pform_put_enum_type_in_scope() when the enum_type_t is
created in the parser. This makes sure that it is handled the same
regardless where the type is declared or used.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>