Skip to content

Commit

Permalink
[Major] Explicit compile time constant expressions. (#67)
Browse files Browse the repository at this point in the history
Add a constant type modifier for compile-time evaluation (under whole-world assumptions). Constant types in external ports implies that uses of constant types are not guaranteed to be know at compile time (this is not a mechanism to guarantee constant propagation).

Asynchronous resets are updated to take constant reset values. This moves ad-hoc checks which exist in implementations now to type checks. This is not sufficient for some tools, but is a strict improvement on the current implementations.

The change of asyncreset value is what makes this a major change.
  • Loading branch information
darthscsi authored Mar 1, 2023
1 parent 1fd1f69 commit a30819d
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 24 deletions.
1 change: 1 addition & 0 deletions revision-history.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ revisionHistory:
be preserved by a FIRRTL compiler
- Add Compiler Implementation Details documenting Lower Types pass
- Allow out-of-bounds errors to be caught at compile time.
- Define constant type modifier.
- Clarify the string argument for cover is a comment, not a message
as it is for assert and assume.
- Remove stray language leftover from removing conditionally valid.
Expand Down
145 changes: 121 additions & 24 deletions spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,55 @@ the module. The `c`{.firrtl} sub-field contained in the `b`{.firrtl} sub-field
flows into the module, and the `d`{.firrtl} sub-field contained in the
`b`{.firrtl} sub-field flows out of the module.

## Type Modifiers

### Constant Type

A constant type is a type whose value is guaranteed to be unchanging at circuit
execution time. Constant is a constraint on the mutability of the value, it
does not imply a literal value at a point in the emitted design. Constant types
may be used in ports, wire, nodes, and generally anywhere a non-constant type is
usable. Operations on constant type are well defined. As a general rule (with
any exception listed in the definition for such operations as have exceptions),
an operation whose arguments are constant produces a constant. An operation
with some non-constant arguments produce a non-constant. Constants can be used
in any context with a source flow which allows a non-constant. Constants may be
used as the target of a connect so long as the source of the connect is itself
constant. These rules ensure all constants are derived from literals or from
constant-typed input ports of the top-level module.

``` firrtl
const UInt<3>
const SInt
const {real: UInt<32>, imag : UInt<32>, other : const SInt}
```

Last-connect semantics of constant typed values are well defined, so long as any
control flow is conditioned on an expression which has a constant type. This
means if a constant is being assigned to in a `when` block, the `when`'s
condition must be a constant.

Output ports of external modules and input ports to the top-level module may be
constant. In such case, the value of the port is not known, but that it is
non-mutating at runtime is known.

The indexing of a constant aggregate produces a constant of the appropriate type
for the element.

#### A note on implementation

Constant types are a restriction on FIRRTL types. Therefore, FIRRTL structures
which would be expected to produce certain Verilog structures will produce the
same structure if instantiated with a constant type. For example, an input port
of type `const UInt` will result in a port in the Verilog, if under the same
conditions an input port of type `UInt` would have.

It is not intended that constants are a replacement for parameterization.
Constant typed values have no particular meta-programming capability. It is,
for example, expected that a module with a constant input port be fully
compilable to non-parameterized Verilog.


## Passive Types

It is inappropriate for some circuit components to be declared with a type that
Expand Down Expand Up @@ -745,7 +794,8 @@ the current value of the element, writes are not visible until after a positive
edges of the register's clock port.

The clock signal for a register must be of type `Clock`{.firrtl}. The type of a
register must be a passive type (see [@sec:passive-types]).
register must be a passive type (see [@sec:passive-types]) and may not be
`const`.{.firrtl}.

The following example demonstrates instantiating a register with the given name
`myreg`{.firrtl}, type `SInt`{.firrtl}, and is driven by the clock signal
Expand All @@ -761,14 +811,15 @@ A register may be declared with a reset signal and value. The register's value
is updated with the reset value when the reset is asserted. The reset signal
must be a `Reset`{.firrtl}, `UInt<1>`{.firrtl}, or `AsyncReset`{.firrtl}, and
the type of initialization value must be equivalent to the declared type of the
register (see [@sec:type-equivalence] for details). The behavior of the
register depends on the type of the reset signal. `AsyncReset`{.firrtl} will
immediately change the value of the register. `UInt<1>`{.firrtl} will not
change the value of the register until the next positive edge of the clock
signal (see [@sec:reset-type]). `Reset`{.firrtl} is an abstract reset whose
behavior depends on reset inference. In the following example, `myreg`{.firrtl}
is assigned the value `myinit`{.firrtl} when the signal `myreset`{.firrtl} is
high.
register (see [@sec:type-equivalence] for details). If the reset signal is an
`AsyncReset`{.firrtl}, then the reset value must be a constant type. The
behavior of the register depends on the type of the reset signal.
`AsyncReset`{.firrtl} will immediately change the value of the register.
`UInt<1>`{.firrtl} will not change the value of the register until the next
positive edge of the clock signal (see [@sec:reset-type]). `Reset`{.firrtl} is
an abstract reset whose behavior depends on reset inference. In the following
example, `myreg`{.firrtl} is assigned the value `myinit`{.firrtl} when the
signal `myreset`{.firrtl} is high.

``` firrtl
wire myclock: Clock
Expand Down Expand Up @@ -1341,6 +1392,12 @@ data that is read out of the read port is undefined.
In all cases, if a memory location is written to by more than one port on the
same cycle, the stored value is undefined.

### Constant memory type

A memory with a constant data-type represents a ROM and may not have
write-ports. It is beyond the scope of this specification how ROMs are
initialized.

## Instances

FIRRTL modules are instantiated with the instance statement. The following
Expand Down Expand Up @@ -1539,8 +1596,8 @@ for performing primitive operations.
## Unsigned Integers

A literal unsigned integer can be created given a non-negative integer value and
an optional positive bit width. The following example creates a 10-bit unsigned
integer representing the number 42.
an optional positive bit width. Integer literals are of constant type. The
following example creates a 10-bit unsigned integer representing the number 42.

``` firrtl
UInt<10>(42)
Expand All @@ -1557,7 +1614,8 @@ UInt(42)
## Unsigned Integers from Literal Bits

A literal unsigned integer can alternatively be created given a string
representing its bit representation and an optional bit width.
representing its bit representation and an optional bit width. Like with the
integer representation, the expression has a constant type.

The following radices are supported:

Expand Down Expand Up @@ -1594,8 +1652,9 @@ UInt<7>("hD")
## Signed Integers

Similar to unsigned integers, a literal signed integer can be created given an
integer value and an optional positive bit width. The following example creates
a 10-bit unsigned integer representing the number -42.
integer value and an optional positive bit width. Integer literals are of
constant type. The following example creates a 10-bit unsigned integer
representing the number -42.

``` firrtl
SInt<10>(-42)
Expand All @@ -1614,7 +1673,8 @@ SInt(-42)

Similar to unsigned integers, a literal signed integer can alternatively be
created given a string representing its bit representation and an optional bit
width.
width. Like with the integer representation, the expression has a constant
type.

The bit representation contains a binary, octal or hex indicator, followed by an
optional sign, followed by the value.
Expand Down Expand Up @@ -1660,7 +1720,9 @@ will be rewritten as "the port `in`{.firrtl} is connected to the port
## Sub-fields

The sub-field expression refers to a sub-element of an expression with a bundle
type.
type. If the expression is of a constant bundle type, the sub-element shall be
of a constant type (`const`.{firrtl} propagates from the bundle to the element
on indexing).

The following example connects the `in`{.firrtl} port to the `a`{.firrtl}
sub-element of the `out`{.firrtl} port.
Expand All @@ -1672,11 +1734,33 @@ module MyModule :
out.a <= in
```

The following example is the same as above, but with a constant output bundle.

``` firrtl
module MyModule :
input in: const UInt
output out: const {a: UInt, b: UInt}
out.a <= in ; out.a is of type const UInt
```

The following example is the same as above, but with a bundle with a constant
field.

``` firrtl
module MyModule :
input in: const UInt
output out: {a: const UInt, b: UInt}
out.a <= in ; out.a is of type const UInt
```


## Sub-indices

The sub-index expression statically refers, by index, to a sub-element of an
expression with a vector type. The index must be a non-negative integer and
cannot be equal to or exceed the length of the vector it indexes.
cannot be equal to or exceed the length of the vector it indexes. If the
expression is of a constant vector type, the sub-element shall be of a constant
type.

The following example connects the `in`{.firrtl} port to the fifth sub-element
of the `out`{.firrtl} port.
Expand All @@ -1688,15 +1772,26 @@ module MyModule :
out[4] <= in
```

The following example is the same as above, but with a constant vector.

``` firrtl
module MyModule :
input in: const UInt
output out: const UInt[10]
out[4] <= in ; out[4] has a type of const UInt
```


## Sub-accesses

The sub-access expression dynamically refers to a sub-element of a vector-typed
expression using a calculated index. The index must be an expression with an
unsigned integer type. An access to an out-of-bounds element results in an
indeterminate value (see [@sec:indeterminate-values]). Each out-of-bounds
element is a different indeterminate value. Sub-access operations with constant
index may be convereted to sub-index operations even though it converts
indeterminate-value-on-out-of-bounds behavior to a compile-time error.
unsigned integer type. If the expression is of a constant vector type, the
sub-element shall be of a constant type. An access to an out-of-bounds element
results in an indeterminate value (see [@sec:indeterminate-values]). Each
out-of-bounds element is a different indeterminate value. Sub-access operations
with constant index may be converted to sub-index operations even though it
converts indeterminate-value-on-out-of-bounds behavior to a compile-time error.

The following example connects the n'th sub-element of the `in`{.firrtl} port to
the `out`{.firrtl} port.
Expand Down Expand Up @@ -1867,7 +1962,9 @@ primitive operation.
The arguments of all primitive operations must be expressions with ground types,
while their parameters are static integer literals. Each specific operation can
place additional restrictions on the number and types of their arguments and
parameters.
parameters. Primitive operations may have all their arguments of constant type,
in which case their return type is of constant type. If the operation has a
mixed constant and non-constant arguments, the result is non-constant.

Notationally, the width of an argument e is represented as w~e~.

Expand Down Expand Up @@ -2674,7 +2771,7 @@ type_ground = "Clock" | "Reset" | "AsyncReset"
type_aggregate = "{" , field , { field } , "}"
| type , "[" , int , "]" ;
field = [ "flip" ] , id , ":" , type ;
type = type_ground | type_aggregate ;
type = [ "const" ], ( type_ground | type_aggregate ) ;
(* Primitive operations *)
primop_2expr_keyword =
Expand Down

0 comments on commit a30819d

Please sign in to comment.