If a net or variable is referenced in another net or variable declaration
or in a value parameter definition (e.g. when using the $bits function)
and hasn't already been elaborated, we need to elaborate it early. So
during the scope elaboration phase, add placeholders in each NetScope
object to record the PWire objects that are yet to be elaborated. This
allows the symbol_search() function to find the unelaborated objects
and to trigger early elaboration.
Add a flag in the PWire object to indicate when we are elaborating it.
This allows us to detect circular references and avoid an infinite loop.
This fixes issue #483, issue #575, and issue #1097.
This is legal if the procedural and continuous assignments target
different words.
NOTE: This is not fully compliant with the standard, because vvp
does not know that the nets were originally declared as variables,
so initialises to 'bz instead of 'bx and does not handle release
correctly.
Internally we convert SystemVerilog variables that have a continuous
assignment into unresolved wires. But from a user's perspective they
are still variables, so we should refer to them as such in error
messages. This new flag lets us distinguish between such variables
and nets that were declared as uwires.
Most places in the code use a std::vector for array dimensions.
The only exception is the constructor of NetNet, which uses
a `std::list` to pass the unpacked dimensions. But to store the
unpacked dimensions it also uses a `std::vector`.
There does not seem to be a good reason why the constructor
has to take a `std::list`, so switch it also to `std::vector`.
This allows to simplify the code and remove some special handling
for `std::list<netrange_t>`.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
`std::vector<netrange_t>` is used for signal array dimensions. As such it is
used in quite a few places.
Add a typedef that can be used as a shorthand to refer to it. This helps to
keep lines where this is used from growing to overly long.
The new type is called `netranges_t`.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
There are two `NetNet` constructors, one for arrays and one for non-arrays.
There are a few places where the array constructor is used for non-arrays,
but with an empty unpacked dimensions list. Switch this over to using the
non-array constructor.
This slightly reduces boiler-plate code.
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>
SystemVerilog allows to use assignment patterns to assign values to an
array. E.g. `int a[4] = '{1, 2, 3, 4}`.
Each value is evaluated in the context of the element type of the array.
Nested assignment patterns are supported. E.g. `int a[2][2] = '{'{1, 2},
'{1, 2}};`
Add initial support for array assignment patterns for both continuous as
well as procedural assignments.
For continuous assignments the assignment pattern is synthesized into an
array of nets. Each pin is connected to one of the assignment pattern
values and then the whole net array is connected to target array.
For procedural assignments it is unrolled in the vvp backend. E.g
effectively turning `a = '{1, 2};` into `a[0] = 1; a[1] = 2;`.
Not yet supported are indexed initializers or `default`.
E.g. `int a[10] = '{1:10, default: 20};`
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Types for array signals are currently handled as a special case. The type
that is associated with the signal is not the array type itself but rather
the element type.
There is a fair amount of existing code that depends on this behavior so it
is not trivial to change this.
But there are certain constructs such as assignment patterns or array
concatenation where the array type itself is required.
Add a new `NetNet::array_type()` method that will return the array type if
the signal is an array. This will allow to query the array type when
needed.
`NetAssign_::net_type()` is updated to use this new method to return the
array type if the assigned signal is an array.
Long term the special handling of arrays for signals should be removed.
This will for example allow to unify the handling of arrays for signals,
class properties and struct members.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The are many binary operations where if the two operands are 2-state the
result is guaranteed to be 2-state.
This is true for all arithmetic operation with the exception of division
where division by 0 will always result in 'x even if the inputs are both
2-state.
The same is true for all binary bitwise operators as well as the binary
logical operators.
Having the expression type be 2-state avoids some unnecessary %cast2
instructions that would otherwise get inserted when assigning the result to
a 2-state variable.
E.g without this change the following will result in
```
int a, b, c;
b = a + b;
```
will result in
```
%load/vec4 ...;
%load/vec4 ...;
%add;
%cast2;
%store/vec4 ...;
```
For binary comparison operators this is already handled.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Add support for `continue` and `break` in constant functions. This is done
in a similar way to how `disable` is implemented for constant functions.
Two new global flags are introduced `loop_break` and `loop_continue` that
get set when evaluating the corresponding statement. If either of these
flags are set all other statements are ignored until the end of a loop is
reached. At the end of the loop both `loop_break` and `loop_continue` get
cleared. If `loop_break` was set before clearing it the loop is exited.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
SystemVerilog allows to use the `return` statement in a task to exit the
task before it reaches the end of its execution. This is defined in section
13.3 ("Tasks") of the LRM (1800-2017).
This is similar to using `disable` to stop a task from within itself with
the difference that `disable` will affect all concurrently running
executions of a task, while `return` will only affect the task from which
it has been called.
The `%disable/flow` vvp instruction allows to implement the required
behavior for task return.
There is one complication in that it is not allowed to call return from
inside a parallel block (fork-join). If a parallel block is unnamed and has
no variable declarations there won't be a NetScope for it. So it is not
possible to detect whether the return is inside a parallel block by
walking up the scope chain.
To solve this add a design global counter that gets incremented when
entering a fork block and decremented when exiting a parallel block. The
return implementation then checks if the counter is non 0 to determine
whether it is in a parallel block.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The port_index_ member of the NetNet is not initialized which can lead to
undefined behavior. Make sure to initialize to -1 to indicate that the net
is not associated with any port.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
In SystemVerilog it is not allowed to assign a value to a enum variable
that is not of the same time as the enum variable.
This is currently enforced for assignment and continuous assignment. But
ignored in other places such as function parameter passing.
Move the enum type check into `elab_rval_expr()` to cover more cases.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
In most cases the type of an lvalue part select is the base type of the
lvalue with the width of the part select. But there are some exceptions.
1) An index into a `string` type is of type `byte`.
2) Packed structs are implemented as packed arrays under the hood. A lvalue
struct member is elaborated as a normal part select on a packed array. The
type of that select should be the type of the member.
For the case 1 there is some special handling for strings that accounts for
this. But for case 2 the type information of the member is lost.
This works fine for most things but there are a few constructs where the
type information is required.
* Enum type compatibility check
* Assignment pattern behavior depends on the type of the lvalue
Allow to attach a specific type to a lvalue part select to allow correct
behavior for constructs where the type is required.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The NetAssign_:net_type() function return the type of lvalue expression.
But it only does so for a limited amount of cases.
Refactor the function so that it works for the general case and always
returns the data type, if the data type of the lvalue expression is known.
This will allow to implement better type checking and other constructs such
as pattern assignments that require to know the type of the lvalue.
It also allows to remove some duplicated code in other methods of
NetAssign_ that want to lookup the type.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
There are a few NetExpr subclasses where the data type of the expression
is known, but it not attached to the NetExpr and only kept as a private
member in the subclass.
Attaching the type directly to the NetExpr allows to query it externally
and implement better type checking.
It also allows to remove a bit of duplicated code in the subclasses and
rely on the default implementation in the NetExpr base class.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The current NetExpr::enumeration() always returns a nullptr.
The NetExpr class has a ivl_type_t member that represents
the type of the expression.
Provide a default implementation of NetExpr::enumeration() that
casts this type to the netenum_t type. This will allow
to share this implementation between subclasses and remove
a bit of duplicated code.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
This includes support at the parser (pform) through enaboration
and the netlist format for the break and continue statements.
Elaboration actually already worked for for-loops, but since the code
generators need more information, this is a rewire of that support to
be explicit about for-loops. This means they are not rewritten as fancy
while loops. The code generators will have to handle that.
Given the elaboration of for-loops now work, write the vvp code generator
support needed to implement it.
Now that for-loops are presented as for-loops to the code generator, the
vlog95 code generator doesn't need to infer them anymore. Generate the code
more directly.
Also update the tests list so that the vlog95_reg tests all pass.
SystemVerilog allows to use the `super` keyword to access properties and
methods of a base class. This is useful if there is for example an
identifier with the same name in the current class as in the base class and
the code wants to access the base class identifier.
To support this a bit of refactoring is required. Currently properties are
internally referenced by name, this does not work if there are multiple
properties of the same. Instead reference properties always by index.
In addition when looking up an identifier that resolves to an object return
both the type and the object itself. This is necessary since both `this`
and `super` resolve to the same object, but each with a different 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>
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>
The default implementation of the virtual method `NetExpr::has_width()`
returns true. There are a few classes that directly inherit from NetExpr
that override the method with the exact same implementation. Remove these.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
There are no users for the `NetScope::find_enumeration_for_name()` and
`Definitons::enumeration_for_name()` methods. Remove both of them.
The last user was removed in commit 61a088fa78 ("Use elaborate_type()
infrastructure to elaborate signal types").
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 result for the built-in methods for the SystemVerilog types is
currently always unsigned. This can lead to incorrect behavior if the value
is sign extended or passed as an argument to a system function (e.g.
$display).
For most built-in methods this does not matter, since even though they have
a signed return type, they will not return a negative value. E.g. the
string `len()` or queue `size()` functions.
It does make a difference though for the queue `pop_front()` and
`pop_back()` methods. Their return type is the element type of the queue.
If the element type is signed and the value in queue is negative is will be
handled incorrectly.
E.g. the following will print `4294967295` rather than `-1`.
```
int q[$];
q.push_back(-1);
$display(q.pop_front());
```
To correctly support this consistently assign the actual data type of the
built-in method's return value to the `NetESFunc`, rather than just the width
and base type. The width, base type and also the signedness can be derived
from the data type.
Note that this only fixes the default signedness, but not the case where
the signedness of the expression is changed by its context (e.g. in
arithmetic expression). Handling this will require some additional work.
Also note that assigning the actual data type is also required to support type
checking on the return value, e.g. as needed for enum types.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
There are a couple of different NetNet constructors for different data
types. They are all very similar, consolidate them into a single
constructor taking a ivl_type_t.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
A `return` statement in a function gets translated into a vvp `%disable`
instruction. This works fine as long as no recursion is involved. The
`%disable` instruction will stop execution of all active threads of a
particular scope. For recursive functions this means as soon as the inner
most function returns all containing outer function calls get disabled as
well. This results in incorrect behavior.
To make recursive functions using the `return` statement work use the new
vvp `%disable/parent` instruction. This instruction will only disable the
closest thread in the thread hierarchy that matches the target scope.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
When using non-ANSI style port declarations it is possible to declare the
port direction and the data type for the port in separate statements. E.g.
```
input x;
reg x;
```
When using packed array dimensions they must match for both declarations.
E.g.
```
input [3:0] x;
reg [3:0] x;
```
But this only applies for vector types, i.e. the packed dimension is
explicitly declared. It does not apply to the `integer` and `time` types,
which have an implicit packed dimension.
The current implementation requires that even for `integer` and `time`
types the implicit dimension needs to be explicitly declared in the port
direction. E.g. the following will result in a elaboration error
complaining about a packed dimension mismatch.
```
module test;
output x;
integer x;
endmodule
```
Currently the parser creates a vector_type_t for `time` and `integer`. This
means that e.g. `time` and `reg [63:0]` are indistinguishable during
elaboration, even though they require different behavior.
To fix let the atom2_type_t handle `integer` and `time`. Since it no longer
exclusively handles 2-state types, rename it to atom_type_t.
This also fixes a problem with the vlog95 target unit tests. The vlog95
target translates
```
module test(output integer x);
endmodule
```
to
```
module test(x);
output x;
integer x;
endmodule
```
which then fails when being elaborated again. There were some regression
tests that were failing because of this that will now pass.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Void functions can be used in always_comb, but since the compiler
uses the check_synth() method to generate some warnings, make sure
that function is implemented for functions as well as tasks.
NetNet::get_isint() is never used anywhere, remove it. The information
whether a signal is an integer is always directly queried from the signal
data type.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Parameter expressions need to remember the scope they have been declared in
so that the code generator backends can insert the right parameter
reference, rather than a constant value.
Currently the scope is stored as a non-const reference. But that is not
needed. Mark the scope reference as const so NetEConstParam and
NetECRealParam can be created when only a const scope reference is
available.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The NetScope class has a method called find_parameter() that looks up the
parameter and returns a iterator to it. This is only ever used to get the
line information of the parameter.
Refactor the function so that it only returns the line info. This will
allow to call this function on a const NetScope object.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Parameters declared in certain scopes behave like local parameters and can
not be overridden. Rather than making those parameters a localparam track
whether a parameter can be overridden.
This allows to generate better error messages when trying to override the
parameter.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Overriding a parameter that does not exist will only generate a warning at
the moment. This can hide programming mistakes such as an typo in a
parameter override.
There is nothing in the LRMs to support that this should only be warning,
so elevate this to an error. This is consistent with how an error is
generated when trying to reference a non-existing port or variable.
The generated error message differentiates between whether the parameter
does not exist at all, or whether it is a localparam.
There are two regression tests that rely on that only a warning is
generated, these have been updated to expect an error.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
The scope_ field of the NetEConstEnum class is initialized in the
constructor, but never used anywhere again. Remove it.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
This requires us to make a copy of the typedefs map when adding it to
a NetScope object, because the pform data is deleted before we are
finished with it.