author | title | revisionHistorySpec | |
---|---|---|---|
|
Specification for the FIRRTL Language |
true |
The ideas for FIRRTL (Flexible Intermediate Representation for RTL) originated from work on Chisel, a hardware description language (HDL) embedded in Scala used for writing highly-parameterized circuit design generators. Chisel designers manipulate circuit components using Scala functions, encode their interfaces in Scala types, and use Scala's object-orientation features to write their own circuit libraries. This form of meta-programming enables expressive, reliable and type-safe generators that improve RTL design productivity and robustness.
The computer architecture research group at U.C. Berkeley relies critically on Chisel to allow small teams of graduate students to design sophisticated RTL circuits. Over a three year period with under twelve graduate students, the architecture group has taped-out over ten different designs.
Internally, the investment in developing and learning Chisel was rewarded with huge gains in productivity. However, Chisel's external rate of adoption was slow for the following reasons.
-
Writing custom circuit transformers requires intimate knowledge about the internals of the Chisel compiler.
-
Chisel semantics are under-specified and thus impossible to target from other languages.
-
Error checking is unprincipled due to under-specified semantics resulting in incomprehensible error messages.
-
Learning a functional programming language (Scala) is difficult for RTL designers with limited programming language experience.
-
Confounding the previous point, conceptually separating the embedded Chisel HDL from the host language is difficult for new users.
-
The output of Chisel (Verilog) is unreadable and slow to simulate.
As a consequence, Chisel needed to be redesigned from the ground up to standardize its semantics, modularize its compilation process, and cleanly separate its front-end, intermediate representation, and backends. A well defined intermediate representation (IR) allows the system to be targeted by other HDLs embedded in other host programming languages, making it possible for RTL designers to work within a language they are already comfortable with. A clearly defined IR with a concrete syntax also allows for inspection of the output of circuit generators and transformers thus making clear the distinction between the host language and the constructed circuit. Clearly defined semantics allows users without knowledge of the compiler implementation to write circuit transformers; examples include optimization of circuits for simulation speed, and automatic insertion of signal activity counters. An additional benefit of a well defined IR is the structural invariants that can be enforced before and after each compilation stage, resulting in a more robust compiler and structured mechanism for error checking.
FIRRTL represents the standardized elaborated circuit that the Chisel HDL produces. FIRRTL represents the circuit immediately after Chisel's elaboration. It is designed to resemble the Chisel HDL after all meta-programming has executed. Thus, a user program that makes little use of meta-programming facilities should look almost identical to the generated FIRRTL.
For this reason, FIRRTL has first-class support for high-level constructs such as vector types, bundle types, conditional statements, and modules. A FIRRTL compiler may choose to convert high-level constructs into low-level constructs before generating Verilog.
Because the host language is now used solely for its meta-programming facilities, the frontend can be very light-weight, and additional HDLs written in other languages can target FIRRTL and reuse the majority of the compiler toolchain.
The FIRRTL specification was originally published as a UC Berkeley Tech Report
(UCB/EECS-2016-9)
authored by Adam Izraelevitz (@azidar
), Patrick
Li (@CuppoJava
), and Jonathan Bachrach
(@jackbackrack
). The vision for FIRRTL was
then expanded in an ICCAD
paper and in Adam's
thesis.
During that time and since, there have been a number of contributions and improvements to the specification. To better reflect the work of contributors after the original tech report, the FIRRTL specification was changed to be authored by The FIRRTL Specification Contributors. A list of these contributors is below:
@albert-magyar
@azidar
@ben-marshall
@boqwxp
@chick
@dansvo
@darthscsi
@debs-sifive
@donggyukim
@dtzSiFive
@eigenform
@ekiwi
@ekiwi-sifive
@felixonmars
@grebe
@jackkoenig
@jared-barocsi
@keszocze
@mwachs5
@prithayan
@richardxia
@rwy7
@seldridge
@sequencer
@shunshou
@tdb-alcorn
@tymcauley
@uenoku
@youngar
A FIRRTL file begins with a magic string and version identifier indicating the version of this standard the file conforms to (see [@sec:versioning-scheme-of-this-document]). This will not be present on files generated according to versions of this standard prior to the first versioned release of this standard to include this preamble.
FIRRTL version 1.1.0
circuit MyTop...
All FIRRTL circuits consist of a list of modules, each representing a hardware block that can be instantiated. The circuit must specify the name of the top-level module.
circuit MyTop :
module MyTop :
; ...
module MyModule :
; ...
Each module has a given name, a list of ports, and a list of statements representing the circuit connections within the module. A module port is specified by its direction, which may be input or output, a name, and the data type of the port.
The following example declares a module with one input port, one output port, and one statement connecting the input port to the output port. See [@sec:connects] for details on the connect statement.
module MyModule :
input foo: UInt
output bar: UInt
connect bar, foo
Note that a module definition does not indicate that the module will be physically present in the final circuit. Refer to the description of the instance statement for details on how to instantiate a module ([@sec:instances]).
Optional groups are named collections of statements inside a module. Optional groups contain functionality which will not be present in all executions of a circuit. Optional groups are intended to be used to keep verification, debugging, or other collateral, not relevant to the operation of the circuit, in a separate area. Each group can then be optionally included in the resulting design.
The declgroup
{.firrtl} keyword declares an optional group with a specific
identifier. An optional group may be declared in a circuit or in another
optional group declaration. An optional group's identifier must be unique
within the current namespace. I.e., the identifier of a top-level group
declared in a circuit must not conflict with the identifier of a module,
external module, or implementation defined module.
Each optional group declaration must include a string that sets the lowering
convention for that group. The FIRRTL ABI specification defines supported
lowering convention. One such strategy is "bind"
{.firrtl} which lowers to
modules and instances which are instantiated using the SystemVerilog
bind
{.verilog} feature.
The group
{.firrtl} keyword defines optional functionality inside a module. An
optional group may only be defined inside a module. An optional group must
reference a group declared in the current circuit. Declarations of identifiers
and references to existing identifiers following the same lexical scoping rules
as FIRRTL conditional statements (see: [@sec:scoping])---identifiers declared in
the group definition may not be used outside the group while groups may refer to
identifiers declared outside the group. The statements in a group are
restricted in what identifiers they are allowed to drive. A statement in a
group may drive no sinks declared outside the group with one exception: a
statement in a group may drive reference types declared outside the group if the
reference types are associated with the group in which the statement is declared
(see: [@sec:reference-types]).
The circuit below contains one optional group declaration, Bar
. Module Foo
contains a group definition that creates a node computed from a port defined in
the scope of Foo
.
circuit Foo:
declgroup Bar, bind: ; Declaration of group Bar with convention "bind"
module Foo:
input a: UInt<1>
group Bar: ; Definition of group Bar inside module Foo
node notA = not(a)
Optional group declarations may be nested. Optional group declarations are
declared with the declgroup
{.firrtl} keyword indented under an existing
declgroup
{.firrtl} keyword. The circuit below contains four optional group
declarations, three of which are nested. Bar
is the top-level group. Baz
and Qux
are nested under Bar
. Quz
is nested under Qux
.
circuit Foo:
declgroup Bar, bind:
declgroup Baz, bind:
declgroup Qux, bind:
declgroup Quz, bind:
Optional group definitions must match the nesting of declared groups. Optional
groups are defined under existing groups with the group
{.firrtl} keyword
indented under an existing group
{.firrtl} keyword. For the four declared
groups in the circuit above, the following is a legal nesting of group
definitions:
module Foo:
input a: UInt<1>
group Bar:
node notA = not(a)
group Baz:
group Qux:
group Quz:
node notNotA = not(notA)
Statements in a nested optional group may only read from ports or declarations
of the current module, the current group, or a parent group---statements in a
group may not drive components declared outside the group except reference types
associated with the same group. In the above example, notA
is accessible in
the group definition of Quz
because notA
is declared in a parent group.
In the example below, module Baz
defines a group Bar
. Module Baz
has an
output port, _a
, that is associated with the group, Bar
. This port can then
be driven from inside the group. In module Foo
, the port may be read from
inside the group. Stated differently, module Baz
has an additional port _a
that is only accessible inside a defined group Bar
.
circuit Foo:
declgroup Bar, bind:
module Baz:
output _a: Probe<UInt<1>, Bar>
wire a: UInt<1>
group Bar:
node notA = not(a)
define _a = probe(notA)
module Foo:
inst baz of Baz
group Bar:
node _b = baz._a
If a port is associated with a nested group then a period is used to indicate
the nesting. E.g., the following circuit has a port associated with the nested
group Bar.Baz
:
circuit Foo:
declgroup Bar, bind:
declgroup Baz, bind:
module Foo:
output a: Probe<UInt<1>, Bar.Baz>
Optional groups will be compiled to modules whose ports are derived from what they capture from their visible scope. For full details of the way optional groups are compiled, see the FIRRTL ABI specification.
Externally defined modules are modules whose implementation is not provided in
the current circuit. Only the ports and name of the externally defined module
are specified in the circuit. An externally defined module may include, in
order, an optional defname which sets the name of the external module in the
resulting Verilog, zero or more name--value parameter statements, and zero or
more ref statements indicating the resolved paths of the module's exported
references. Each name--value parameter statement will result in a value being
passed to the named parameter in the resulting Verilog. Every port or port
sub-element of reference type must have exactly one ref
{.firrtl} statement.
The widths of all externally defined module ports must be specified. Width inference, described in [@sec:width-inference], is not supported for externally defined module ports.
A common use of an externally defined module is to represent a Verilog module that will be written separately and provided together with FIRRTL-generated Verilog to downstream tools.
An example of an externally defined module with parameters is:
extmodule MyExternalModule :
input foo: UInt<2>
output bar: UInt<4>
output baz: SInt<8>
defname = VerilogName
parameter x = "hello"
parameter y = 42
An example of an externally defined module with references is:
extmodule MyExternalModuleWithRefs :
input foo : UInt<2>
output mysignal : Probe<UInt<1>>
output myreg : RWProbe<UInt<8>>
ref mysignal is "a.b"
ref myreg is "x.y"
These resolved reference paths capture important information for use in the current FIRRTL design. While they are part of the FIRRTL-level interface to the external module, they are not expected to correspond to a particular Verilog construct. They exist to carry information about the implementation of the extmodule necessary for code generation of the current circuit.
The types of parameters may be any of the following literal types. See [@sec:literals] for more information:
- Integer literal, e.g.
42
{.firrtl} - String literal, e.g.,
"hello"
{.firrtl} - Raw String Literal, e.g.,
'world'
{.firrtl}
An integer literal is lowered to a Verilog literal. A string literal is lowered to a Verilog string. A raw string literal is lowered verbatim to Verilog.
As an example, consider the following external module:
extmodule Foo:
parameter foo = 'hello'
parameter bar = "world"
parameter baz = 42
This is lowered to a Verilog instantiation site as:
Foo #(
.foo(hello),
.bar("world")
.baz(42)
) bar();
Intrinsic modules are modules which represent implementation-defined, compiler-provided functionality. Intrinsics generally are used for functionality which requires knowledge of the implementation or circuit not available to a library writer. What intrinsics are supported by an implementation is defined by the implementation. The particular intrinsic represented by an intrinsic module is encoded in intrinsic. The name of the intmodule is only used to identify a specific instance. An implementation shall type-check all ports and parameters. Ports may be uninferred (either width or reset) if specified by the implementation (which is useful for inspecting and interacting with those inference features).
intmodule MyIntrinsicModule_xhello_y64 :
input foo: UInt
output bar: UInt<4>
output baz: SInt<8>
intrinsic = IntrinsicName
parameter x = "hello"
parameter y = 42
The types of intrinsic module parameters may only be literal integers or string literals.
FIRRTL has both integer, string, and raw string literals.
An integer literal is a signed or unsigned decimal integer. The following are examples of integer literals:
42
-9000
A string literal is a sequence of characters with a leading "
{.firrtl} and a
trailing "
{.firrtl}. The following is an example of a string literal:
"hello"
A raw string literal is a sequence of characters with a leading '
{.firrtl} and
a trailing '
{.firrtl}. The following is an example of a raw string literal:
'world'
A radix-specified integer literal is a special integer literal with one of the following leading characters to indicate the numerical encoding:
0b
{.firrtl} -- for representing binary numbers0o
{.firrtl} -- for representing octal numbers0d
{.firrtl} -- for representing decimal numbers0h
{.firrtl} -- for representing hexadecimal numbers
Signed radix-specified integer literals have their sign before the leading encoding character.
The following string-encoded integer literals all have the value 42
:
0b101010
0o52
0d42
0h2a
The following string-encoded integer literals all have the value -42
:
-0b101010
-0o52
-0d42
-0h2a
Radix-specified integer literals are only usable when constructing hardware integer literals. Any use in place of an integer is disallowed.
FIRRTL has four classes of types: ground types, aggregate types, reference types, and property types. Ground types are fundamental and are not composed of other types. Aggregate types and reference types are composed of one or more aggregate or ground types. Reference types may not contain other reference types. Property types represent information about the circuit that is not hardware.
There are five classes of ground types in FIRRTL: unsigned integer types, signed integer types, a clock type, reset types, and analog types.
Both unsigned and signed integer types may optionally be given a known non-negative integer bit width.
UInt ; unsigned int type with inferred width
SInt ; signed int type with inferred width
UInt<10> ; unsigned int type with 10 bits
SInt<32> ; signed int type with 32 bits
Alternatively, if the bit width is omitted, it will be automatically inferred by FIRRTL's width inferencer, as detailed in [@sec:width-inference].
Integers of width zero are permissible. They are always zero extended.
Thus, when used in an operation that extends to a positive bit width, they
behave like a zero. While zero bit width integers carry no information, we
allow 0-bit integer constant zeros for convenience:
UInt<0>(0)
{.firrtl} and SInt<0>(0)
{.firrtl}.
wire zero_u : UInt<0>
invalidate zero_u
wire zero_s : SInt<0>
invalidate zero_s
wire one_u : UInt<1>
connect one_u, zero_u
wire one_s : SInt<1>
connect one_s, zero_s
Is equivalent to:
wire one_u : UInt<1>
connect one_u, UInt<1>(0)
wire one_s : SInt<1>
connect one_s, SInt<1>(0)
The clock type is used to describe wires and ports meant for carrying clock signals. The usage of components with clock types are restricted. Clock signals cannot be used in most primitive operations, and clock signals can only be connected to components that have been declared with the clock type.
The clock type is specified as follows:
Clock
The uninferred Reset
{.firrtl} type is either inferred to UInt<1>
{.firrtl}
(synchronous reset) or AsyncReset
{.firrtl} (asynchronous reset) during
compilation.
Reset ; inferred type
AsyncReset
Synchronous resets used in registers will be mapped to a hardware description language representation for synchronous resets.
The following example shows an uninferred reset that will get inferred to a synchronous reset.
input a : UInt<1>
wire reset : Reset
connect reset, a
After reset inference, reset
{.firrtl} is inferred to the synchronous
UInt<1>
{.firrtl} type:
input a : UInt<1>
wire reset : UInt<1>
connect reset, a
Asynchronous resets used in registers will be mapped to a hardware description language representation for asynchronous resets.
The following example demonstrates usage of an asynchronous reset.
input clock : Clock
input reset : AsyncReset
input x : UInt<8>
regreset y : UInt<8>, clock, reset, UInt(123)
; ...
Inference rules are as follows:
- An uninferred reset driven by and/or driving only asynchronous resets will be inferred as asynchronous reset.
- An uninferred reset driven by and/or driving both asynchronous and synchronous resets is an error.
- Otherwise, the reset is inferred as synchronous (i.e. the uninferred reset is only invalidated or is driven by or drives only synchronous resets).
Reset
{.firrtl}s, whether synchronous or asynchronous, can be cast to other
types. Casting between reset types is also legal:
input a : UInt<1>
output y : AsyncReset
output z : Reset
wire r : Reset
connect r, a
connect y, asAsyncReset(r)
connect z, asUInt(y)
See [@sec:primitive-operations] for more details on casting.
The analog type specifies that a wire or port can be attached to multiple
drivers. Analog
{.firrtl} cannot be used as part of the type of a node or
register, nor can it be used as part of the datatype of a memory. In this
respect, it is similar to how inout
{.firrtl} ports are used in Verilog, and
FIRRTL analog signals are often used to interface with external Verilog or VHDL
IP.
In contrast with all other ground types, analog signals cannot appear on either
side of a connection statement. Instead, analog signals are attached to each
other with the commutative attach
{.firrtl} statement. An analog signal may
appear in any number of attach statements, and a legal circuit may also contain
analog signals that are never attached. The only primitive operations that may
be applied to analog signals are casts to other signal types.
When an analog signal appears as a field of an aggregate type, the aggregate cannot appear in a standard connection statement.
As with integer types, an analog type can represent a multi-bit signal. When analog signals are not given a concrete width, their widths are inferred according to a highly restrictive width inference rule, which requires that the widths of all arguments to a given attach operation be identical.
Analog<1> ; 1-bit analog type
Analog<32> ; 32-bit analog type
Analog ; analog type with inferred width
FIRRTL supports three aggregate types: vectors, bundles, and enumeration. Aggregate types are composed of ground types or other aggregate types.
A vector type is used to express an ordered sequence of elements of a given type. The length of the sequence must be non-negative and known.
The following example specifies a ten element vector of 16-bit unsigned integers.
UInt<16>[10]
The next example specifies a ten element vector of unsigned integers of omitted but identical bit widths.
UInt[10]
Note that any type, including other aggregate types, may be used as the element type of the vector. The following example specifies a twenty element vector, each of which is a ten element vector of 16-bit unsigned integers.
UInt<16>[10][20]
A bundle type is used to express a collection of nested and named types. All fields in a bundle type must have a given name, and type. All names must be legal identifiers.
The following is an example of a possible type for representing a complex
number. It has two fields, real
{.firrtl}, and imag
{.firrtl}, both 10-bit
signed integers.
{real: SInt<10>, imag: SInt<10>}
Additionally, a field may optionally be declared with a flipped orientation.
{word: UInt<32>, valid: UInt<1>, flip ready: UInt<1>}
In a connection between circuit components with bundle types, the data carried by the flipped fields flow in the opposite direction as the data carried by the non-flipped fields.
As an example, consider a module output port declared with the following type:
output a: {word: UInt<32>, valid: UInt<1>, flip ready: UInt<1>}
In a connection to the a
{.firrtl} port, the data carried by the
word
{.firrtl} and valid
{.firrtl} sub-fields will flow out of the module,
while data carried by the ready
{.firrtl} sub-field will flow into the
module. More details about how the bundle field orientation affects connections
are explained in [@sec:connects].
As in the case of vector types, a bundle field may be declared with any type, including other aggregate types.
{real: {word: UInt<32>, valid: UInt<1>, flip ready: UInt<1>},
imag: {word: UInt<32>, valid: UInt<1>, flip ready: UInt<1>}}
When calculating the final direction of data flow, the orientation of a field is applied recursively to all nested types in the field. As an example, consider the following module port declared with a bundle type containing a nested bundle type.
output myport: {a: UInt, flip b: {c: UInt, flip d: UInt}}
In a connection to myport
{.firrtl}, the a
{.firrtl} sub-field flows out of
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.
Enumerations are structural disjoint union types. An enumeration has a number of variants, each with a type. The different variants are specified with tags. The variant types of an enumeration must all be passive and cannot contain analog or probe types.
In the following example, the first variant has the tag a
{.firrtl} with type
UInt<8>
{.firrtl}, and the second variant has the tag b
{.firrtl} with type
UInt<16>
{.firrtl}.
{|a: UInt<8>, b: UInt<16>|}
A variant may optionally omit the type, in which case it is implicitly defined
to be UInt<0>
{.firrtl}. In the following example, all variants have the type
UInt<0>
{.firrtl}.
{|a, b, c|}
References can be exported from a module for indirect access elsewhere, and are captured using values of reference type.
For use in cross-module references (hierarchical references in Verilog), a reference to a probe of the circuit component is used. See [@sec:probes] for details.
Using probe-type ports, modules may expose internals for reading and forcing without routing wires out of the design.
This is often useful for testing and verification, where probe types allow reads of the entities to be explicitly exported without hard-coding their place in the design. Instead, by using probe-type references, a testbench module may express accesses to the internals which will resolve to the appropriate target language construct by the compiler (e.g., hierarchical reference).
Reference ports are not expected to be synthesizable or representable in the target language and are omitted in the compiled design; they only exist at the FIRRTL level.
Reference-type ports are statically routed through the design using the
define
{.firrtl} statement.
There are two reference types, Probe
{.firrtl} and RWProbe
{.firrtl},
described below. These are used for indirect access to probes of the data
underlying circuit constructs they originate from, captured using
probe
{.firrtl} expressions (see [@sec:probes]).
Probe
{.firrtl} types are read-only, and RWProbe
{.firrtl} may be used with
force
{.firrtl} and related statements. Prefer the former as much as
possible, as read-only probes impose fewer limitations and are more amenable to
optimization.
Probe references must always be able to be statically traced to their target, or to an external module's output reference. This means no conditional connections via sub-accesses, multiplexers, or other means.
Reference types compose with optional groups (see [@sec:optional-groups]. A reference type may be associated with an optional group. When associated with an optional group, the reference type may only be driven from that optional group.
Probe types are reference types used to access circuit elements' data remotely.
There are two probe types: Probe
{.firrtl} and RWProbe
{.firrtl}.
RWProbe
{.firrtl} is a Probe
{.firrtl} type, but not the other way around.
Probe types are parametric over the type of data that they refer to, which is always passive (as defined in [@sec:passive-types]) even when the probed target is not (see [@sec:probes-and-passive-types]). Probe types cannot contain reference types.
Conceptually probe types are single-direction views of the probed data-flow point. They are references to the data accessed with the probe expression generating the reference.
Examples:
Probe<UInt> ; readable reference to unsigned integer with inferred width
RWProbe<{x: {y: UInt}}> ; readable and forceable reference to bundle
Probe<UInt, A.B> ; readable reference associated with group A.B
For details of how to read and write through probe types, see [@sec:reading-probe-references;@sec:force-and-release].
All ports of probe type must be initialized with exactly one define
{.firrtl}
statement.
Probe types are only allowed as part of module ports and may not appear anywhere else.
Sub-accesses are not allowed with types where the result is or has probe types within. This is because sub-accesses are essentially conditional connections (see [@sec:sub-accesses] for details), which are not allowed with probe types. The following example demonstrates some legal and illegal expressions:
module NoSubAccessesWithProbes :
input x : {a : Probe<UInt[2]>, b : UInt}[3]
input i : UInt
input c : const UInt
output p : Probe<UInt>
; Illegal: x[i], x[c]
; Illegal: x[0].a[i], x[0].a[c]
; Legal:
define p = x[0].a[1]
Probe types may be specified as part of an external module (see
[@sec:externally-defined-modules]), with the resolved referent for each
specified using ref
{.firrtl} statements.
Probe types may target const
{.firrtl} signals, but cannot use
rwprobe
{.firrtl} with a constant signal to produce a
RWProbe<const T>
{.firrtl}, as constant values should never be mutated at
runtime.
Probe types do participate in global width and reset inference, but only in the
direction of the reference itself (no inference in the other direction, even
with force statements). Both inner types of the references used in a
define
{.firrtl} statement must be identical or the same type with the
destination uninferred (this is checked recursively). Additionally, any
contained reset type is similarly only inferred in the direction of the
reference, even if it eventually reaches a known reset type.
In the following example, the FIRRTL compiler will produce an error constrasted
with inferring the input port as AsyncReset
{.firrtl} if a direct connection
was used:
circuit ResetInferBad :
module ResetInferBad :
input in : Reset
output out : AsyncReset
connect out, read(probe(in))
The following circuit has all resets inferred to AsyncReset
{.firrtl},
however:
circuit ResetInferGood :
module ResetInferGood :
input in : Reset
output out : Reset
output out2 : AsyncReset
connect out, read(probe(in))
connect out2, in
Probe references are generally forwarded up the design hierarchy, being used to reach down into design internals from a higher point. As a result probe-type references are most often output ports, but may also be used on input ports internally, as described in this section.
Input probe references are allowed on internal modules, but they should be used
with care because they make it possible to express invalid or multiple
reference paths. When probe references are used to access the underlying data
(e.g., with a read
{.firrtl} or force
{.firrtl}), they must target a
statically known element at or below the point of that use, in all contexts.
Support for other scenarios are allowed as determined by the implementation.
Input probe references are not allowed on public-facing modules: e.g., the top module and external modules.
Examples of input probe references follow.
module UTurn:
input in : Probe<UInt>
output out : Probe<UInt>
define out = in
module RefBouncing:
input x: UInt
output y: UInt
inst u1 of UTurn
inst u2 of UTurn
node n = x
define u1.in = probe(n)
define u2.in = u1.out
connect y, read(u2.out) ; = x
In the above example, the probe of node n
{.firrtl} is routed through two
modules before its resolution.
When using a probe reference, the target must reside at or below the point of use in the design hierarchy. Input references make it possible to create designs where this is not the case, and such upwards references are not supported:
module Foo:
input in : Probe<UInt>
output out : UInt
connect out, read(in)
Even when the target resolves at or below, the path must be the same in all contexts so a single description of the module may be generated.
The following example demonstrates such an invalid use of probe references:
circuit Top:
module Top:
input in : UInt<4>
output out : UInt
inst ud1 of UpDown
connect ud1.in, in
define ud1.in_ref = ud1.r1
inst ud2 of UpDown
connect ud2.in, in
define ud2.in_ref = ud2.r2
connect out, add(ud1.out, ud2.out)
module UpDown:
input in : UInt<4>
input in_ref : Probe<UInt<4>>
output r1 : Probe<UInt<4>>
output r2 : Probe<UInt<4>>
output out : UInt
; In ud1, this is UpDown.n1, in ud2 this is UpDown.n2 .
; However, this is not supported as it cannot be both at once.
connect out, read(in_ref)
node n1 = and(in, UInt<4>(1))
node n2 = and(in, UInt<4>(3))
define r1 = probe(n1)
define r2 = probe(n2)
A primary motivation for input probe references is that in some situations they make it easier to generate the FIRRTL code. While output references necessarily capture this design equivalently, this can be harder to generate and so is useful to support.
The following demonstrates an example of this, where it's convenient to use the
same bundle type as both output to one module and input to another, with
references populated by both modules targeting signals of interest at each end.
For this to be the same bundle type -- input on one and output on another --
the Probe
references for each end should be output-oriented and accordingly
are input-oriented at the other end. It would be inconvenient to generate this
design so that each has output probe references only.
The Connect
module instantiates a Producer
and Consumer
module, connects
them using a bundle with references in both orientations, and forwards those
references for inspection up the hierarchy. The probe targets are not
significant, here they are the same data being sent between the two, as stored
in each module.
module Consumer:
input in : {a: UInt, pref: Probe<UInt>, flip cref: Probe<UInt>}
; ...
node n = in.a
define in.cref = probe(n)
module Producer:
output out : {a: UInt, pref: Probe<UInt>, flip cref: Probe<UInt>}
wire x : UInt
define out.pref = probe(x)
; ...
connect out.a, x
module Connect:
output out : {pref: Probe<UInt>, cref: Probe<UInt>}
inst a of Consumer
inst b of Producer
; Producer => Consumer
connect a.in.a, b.out.a
define a.in.pref = b.out.pref
define b.out.cref = a.in.cref
; Send references out
define out.pref = b.out.pref
define out.cref = a.in.cref
module Top:
inst c of Connect
node producer_debug = read(c.out.pref); ; Producer-side signal
node consumer_debug = read(c.out.cref); ; Consumer-side signal
A type alias is a mechanism to assign names to existing FIRRTL types. Type aliases enables their reuse across multiple declarations.
type WordType = UInt<32>
type ValidType = UInt<1>
type Data = {w: WordType, valid: ValidType, flip ready: UInt<1>}
type AnotherWordType = UInt<32>
module TypeAliasMod:
input in: Data
output out: Data
wire w: AnotherWordType
connect w, in.w
...
The type
declaration is globally defined and all named types exist in the same
namespace and thus must all have a unique name. Type aliases do not share the same
namespace as modules; hence it is allowed for type aliases to conflict with module
names. Note that when we compare two types, the equivalence is determined solely by
their structures. For instance types of w
{.firrtl} and in.w
{.firrtl} are
equivalent in the example above even though they are different type alias.
FIRRTL property types represent information about the circuit that is not hardware. This is useful to capture domain-specific knowledge and design intent alongside the hardware description within the same FIRRTL.
Property types cannot affect hardware functionality or the hardware ABI. They cannot be used in any hardware types, including aggregates and references. They only exist to augment the hardware description with extra information.
Handling of property types is completely implementation-defined. A valid FIRRTL compiler implementation may do anything with property types as long as the existence of property types does not affect hardware functionality or the hardware ABI. For example, it is valid to drop property types from the IR completely.
Property types are legal in the following constructs:
- Port declarations on modules and external modules
Integer property types represent arbitrary precision signed integer values.
module Example:
input intProp : Integer ; an input port of Integer property 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 constant integer expressions or from constant-typed input ports of the top-level module.
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
{.firrtl} block, the
when
{.firrtl}'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.
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
{.firrtl} will result in a port in the Verilog, if under
the same conditions an input port of type UInt
{.firrtl} 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.
It is inappropriate for some circuit components to be declared with a type that allows for data to flow in both directions. For example, all sub-elements in a memory should flow in the same direction. These components are restricted to only have a passive type.
Intuitively, a passive type is a type where all data flows in the same direction, and is defined to be a type that recursively contains no fields with flipped orientations. Thus all ground types are passive types. Vector types are passive if their element type is passive. And bundle types are passive if no fields are flipped and if all field types are passive.
All property types are passive.
The type equivalence relation is used to determine whether a connection between two components is legal. See [@sec:connects] for further details about connect statements.
An unsigned integer type is always equivalent to another unsigned integer type regardless of bit width, and is not equivalent to any other type. Similarly, a signed integer type is always equivalent to another signed integer type regardless of bit width, and is not equivalent to any other type.
Clock types are equivalent to clock types, and are not equivalent to any other type.
An uninferred Reset
{.firrtl} can be connected to another Reset
{.firrtl},
UInt
{.firrtl} of unknown width, UInt<1>
{.firrtl}, or AsyncReset
{.firrtl}.
It cannot be connected to both a UInt
{.firrtl} and an AsyncReset
{.firrtl}.
The AsyncReset
{.firrtl} type can be connected to another
AsyncReset
{.firrtl} or to a Reset
{.firrtl}.
Two enumeration types are equivalent if both have the same number of variants, and both the enumerations' i'th variants have matching names and equivalent types.
Two vector types are equivalent if they have the same length, and if their element types are equivalent.
Two bundle types are equivalent if they have the same number of fields, and both
the bundles' i'th fields have matching names and orientations, as well as
equivalent types. Consequently, {a:UInt, b:UInt}
{.firrtl} is not equivalent to
{b:UInt, a:UInt}
{.firrtl}, and {a: {flip b:UInt}}
{.firrtl} is not equivalent
to {flip a: {b: UInt}}
{.firrtl}.
Two property types are equivalent if they are the same concrete property type.
Statements are used to describe the components within a module and how they interact.
The connect statement is used to specify a physically wired connection between two circuit components.
The following example demonstrates connecting a module's input port to its
output port, where port myinput
{.firrtl} is connected to port
myoutput
{.firrtl}.
module MyModule :
input myinput: UInt
output myoutput: UInt
connect myoutput, myinput
In order for a connection to be legal the following conditions must hold:
-
The types of the left-hand and right-hand side expressions must be equivalent (see [@sec:type-equivalence] for details).
-
The flow of the left-hand side expression must be sink or duplex (see [@sec:flows] for an explanation of flow).
-
Either the flow of the right-hand side expression is source or duplex, or the right-hand side expression has a passive type.
-
The left-hand side and right-hand side types are not property types.
Connect statements from a narrower ground type component to a wider ground type component will have its value automatically sign-extended or zero-extended to the larger bit width. The behavior of connect statements between two circuit components with aggregate types is defined by the connection algorithm in [@sec:the-connection-algorithm].
Connect statements between ground types cannot be expanded further.
Connect statements between two vector typed components recursively connects each sub-element in the right-hand side expression to the corresponding sub-element in the left-hand side expression.
Connect statements between two bundle typed components connects the i'th field of the right-hand side expression and the i'th field of the left-hand side expression. If the i'th field is not flipped, then the right-hand side field is connected to the left-hand side field. Conversely, if the i'th field is flipped, then the left-hand side field is connected to the right-hand side field.
Ordering of connects is significant. Later connects take precedence over
earlier ones. In the following example port b
{.firrtl} will be connected to
myport1
{.firrtl}, and port a
{.firrtl} will be connected to
myport2
{.firrtl}:
module MyModule :
input a: UInt
input b: UInt
output myport1: UInt
output myport2: UInt
connect myport1, a
connect myport1, b
connect myport2, a
Conditional statements are affected by last connect semantics. For details see [@sec:conditional-last-connect-semantics].
When a connection to a component with an aggregate type is followed by a
connection to a sub-element of that same component, only the connection to the
sub-element is overwritten. Connections to the other sub-elements remain
unaffected. In the following example the c
{.firrtl} sub-element of port
portx
{.firrtl} will be connected to the c
{.firrtl} sub-element of
myport
{.firrtl}, and port porty
{.firrtl} will be connected to the
b
{.firrtl} sub-element of myport
{.firrtl}.
module MyModule :
input portx: {b: UInt, c: UInt}
input porty: UInt
output myport: {b: UInt, c: UInt}
connect myport, portx
connect myport.b, porty
The above circuit can be rewritten as:
module MyModule :
input portx: {b: UInt, c: UInt}
input porty: UInt
output myport: {b: UInt, c: UInt}
connect myport.b, porty
connect myport.c, portx.c
When a connection to a sub-element of an aggregate component is followed by a connection to the entire circuit component, the later connection overwrites the earlier sub-element connection.
module MyModule :
input portx: {b: UInt, c: UInt}
input porty: UInt
output myport: {b: UInt, c: UInt}
connect myport.b, porty
connect myport, portx
The above circuit can be rewritten as:
module MyModule :
input portx: {b: UInt, c: UInt}
input porty: UInt
output myport: {b: UInt, c: UInt}
connect myport, portx
See [@sec:sub-fields] for more details about sub-field expressions.
The empty statement does nothing and is used simply as a placeholder where a
statement is expected. It is specified using the skip
{.firrtl} keyword.
The following example:
connect a, b
skip
connect c, d
can be equivalently expressed as:
connect a, b
connect c, d
The empty statement is most often used as the else
{.firrtl} branch in a
conditional statement, or as a convenient placeholder for removed components
during transformational passes. See [@sec:conditionals] for details on the
conditional statement.
A wire is a named combinational circuit component that can be connected to and from using connect statements.
The following example demonstrates instantiating a wire with the given name
mywire
{.firrtl} and type UInt
{.firrtl}.
wire mywire: UInt
A register is a named stateful circuit component. Reads from a register return 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]) and may not be
const
{.firrtl}.
Registers may be declared without a reset using the reg
{.firrtl} syntax and
with a reset using the regreset
{.firrtl} syntax.
The following example demonstrates instantiating a register with the given name
myreg
{.firrtl}, type SInt
{.firrtl}, and is driven by the clock signal
myclock
{.firrtl}.
wire myclock: Clock
reg myreg: SInt, myclock
; ...
A register with a reset is declared using regreset
{.firrtl}. A
regreset
{.firrtl} adds two expressions after the type and clock arguments: a
reset signal and a reset 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). 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.
wire myclock: Clock
wire myreset: UInt<1>
wire myinit: SInt
regreset myreg: SInt, myclock, myreset, myinit
; ...
A register is initialized with an indeterminate value (see [@sec:indeterminate-values]).
An invalidate statement is used to indicate that a circuit component contains indeterminate values (see [@sec:indeterminate-values]). It is specified as follows:
wire w: UInt
invalidate w
Invalidate statements can be applied to any circuit component of any type. However, if the circuit component cannot be connected to, then the statement has no effect on the component. This allows the invalidate statement to be applied to any component, to explicitly ignore initialization coverage errors.
The following example demonstrates the effect of invalidating a variety of circuit components with aggregate types. See [@sec:the-invalidate-algorithm] for details on the algorithm for determining what is invalidated.
module MyModule :
input in: {flip a: UInt, b: UInt}
output out: {flip a: UInt, b: UInt}
wire w: {flip a: UInt, b: UInt}
invalidate in
invalidate out
invalidate w
is equivalent to the following:
module MyModule :
input in: {flip a: UInt, b: UInt}
output out: {flip a: UInt, b: UInt}
wire w: {flip a: UInt, b: UInt}
invalidate in.a
invalidate out.b
invalidate w.a
invalidate w.b
The handing of invalidated components is covered in [@sec:indeterminate-values].
Invalidating a component with a ground type indicates that the component's value is undetermined if the component has sink or duplex flow (see [@sec:flows]). Otherwise, the component is unaffected.
Invalidating a component with a vector type recursively invalidates each sub-element in the vector.
Invalidating a component with a bundle type recursively invalidates each sub-element in the bundle.
Components of reference and analog type are ignored, as are any reference or analog types within the component (as they cannot be connected to).
The attach
{.firrtl} statement is used to attach two or more analog signals,
defining that their values be the same in a commutative fashion that lacks the
directionality of a regular connection. It can only be applied to signals with
analog type, and each analog signal may be attached zero or more times.
wire x: Analog<2>
wire y: Analog<2>
wire z: Analog<2>
attach(x, y) ; binary attach
attach(z, y, x) ; attach all three signals
A node is simply a named intermediate value in a circuit. The node must be initialized to a value with a passive type and cannot be connected to. Nodes are often used to split a complicated compound expression into named sub-expressions.
The following example demonstrates instantiating a node with the given name
mynode
{.firrtl} initialized with the output of a multiplexer (see
[@sec:multiplexers]).
wire pred: UInt<1>
wire a: SInt
wire b: SInt
node mynode = mux(pred, a, b)
Several statements provide branching in the data-flow and conditional control of verification constructs.
Connections within a when statement that connect to previously declared components hold only when the given condition is high. The condition must have a 1-bit unsigned integer type.
In the following example, the wire x
{.firrtl} is connected to the input
a
{.firrtl} only when the en
{.firrtl} signal is high. Otherwise, the wire
x
{.firrtl} is connected to the input b
{.firrtl}.
module MyModule :
input a: UInt
input b: UInt
input en: UInt<1>
wire x: UInt
when en :
connect x, a
else :
connect x, b
The else
{.firrtl} branch of a conditional statement may be omitted, in which
case a default else
{.firrtl} branch is supplied consisting of the empty
statement.
Thus the following example:
module MyModule :
input a: UInt
input b: UInt
input en: UInt<1>
wire x: UInt
when en :
connect x, a
can be equivalently expressed as:
module MyModule :
input a: UInt
input b: UInt
input en: UInt<1>
wire x: UInt
when en :
connect x, a
else :
skip
To aid readability of long chains of conditional statements, the colon following
the else
{.firrtl} keyword may be omitted if the else
{.firrtl} branch
consists of a single conditional statement.
Thus the following example:
module MyModule :
input a: UInt
input b: UInt
input c: UInt
input d: UInt
input c1: UInt<1>
input c2: UInt<1>
input c3: UInt<1>
wire x: UInt
when c1 :
connect x, a
else :
when c2 :
connect x, b
else :
when c3 :
connect x, c
else :
connect x, d
can be equivalently written as:
module MyModule :
input a: UInt
input b: UInt
input c: UInt
input d: UInt
input c1: UInt<1>
input c2: UInt<1>
input c3: UInt<1>
wire x: UInt
when c1 :
connect x, a
else when c2 :
connect x, b
else when c3 :
connect x, c
else :
connect x, d
To additionally aid readability, a conditional statement where the contents of
the when
{.firrtl} branch consist of a single line may be combined into a
single line. If an else
{.firrtl} branch exists, then the else
{.firrtl}
keyword must be included on the same line.
The following statement:
when c :
connect a, b
else :
connect e, f
can have the when
{.firrtl} keyword, the when
{.firrtl} branch, and the
else
{.firrtl} keyword expressed as a single line:
when c : connect a, b else :
connect e, f
The else
{.firrtl} branch may also be added to the single line:
when c : connect a, b else : connect e, f
Match statements are used to discriminate the active variant of an enumeration typed expression. A match statement must exhaustively test every variant of an enumeration. An optional binder may be specified to extract the data of the variant.
match x:
some(v):
connect a, v
none:
connect e, f
If a component is declared within a conditional statement, connections to the
component are unaffected by the condition. In the following example, register
myreg1
{.firrtl} is always connected to a
{.firrtl}, and register
myreg2
{.firrtl} is always connected to b
{.firrtl}.
module MyModule :
input a: UInt
input b: UInt
input en: UInt<1>
input clk : Clock
when en :
reg myreg1 : UInt, clk
connect myreg1, a
else :
reg myreg2 : UInt, clk
connect myreg2, b
Intuitively, a line can be drawn between a connection to a component and that component's declaration. All conditional statements that are crossed by the line apply to that connection.
Because of the conditional statement, it is possible to syntactically express circuits containing wires that have not been connected to under all conditions.
In the following example, the wire a
{.firrtl} is connected to the wire
w
{.firrtl} when en
{.firrtl} is high, but it is not specified what is
connected to w
{.firrtl} when en
{.firrtl} is low.
module MyModule :
input en: UInt<1>
input a: UInt
wire w: UInt
when en :
connect w, a
This is an illegal FIRRTL circuit and an error will be thrown during compilation. All wires, memory ports, instance ports, and module ports that can be connected to must be connected to under all conditions. Registers do not need to be connected to under all conditions, as it will keep its previous value if unconnected.
The conditional statement creates a new scope within each of its
when
{.firrtl} and else
{.firrtl} branches. It is an error to refer to any
component declared within a branch after the branch has ended. As mention in
[@sec:namespaces], circuit component declarations in a module must be unique
within the module's flat namespace; this means that shadowing a component in an
enclosing scope with a component of the same name inside a conditional statement
is not allowed.
In the case where a connection to a circuit component is followed by a conditional statement containing a connection to the same component, the connection is overwritten only when the condition holds. Intuitively, a multiplexer is generated such that when the condition is low, the multiplexer returns the old value, and otherwise returns the new value. For details about the multiplexer, see [@sec:multiplexers].
The following example:
wire a: UInt
wire b: UInt
wire c: UInt<1>
wire w: UInt
connect w, a
when c :
connect w, b
can be rewritten equivalently using a multiplexer as follows:
wire a: UInt
wire b: UInt
wire c: UInt<1>
wire w: UInt
connect w, mux(c, b, a)
Because invalid statements assign indeterminate values to components, a FIRRTL
Compiler is free to choose any specific value for an indeterminate value when
resolving last connect semantics. E.g., in the following circuit w
{.firrtl}
has an indeterminate value when c
{.firrtl} is false.
wire a: UInt
wire c: UInt<1>
wire w: UInt
invalidate w
when c :
connect w, a
A FIRRTL compiler is free to optimize this to the following circuit by assuming
that w
{.firrtl} takes on the value of a
{.firrtl} when c
{.firrtl} is false.
wire a: UInt
wire c: UInt<1>
wire w: UInt
connect w, a
See [@sec:indeterminate-values] for more information on indeterminate values.
The behavior of conditional connections to circuit components with aggregate types can be modeled by first expanding each connect into individual connect statements on its ground elements (see [@sec:the-connection-algorithm] for the connection algorithm) and then applying the conditional last connect semantics.
For example, the following snippet:
wire x: {a: UInt, b: UInt}
wire y: {a: UInt, b: UInt}
wire c: UInt<1>
wire w: {a: UInt, b: UInt}
connect w, x
when c :
connect w, y
can be rewritten equivalently as follows:
wire x: {a:UInt, b:UInt}
wire y: {a:UInt, b:UInt}
wire c: UInt<1>
wire w: {a:UInt, b:UInt}
connect w.a, mux(c, y.a, x.a)
connect w.b, mux(c, y.b, x.b)
Similar to the behavior of aggregate types under last connect semantics (see [@sec:last-connect-semantics]), the conditional connects to a sub-element of an aggregate component only generates a multiplexer for the sub-element that is overwritten.
For example, the following snippet:
wire x: {a: UInt, b: UInt}
wire y: UInt
wire c: UInt<1>
wire w: {a: UInt, b: UInt}
connect w, x
when c :
connect w.a, y
can be rewritten equivalently as follows:
wire x: {a: UInt, b: UInt}
wire y: UInt
wire c: UInt<1>
wire w: {a: UInt, b: UInt}
connect w.a, mux(c, y, x.a)
connect w.b, x.b
A memory is an abstract representation of a hardware memory. It is characterized by the following parameters.
-
A passive type representing the type of each element in the memory.
-
A positive integer literal representing the number of elements in the memory.
-
A variable number of named ports, each being a read port, a write port, or readwrite port.
-
A non-negative integer literal indicating the read latency, which is the number of cycles after setting the port's read address before the corresponding element's value can be read from the port's data field.
-
A positive integer literal indicating the write latency, which is the number of cycles after setting the port's write address and data before the corresponding element within the memory holds the new value.
-
A read-under-write flag indicating the behavior when a memory location is written to while a read to that location is in progress.
Integer literals for the number of elements and the read/write latencies may not be radix-encoded integer literals.
The following example demonstrates instantiating a memory containing 256 complex
numbers, each with 16-bit signed integer fields for its real and imaginary
components. It has two read ports, r1
{.firrtl} and r2
{.firrtl}, and one
write port, w
{.firrtl}. It is combinationally read (read latency is zero
cycles) and has a write latency of one cycle. Finally, its read-under-write
behavior is undefined.
mem mymem :
data-type => {real:SInt<16>, imag:SInt<16>}
depth => 256
reader => r1
reader => r2
writer => w
read-latency => 0
write-latency => 1
read-under-write => undefined
In the example above, the type of mymem
{.firrtl} is:
{flip r1: {addr: UInt<8>,
en: UInt<1>,
clk: Clock,
flip data: {real: SInt<16>, imag: SInt<16>}},
flip r2: {addr: UInt<8>,
en: UInt<1>,
clk: Clock,
flip data: {real: SInt<16>, imag: SInt<16>}},
flip w: {addr: UInt<8>,
en: UInt<1>,
clk: Clock,
data: {real: SInt<16>, imag: SInt<16>},
mask: {real: UInt<1>, imag: UInt<1>}}}
The following sections describe how a memory's field types are calculated and the behavior of each type of memory port.
If a memory is declared with element type T
{.firrtl}, has a size less than or
equal to
{addr: UInt<N>, en: UInt<1>, clk: Clock, flip data: T}
If the en
{.firrtl} field is high, then the element value associated with the
address in the addr
{.firrtl} field can be retrieved by reading from the
data
{.firrtl} field after the appropriate read latency. If the en
{.firrtl}
field is low, then the value in the data
{.firrtl} field, after the appropriate
read latency, is undefined. The port is driven by the clock signal in the
clk
{.firrtl} field.
If a memory is declared with element type T
{.firrtl}, has a size less than or
equal to
{addr: UInt<N>, en: UInt<1>, clk: Clock, data: T, mask: M}
where M
{.firrtl} is the mask type calculated from the element type
T
{.firrtl}. Intuitively, the mask type mirrors the aggregate structure of the
element type except with all ground types replaced with a single bit unsigned
integer type. The non-masked portion of the data value is defined as the set
of data value leaf sub-elements where the corresponding mask leaf sub-element is
high.
If the en
{.firrtl} field is high, then the non-masked portion of the
data
{.firrtl} field value is written, after the appropriate write latency, to
the location indicated by the addr
{.firrtl} field. If the en
{.firrtl} field
is low, then no value is written after the appropriate write latency. The port
is driven by the clock signal in the clk
{.firrtl} field.
Finally, the readwrite ports have type:
{addr: UInt<N>, en: UInt<1>, clk: Clock, flip rdata: T, wmode: UInt<1>,
wdata: T, wmask: M}
A readwrite port is a single port that, on a given cycle, can be used either as
a read or a write port. If the readwrite port is not in write mode (the
wmode
{.firrtl} field is low), then the rdata
{.firrtl}, addr
{.firrtl},
en
{.firrtl}, and clk
{.firrtl} fields constitute its read port fields, and
should be used accordingly. If the readwrite port is in write mode (the
wmode
{.firrtl} field is high), then the wdata
{.firrtl}, wmask
{.firrtl},
addr
{.firrtl}, en
{.firrtl}, and clk
{.firrtl} fields constitute its write
port fields, and should be used accordingly.
The read-under-write flag indicates the value held on a read port's
data
{.firrtl} field if its memory location is written to while it is reading.
The flag may take on three settings: old
{.firrtl}, new
{.firrtl}, and
undefined
{.firrtl}.
If the read-under-write flag is set to old
{.firrtl}, then a read port always
returns the value existing in the memory on the same cycle that the read was
requested.
Assuming that a combinational read always returns the value stored in the memory (no write forwarding), then intuitively, this is modeled as a combinational read from the memory that is then delayed by the appropriate read latency.
If the read-under-write flag is set to new
{.firrtl}, then a read port always
returns the value existing in the memory on the same cycle that the read was
made available. Intuitively, this is modeled as a combinational read from the
memory after delaying the read address by the appropriate read latency.
If the read-under-write flag is set to undefined
{.firrtl}, then the value held
by the read port after the appropriate read latency is undefined.
For the purpose of defining such collisions, an "active write port" is a write
port or a readwrite port that is used to initiate a write operation on a given
clock edge, where en
{.firrtl} is set and, for a readwriter, wmode
{.firrtl}
is set. An "active read port" is a read port or a readwrite port that is used to
initiate a read operation on a given clock edge, where en
{.firrtl} is set and,
for a readwriter, wmode
{.firrtl} is not set. Each operation is defined to be
"active" for the number of cycles set by its corresponding latency, starting
from the cycle where its inputs were provided to its associated port. Note that
this excludes combinational reads, which are simply modeled as combinationally
selecting from stored values
For memories with independently clocked ports, a collision between a read operation and a write operation with independent clocks is defined to occur when the address of an active write port and the address of an active read port are the same for overlapping clock periods, or when any portion of a read operation overlaps part of a write operation with a matching addresses. In such cases, the 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.
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.
FIRRTL modules are instantiated with the instance statement. The following
example demonstrates creating an instance named myinstance
{.firrtl} of the
MyModule
{.firrtl} module within the top level module Top
{.firrtl}.
circuit Top :
module MyModule :
input a: UInt
output b: UInt
connect b, a
module Top :
inst myinstance of MyModule
The resulting instance has a bundle type. Each port of the instantiated module
is represented by a field in the bundle with the same name and type as the
port. The fields corresponding to input ports are flipped to indicate their data
flows in the opposite direction as the output ports. The myinstance
{.firrtl}
instance in the example above has type {flip a:UInt, b:UInt}
{.firrtl}.
Modules have the property that instances can always be inlined into the parent module without affecting the semantics of the circuit.
To disallow infinitely recursive hardware, modules cannot contain instances of itself, either directly, or indirectly through instances of other modules it instantiates.
The stop statement is used to halt simulations of the circuit. Backends are free to generate hardware to stop a running circuit for the purpose of debugging, but this is not required by the FIRRTL specification.
A stop statement requires a clock signal, a halt condition signal that has a single bit unsigned integer type, and an integer exit code.
For clocked statements that have side effects in the environment (stop, print, and verification statements), the order of execution of any such statements that are triggered on the same clock edge is determined by their syntactic order in the enclosing module. The order of execution of clocked, side-effect-having statements in different modules or with different clocks that trigger concurrently is undefined.
The stop statement has an optional name attribute which can be used to attach metadata to the statement. The name is part of the module level namespace. However it can never be used in a reference since it is not of any valid type.
wire clk: Clock
wire halt: UInt<1>
stop(clk, halt, 42) : optional_name
The formatted print statement is used to print a formatted string during simulations of the circuit. Backends are free to generate hardware that relays this information to a hardware test harness, but this is not required by the FIRRTL specification.
A printf
{.firrtl} statement requires a clock signal, a print condition signal,
a format string, and a variable list of argument signals. The condition signal
must be a single bit unsigned integer type, and the argument signals must each
have a ground type.
For information about execution ordering of clocked statements with observable environmental side effects, see [@sec:stops].
The printf
{.firrtl} statement has an optional name attribute which can be used
to attach metadata to the statement. The name is part of the module level
namespace. However it can never be used in a reference since it is not of any
valid type.
wire clk: Clock
wire cond: UInt<1>
wire a: UInt
wire b: UInt
printf(clk, cond, "a in hex: %x, b in decimal:%d.\n", a, b) : optional_name
On each positive clock edge, when the condition signal is high, the
printf
{.firrtl} statement prints out the format string where its argument
placeholders are substituted with the value of the corresponding argument.
Format strings support the following argument placeholders:
-
%b
: Prints the argument in binary -
%d
: Prints the argument in decimal -
%x
: Prints the argument in hexadecimal -
%%
: Prints a single%
character
Format strings support the following escape characters:
-
\n
: New line -
\t
: Tab -
\\
: Back slash -
\"
: Double quote -
\'
: Single quote
To facilitate simulation, model checking and formal methods, there are three non-synthesizable verification statements available: assert, assume and cover. Each type of verification statement requires a clock signal, a predicate signal, an enable signal and a string literal. The predicate and enable signals must have single bit unsigned integer type. Assert and assume use the string as an explanatory message. For cover statements the string indicates a suggested comment. When an assert or assume is violated the explanatory message may be issued as guidance. The explanatory message may be phrased as if prefixed by the words "Verifies that...".
Backends are free to generate the corresponding model checking constructs in the target language, but this is not required by the FIRRTL specification. Backends that do not generate such constructs should issue a warning. For example, the SystemVerilog emitter produces SystemVerilog assert, assume and cover statements, but the Verilog emitter does not and instead warns the user if any verification statements are encountered.
For information about execution ordering of clocked statements with observable environmental side effects, see [@sec:stops].
Any verification statement has an optional name attribute which can be used to attach metadata to the statement. The name is part of the module level namespace. However it can never be used in a reference since it is not of any valid type.
The assert statement verifies that the predicate is true on the rising edge of any clock cycle when the enable is true. In other words, it verifies that enable implies predicate.
wire clk: Clock
wire pred: UInt<1>
wire en: UInt<1>
connect pred, eq(X, Y)
connect en, Z_valid
assert(clk, pred, en, "X equals Y when Z is valid") : optional_name
The assume statement directs the model checker to disregard any states where the enable is true and the predicate is not true at the rising edge of the clock cycle. In other words, it reduces the states to be checked to only those where enable implies predicate is true by definition. In simulation, assume is treated as an assert.
wire clk: Clock
wire pred: UInt<1>
wire en: UInt<1>
connect pred, eq(X, Y)
connect en, Z_valid
assume(clk, pred, en, "X equals Y when Z is valid") : optional_name
The cover statement verifies that the predicate is true on the rising edge of some clock cycle when the enable is true. In other words, it directs the model checker to find some way to make both enable and predicate true at some time step. The string argument may be emitted as a comment with the cover.
wire clk: Clock
wire pred: UInt<1>
wire en: UInt<1>
connect pred, eq(X, Y)
connect en, Z_valid
cover(clk, pred, en, "X equals Y when Z is valid") : optional_name
Probe references are created with probe
{.firrtl} expressions, routed through
the design using the define
{.firrtl} statement, read using the
read
{.firrtl} expression (see [@sec:reading-probe-references]), and forced
and released with force
{.firrtl} and release
{.firrtl} statements.
These statements are detailed below.
Define statements are used to route references through the design, and may be used wherever is most convenient in terms of available identifiers -- their location is not significant other than scoping, and do not have last-connect semantics. Every sink-flow probe must be the target of exactly one of these statements.
The define statement takes a sink-flow static reference target and sets it to the specified reference, which must either be a compatible probe expression or static reference source.
Example:
module Refs:
input clock: Clock
output a : Probe<{x: UInt, y: UInt}> ; read-only ref. to wire 'p'
output b : RWProbe<UInt> ; force-able ref. to node 'q', inferred width.
output c : Probe<UInt<1>> ; read-only ref. to register 'r'
output d : Probe<Clock> ; ref. to input clock port
wire p : {x: UInt, flip y : UInt}
define a = probe(p) ; probe is passive
node q = UInt<1>(0)
define b = rwprobe(q)
reg r: UInt, clock
define c = probe(r)
define d = probe(clock)
The target is not required to be only an identifier, it may be a field within a bundle or other statically known sub-element of an aggregate, for example:
module Foo:
input x : UInt
output y : {x: UInt, p: Probe<UInt>}
output z : Probe<UInt>[2]
wire w : UInt
connect w, x
connect y.x, w
define y.p = probe(w)
define z[0] = probe(w)
define z[1] = probe(w)
RWProbe
{.firrtl} references to ports are not allowed on public-facing
modules.
Define statements can set a Probe
{.firrtl} to either a Probe
{.firrtl} or
RWProbe
{.firrtl}, but a RWProbe
{.firrtl} cannot be set to a
Probe
{.firrtl}. The inner types of the two references must (recursively) be
identical or identical with the destination containing uninferred versions of
the corresponding element in the source type. See
[@sec:width-and-reset-inference] for details.
While Probe
{.firrtl} inner types are passive, the type of the probed
static reference is not required to be:
module Foo :
input x : {a: UInt, flip b: UInt}
output y : {a: UInt, flip b: UInt}
output xp : Probe<{a: UInt, b: UInt}> ; passive
wire p : {a: UInt, flip b: UInt} ; p is not passive
define xp = probe(p)
connect p, x
connect y, p
Nested declarations (see [@sec:nested-declarations]) may be exported:
module RefProducer :
input a : UInt<4>
input en : UInt<1>
input clk : Clock
output thereg : Probe<UInt>
when en :
reg myreg : UInt, clk
connect myreg, a
define thereg = probe(myreg)
Define statements can be used to forward a child module's reference further up the hierarchy:
module Foo :
output p : Probe<UInt>
; ...
module Forward :
output p : Probe<UInt>
inst f of Foo
define p = f.p
Define statements may narrow a probe of an aggregate to a sub-element using static expression:
module Foo :
output p : Probe<UInt[2]>[2]
; ...
module Forward :
output p : Probe<UInt>
inst f of Foo
define p = f.p[0][1]
Define statements can also be used to forward references down the hierarchy using input reference-type ports, which are allowed but should be used carefully as they make it possible to express invalid reference paths.
See [@sec:input-probe-references] for more details, a small example is given below:
module UnusedInputRef :
input r : Probe<UInt<1>>
module ForwardDownwards :
input in : UInt<1>
inst u of UnusedInputRef
define u.r = probe(in)
To override existing drivers for a RWProbe
{.firrtl}, the force
{.firrtl}
statement is used, and released with release
{.firrtl}. Force statements are
simulation-only constructs and may not be supported by all implementations.
They are similar to the verification statements (e.g., assert
{.firrtl}) in
this regard.
These are two variants of each, enumerated below:
Name | Arguments | Argument Types |
---|---|---|
force_initial | (ref, val) | (RWProbe<T> {.firrtl}, T) |
release_initial | (ref) | (RWProbe<T> {.firrtl}) |
force | (clock, condition, ref, val) | (Clock {.firrtl}, UInt<1> {.firrtl}, RWProbe<T> {.firrtl}, T) |
release | (clock, condition, ref) | (Clock {.firrtl}, UInt<1> {.firrtl}, RWProbe<T> {.firrtl}) |
Backends optionally generate corresponding constructs in the target language, or issue an warning.
The following AddRefs
{.firrtl} module is used in the examples that follow for
each construct.
module AddRefs:
output a : RWProbe<UInt<2>>
output b : RWProbe<UInt<2>>
output c : RWProbe<UInt<2>>
output sum : UInt<3>
node x = UInt<2>(0)
node y = UInt<2>(0)
node z = UInt<2>(0)
connect sum, add(x, add(y, z))
define a = rwprobe(x)
define b = rwprobe(y)
define c = rwprobe(z)
These variants force and release continuously:
Example:
module ForceAndRelease:
output o : UInt<3>
inst r of AddRefs
connect o, r.sum
force_initial(r.a, UInt<2>(0))
force_initial(r.a, UInt<2>(1))
force_initial(r.b, UInt<2>(2))
force_initial(r.c, UInt<2>(3))
release_initial(r.c)
In this example, the output o
{.firrtl} will be 3
. Note that globally the
last force statement overrides the others until another force or release
including the target.
Sample SystemVerilog output for the force and release statements would be:
initial begin
force ForceAndRelease.AddRefs.x = 0;
force ForceAndRelease.AddRefs.x = 1;
force ForceAndRelease.AddRefs.y = 2;
force ForceAndRelease.AddRefs.z = 3;
release ForceAndRelease.AddRefs.z;
end
The force_initial
{.firrtl} and release_initial
{.firrtl} statements may
occur under when
{.firrtl} blocks which becomes a check of the condition
first. Note that this condition is only checked once and changes to it
afterwards are irrelevant, and if executed the force will
continue to be active. For more control over their behavior, the other
variants should be used. Example:
when c : force_initial(ref, x)
would become:
initial if (c) force a.b = x;
These more detailed variants allow specifying a clock and condition for when activating the force or release behavior continuously:
module ForceAndRelease:
input a: UInt<2>
input clock : Clock
input cond : UInt<1>
output o : UInt<3>
inst r of AddRefs
connect o, r.sum
force(clock, cond, r.a, a)
release(clock, not(cond), r.a)
Which at the positive edge of clock
{.firrtl} will either force or release
AddRefs.x
{.firrtl}. Note that once active, these remain active regardless of
the condition, until another force or release.
Sample SystemVerilog output:
always @(posedge clock) begin
if (cond)
force ForceAndRelease.AddRefs.x = a;
else
release ForceAndRelease.AddRefs.x;
end
Condition is checked in procedural block before the force, as shown above.
When placed under when
{.firrtl} blocks, condition is mixed in as with other
statements (e.g., assert
{.firrtl}).
Force on a non-passive bundle drives in the direction of each field's orientation.
Example:
module Top:
input x : {a: UInt<2>, flip b: UInt<2>}
output y : {a: UInt<2>, flip b: UInt<2>}
inst d of DUT
connect d.x, x
connect y, d.y
wire val : {a: UInt<2>, b: UInt<2>}
connect val.a, UInt<2>(1)
connect val.b, UInt<2>(2)
; Force takes a RWProbe and overrides the target with 'val'.
force_initial(d.xp, val)
module DUT :
input x : {a: UInt<2>, flip b: UInt<2>}
output y : {a: UInt<2>, flip b: UInt<2>}
output xp : RWProbe<{a: UInt<2>, b: UInt<2>}>
; Force drives p.a, p.b, y.a, and x.b, but not y.b and x.a
wire p : {a: UInt<2>, flip b: UInt<2>}
define xp = rwprobe(p)
connect p, x
connect y, p
Connections between property typed expressions (see [@sec:property-types]) are
not supported in the connect
{.firrtl} statement (see [@sec:connects]).
Instead, property typed expressions are assigned with the propassign
{.firrtl}
statement.
Property typed expressions have the normal rules for flow (see [@sec:flows]),
but otherwise use a stricter, simpler algorithm than connect
{.firrtl}. In
order for a property assignment to be legal, the following conditions must hold:
-
The left-hand and right-hand side expressions must be of property types.
-
The types of the left-hand and right-hand side expressions must be the same.
-
The flow of the left-hand side expression must be sink.
-
The flow of the right-hand side expression must be source.
-
The left-hand side expression may be used as the left-hand side in at most one property assignment.
-
The property assignment must not occur within a conditional scope.
Note that property types are not legal for any expressions with duplex flow.
The following example demonstrates a property assignment from a module's input property type port to its output property type port.
module Example:
input propIn : Integer
output propOut : Integer
propassign propOut, propIn
The following example demonstrates a property assignment from a property literal expression to a module's output property type port.
module Example:
output propOut : Integer
propassign propOut, Integer(42)
FIRRTL expressions are used for creating constant integers, for creating literal property type expressions, for referring to a declared circuit component, for statically and dynamically accessing a nested element within a component, for creating multiplexers, for performing primitive operations, and for reading a remote reference to a probe.
A constant unsigned or signed integer expression can be created from an integer
literal or radix-specified integer literal. An optional positive bit width may
be specified. Constant integer expressions are of constant type. All of the
following examples create a 10-bit unsigned constant integer expressions
representing the number 42
:
UInt<10>(42)
UInt<10>(0b101010)
UInt<10>(0o52)
UInt<10>(0h2A)
UInt<10>(0h2a)
Note that it is an error to supply a bit width that is not large enough to fit the given value. If the bit width is omitted, then the minimum number of bits necessary to fit the given value will be inferred. All of the following will infer a bit width of five:
UInt(42)
UInt(0b101010)
UInt(0o52)
UInt(0h2A)
UInt(0h2a)
Signed constant integer expressions may be created from a signed integer literal
or signed radix-encoded integer literal. All of the following examples create
a 10-bit signed hardware integer representing the number -42
:
SInt<10>(-42)
SInt<10>(-0b101010)
SInt<10>(-0o52)
SInt<10>(-0h2A)
SInt<10>(-0h2a)
Signed constant integer expressions may also have an inferred width. All of the
following examples create and infer a 6-bit signed integer with value -42
:
SInt(-42)
SInt(-0b101010)
SInt(-0o52)
SInt(-0h2A)
SInt(-0h2a)
A literal property type expression can be created for a given property type. The property type name is followed by an appropriate literal value for the property type, enclosed in parentheses.
A literal Integer
property type expression can be created from an integer
literal. The following examples show literal Integer
property type
expressions.
Integer(42)
Integer(-42)
An enumeration can be constructed by applying an enumeration type to a variant
tag and a data value expression. The data value expression may be omitted when
the data type is UInt<0>(0)
{.firrtl}, where it is implicitly defined to be
UInt<0>(0)
{.firrtl}.
{|a, b, c|}(a)
{|some: UInt<8>, None|}(Some, x)
A reference is simply a name that refers to a previously declared circuit component. It may refer to a module port, node, wire, register, instance, or memory.
The following example connects a reference expression in
{.firrtl}, referring
to the previously declared port in
{.firrtl}, to the reference expression
out
{.firrtl}, referring to the previously declared port out
{.firrtl}.
module MyModule :
input in: UInt
output out: UInt
connect out, in
In the rest of the document, for brevity, the names of components will be used
to refer to a reference expression to that component. Thus, the above example
will be rewritten as "the port in
{.firrtl} is connected to the port
out
{.firrtl}".
Static references start with an identifier, optionally followed by sub-fields or sub-indices selecting a particular sub-element. Sub-accesses are not allowed.
Define statements must have static references as their target, and their source must be either a static reference or a probe expression whose argument is a static reference.
The sub-field expression refers to a sub-element of an expression with a bundle
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.
module MyModule :
input in: UInt
output out: {a: UInt, b: UInt}
connect out.a, in
The following example is the same as above, but with a constant output bundle.
module MyModule :
input in: const UInt
output out: const {a: UInt, b: UInt}
connect 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.
module MyModule :
input in: const UInt
output out: {a: const UInt, b: UInt}
connect out.a, in ; out.a is of type const UInt
A sub-field referring to a field whose name is a literal identifier is shown below:
module MyModule :
input a: { `0` : { `0` : { b : UInt<1> } } }
output b: UInt<1>
connect b, a.`0`.`0`.b
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. 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.
module MyModule :
input in: UInt
output out: UInt[10]
connect out[4], in
The following example is the same as above, but with a constant vector.
module MyModule :
input in: const UInt
output out: const UInt[10]
connect out[4], in ; out[4] has a type of const UInt
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. 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.
module MyModule :
input in: UInt[3]
input n: UInt<2>
output out: UInt
connect out, in[n]
A connection from a sub-access expression can be modeled by conditionally connecting from every sub-element in the vector, where the condition holds when the dynamic index is equal to the sub-element's static index.
module MyModule :
input in: UInt[3]
input n: UInt<2>
output out: UInt
when eq(n, UInt(0)) :
connect out, in[0]
else when eq(n, UInt(1)) :
connect out, in[1]
else when eq(n, UInt(2)) :
connect out, in[2]
else :
invalidate out
The following example connects the in
{.firrtl} port to the n'th sub-element of
the out
{.firrtl} port. All other sub-elements of the out
{.firrtl} port are
connected from the corresponding sub-elements of the default
{.firrtl} port.
module MyModule :
input in: UInt
input default: UInt[3]
input n: UInt<2>
output out: UInt[3]
connect out, default
connect out[n], in
A connection to a sub-access expression can be modeled by conditionally connecting to every sub-element in the vector, where the condition holds when the dynamic index is equal to the sub-element's static index.
module MyModule :
input in: UInt
input default: UInt[3]
input n: UInt<2>
output out: UInt[3]
connect out, default
when eq(n, UInt(0)) :
connect out[0], in
else when eq(n, UInt(1)) :
connect out[1], in
else when eq(n, UInt(2)) :
connect out[2], in
The following example connects the in
{.firrtl} port to the m'th
UInt
{.firrtl} sub-element of the n'th vector-typed sub-element of the
out
{.firrtl} port. All other sub-elements of the out
{.firrtl} port are
connected from the corresponding sub-elements of the default
{.firrtl} port.
module MyModule :
input in: UInt
input default: UInt[2][2]
input n: UInt<1>
input m: UInt<1>
output out: UInt[2][2]
connect out, default
connect out[n][m], in
A connection to an expression containing multiple nested sub-access expressions can also be modeled by conditionally connecting to every sub-element in the expression. However the condition holds only when all dynamic indices are equal to all of the sub-element's static indices.
module MyModule :
input in: UInt
input default: UInt[2][2]
input n: UInt<1>
input m: UInt<1>
output out: UInt[2][2]
connect out, default
when and(eq(n, UInt(0)), eq(m, UInt(0))) :
connect out[0][0], in
else when and(eq(n, UInt(0)), eq(m, UInt(1))) :
connect out[0][1], in
else when and(eq(n, UInt(1)), eq(m, UInt(0))) :
connect out[1][0], in
else when and(eq(n, UInt(1)), eq(m, UInt(1))) :
connect out[1][1], in
A multiplexer outputs one of two input expressions depending on the value of an unsigned selection signal.
The following example connects to the c
{.firrtl} port the result of selecting
between the a
{.firrtl} and b
{.firrtl} ports. The a
{.firrtl} port is
selected when the sel
{.firrtl} signal is high, otherwise the b
{.firrtl} port
is selected.
module MyModule :
input a: UInt
input b: UInt
input sel: UInt<1>
output c: UInt
connect c, mux(sel, a, b)
A multiplexer expression is legal only if the following holds.
-
The type of the selection signal is an unsigned integer.
-
The width of the selection signal is any of:
-
Zero-bit
-
One-bit
-
Unspecified, but is illegal if infers to wider than one-bit
-
-
The types of the two input expressions are equivalent.
-
The types of the two input expressions are passive (see [@sec:passive-types]).
All fundamental operations on ground types are expressed as a FIRRTL primitive operation. In general, each operation takes some number of argument expressions, along with some number of integer literal parameters.
The general form of a primitive operation is expressed as follows:
op(arg0, arg1, ..., argn, int0, int1, ..., intm)
The following examples of primitive operations demonstrate adding two
expressions, a
{.firrtl} and b
{.firrtl}, shifting expression a
{.firrtl}
left by 3 bits, selecting the fourth bit through and including the seventh bit
in the a
{.firrtl} expression, and interpreting the expression x
{.firrtl} as
a Clock typed signal.
add(a, b)
shl(a, 3)
bits(a, 7, 4)
asClock(x)
[@sec:primitive-operations] will describe the format and semantics of each primitive operation.
Probes are read using the read
{.firrtl} operation.
Read expressions have source flow and can be connected to other components:
module Foo :
output p : Probe<UInt>
; ...
module Bar :
output x : UInt
inst f of Foo
connect x, read(f.p) ; indirectly access the probed data
Indexing statically (sub-field, sub-index) into a probed value is allowed as part of the read:
module Foo :
output p : Probe<{a: UInt, b: UInt}>
; ...
module Bar :
output x : UInt
inst f of Foo
connect x, read(f.p.b) ; indirectly access the probed data
Read operations can be used anywhere a signal of the same underlying type can be used, such as the following:
connect x, add(read(f.p).a, read(f.p).b)
The source of the probe must reside at or below the point of the
read
{.firrtl} expression in the design hierarchy. See
[@sec:invalid-input-reference] for an example of an invalid read
{.firrtl} of
an input reference.
Probe references are generated with probe expressions.
The probe expression creates a reference to a read-only or force-able view of the data underlying the specified reference expression.
The type of the produced probe reference is always passive, but the probed expression may not be. Memories and their ports are not supported as probe targets.
There are two probe varieties: probe
{.firrtl} and rwprobe
{.firrtl} for
producing probes of type Probe
{.firrtl} and RWProbe
{.firrtl}, respectively.
The following example exports a probe reference to a port:
module MyModule :
input in: UInt
output r : Probe<UInt>
define r = probe(in)
The probed expression must be a static reference.
See [@sec:probe-types;@sec:probe] for more details on probe references and their use.
The arguments of all primitive operations must be expressions with ground types, while their parameters are integer literals. Each specific operation can place additional restrictions on the number and types of their arguments and 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 we.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
add | (e1,e2) | () | (UInt,UInt) | UInt | max(w |
(SInt,SInt) | SInt | max(w |
The add operation result is the sum of e1 and e2 without loss of precision.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
sub | (e1,e2) | () | (UInt,UInt) | UInt | max(w |
(SInt,SInt) | SInt | max(w |
The subtract operation result is e2 subtracted from e1, without loss of precision.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
mul | (e1,e2) | () | (UInt,UInt) | UInt | w |
(SInt,SInt) | SInt | w |
The multiply operation result is the product of e1 and e2, without loss of precision.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
div | (num,den) | () | (UInt,UInt) | UInt | w |
(SInt,SInt) | SInt | w |
The divide operation divides num by den, truncating the fractional portion of the result. This is equivalent to rounding the result towards zero. The result of a division where den is zero is undefined.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
rem | (num,den) | () | (UInt,UInt) | UInt | min(w |
(SInt,SInt) | SInt | min(w |
The modulus operation yields the remainder from dividing num by den, keeping the sign of the numerator. Together with the divide operator, the modulus operator satisfies the relationship below:
num = add(mul(den,div(num,den)),rem(num,den))}
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
lt,leq | (UInt,UInt) | UInt | 1 | ||
gt,geq | (e1,e2) | () | (SInt,SInt) | UInt | 1 |
The comparison operations return an unsigned 1 bit signal with value one if e1 is less than (lt), less than or equal to (leq), greater than (gt), greater than or equal to (geq), equal to (eq), or not equal to (neq) e2. The operation returns a value of zero otherwise.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
pad | (e) | (n) | (UInt) | UInt | max(w |
(SInt) | SInt | max(w |
If e's bit width is smaller than n, then the pad operation zero-extends or sign-extends e up to the given width n. Otherwise, the result is simply e. n must be non-negative.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
asUInt | (e) | () | (UInt) | UInt | w |
(SInt) | UInt | w |
|||
(Clock) | UInt | 1 | |||
(Reset) | UInt | 1 | |||
(AsyncReset) | UInt | 1 |
The interpret as UInt operation reinterprets e's bits as an unsigned integer.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
asSInt | (e) | () | (UInt) | SInt | w |
(SInt) | SInt | w |
|||
(Clock) | SInt | 1 | |||
(Reset) | SInt | 1 | |||
(AsyncReset) | SInt | 1 |
The interpret as SInt operation reinterprets e's bits as a signed integer according to two's complement representation.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
asClock | (e) | () | (UInt) | Clock | n/a |
(SInt) | Clock | n/a | |||
(Clock) | Clock | n/a | |||
(Reset) | Clock | n/a | |||
(AsyncReset) | Clock | n/a |
The result of the interpret as clock operation is the Clock typed signal obtained from interpreting a single bit integer as a clock signal.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
asAsyncReset | (e) | () | (AsyncReset) | AsyncReset | n/a |
(UInt) | AsyncReset | n/a | |||
(SInt) | AsyncReset | n/a | |||
(Interval) | AsyncReset | n/a | |||
(Clock) | AsyncReset | n/a | |||
(Reset) | AsyncReset | n/a |
The result of the interpret as asynchronous reset operation is an AsyncReset typed signal.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
shl | (e) | (n) | (UInt) | UInt | w |
(SInt) | SInt | w |
The shift left operation concatenates n zero bits to the least significant end of e. n must be non-negative.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
shr | (e) | (n) | (UInt) | UInt | max(w |
(SInt) | SInt | max(w |
The shift right operation truncates the least significant n bits from e. If n is greater than or equal to the bit-width of e, the resulting value will be zero for unsigned types and the sign bit for signed types. n must be non-negative.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
dshl | (e1, e2) | () | (UInt, UInt) | UInt | w^ w |
(SInt, UInt) | SInt | w^ w |
The dynamic shift left operation shifts the bits in e1 e2 places towards the most significant bit. e2 zeroes are shifted in to the least significant bits.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
dshr | (e1, e2) | () | (UInt, UInt) | UInt | w |
(SInt, UInt) | SInt | w |
The dynamic shift right operation shifts the bits in e1 e2 places towards the least significant bit. e2 signed or zeroed bits are shifted in to the most significant bits, and the e2 least significant bits are truncated.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
cvt | (e) | () | (UInt) | SInt | w |
(SInt) | SInt | w |
The result of the arithmetic convert to signed operation is a signed integer representing the same numerical value as e.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
neg | (e) | () | (UInt) | SInt | w |
(SInt) | SInt | w |
The result of the negate operation is a signed integer representing the negated numerical value of e.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
not | (e) | () | (UInt) | UInt | w |
(SInt) | UInt | w |
The bitwise complement operation performs a logical not on each bit in e.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
and,or,xor | (e1, e2) | () | (UInt,UInt) | UInt | max(w |
(SInt,SInt) | UInt | max(w |
The above bitwise operations perform a bitwise and, or, or exclusive or on e1 and e2. The result has the same width as its widest argument, and any narrower arguments are automatically zero-extended or sign-extended to match the width of the result before performing the operation.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
andr,orr,xorr | (e) | () | (UInt) | UInt | 1 |
(SInt) | UInt | 1 |
The bitwise reduction operations correspond to a bitwise and, or, and exclusive or operation, reduced over every bit in e.
In all cases, the reduction incorporates as an inductive base case the "identity
value" associated with each operator. This is defined as the value that
preserves the value of the other argument: one for and (as
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
cat | (e1,e2) | () | (UInt, UInt) | UInt | w |
(SInt, SInt) | UInt | w |
The result of the concatenate operation is the bits of e1 concatenated to the most significant end of the bits of e2.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
bits | (e) | (hi,lo) | (UInt) | UInt | hi-lo+1 |
(SInt) | UInt | hi-lo+1 |
The result of the bit extraction operation are the bits of e between lo (inclusive) and hi (inclusive). hi must be greater than or equal to lo. Both hi and lo must be non-negative and strictly less than the bit width of e.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
head | (e) | (n) | (UInt) | UInt | n |
(SInt) | UInt | n |
The result of the head operation are the n most significant bits of e. n must be non-negative and less than or equal to the bit width of e.
Name | Arguments | Parameters | Arg Types | Result Type | Result Width |
---|---|---|---|---|---|
tail | (e) | (n) | (UInt) | UInt | w |
(SInt) | UInt | w |
The tail operation truncates the n most significant bits from e. n must be non-negative and less than or equal to the bit width of e.
An expression's flow partially determines the legality of connecting to and from the expression. Every expression is classified as either source, sink, or duplex. For details on connection rules refer back to [@sec:connects].
The flow of a reference to a declared circuit component depends on the kind of circuit component. A reference to an input port, an instance, a memory, and a node, is a source. A reference to an output port is a sink. A reference to a wire or register is duplex.
The flow of a sub-index or sub-access expression is the flow of the vector-typed expression it indexes or accesses.
The flow of a sub-field expression depends upon the orientation of the field. If the field is not flipped, its flow is the same flow as the bundle-typed expression it selects its field from. If the field is flipped, then its flow is the reverse of the flow of the bundle-typed expression it selects its field from. The reverse of source is sink, and vice-versa. The reverse of duplex remains duplex.
The flow of all other expressions are source.
For all circuit components declared with unspecified widths, the FIRRTL compiler will infer the minimum possible width that maintains the legality of all its incoming connections. If a component has no incoming connections, and the width is unspecified, then an error is thrown to indicate that the width could not be inferred.
For module input ports with unspecified widths, the inferred width is the minimum possible width that maintains the legality of all incoming connections to all instantiations of the module.
The width of a ground-typed multiplexer expression is the maximum of its two corresponding input widths. For multiplexing aggregate-typed expressions, the resulting widths of each leaf sub-element is the maximum of its corresponding two input leaf sub-element widths.
The width of each primitive operation is detailed in [@sec:primitive-operations].
The width of constant integer expressions is detailed in their respective sections.
Combinational logic is a section of logic with no registers between gates. A combinational loop exists when the output of some combinational logic is fed back into the input of that combinational logic with no intervening register. FIRRTL does not support combinational loops even if it is possible to show that the loop does not exist under actual mux select values. Combinational loops are not allowed and designs should not depend on any FIRRTL transformation to remove or break such combinational loops.
The module Foo
{.firrtl} has a combinational loop and is not legal, even
though the loop will be removed by last connect semantics.
module Foo:
input a: UInt<1>
output b: UInt<1>
connect b, b
connect b, a
The following module Foo2
{.firrtl} has a combinational loop, even if it can
be proved that n1
{.firrtl} and n2
{.firrtl} never overlap.
module Foo2 :
input n1: UInt<2>
input n2: UInt<2>
wire tmp: UInt<1>
wire vec: UInt<1>[3]
connect tmp, vec[n1]
connect vec[n2], tmp
Module Foo3
{.firrtl} is another example of an illegal combinational loop,
even if it only exists at the word level and not at the bit-level.
module Foo3
wire a : UInt<2>
wire b : UInt<1>
connect a, cat(b, c)
connect b, bits(a, 0, 0)
All modules in a circuit exist in the same module namespace, and thus must all have a unique name.
Each module has an identifier namespace containing the names of all port and circuit component declarations. Thus, all declarations within a module must have unique names.
Within a bundle type declaration, all field names must be unique.
Within a memory declaration, all port names must be unique.
Any modifications to names must preserve the uniqueness of names within a namespace.
Annotations encode arbitrary metadata and associate it with zero or more targets ([@sec:targets]) in a FIRRTL circuit.
Annotations are represented as a dictionary, with a "class" field which describes which annotation it is, and a "target" field which represents the IR object it is attached to. Annotations may have arbitrary additional fields attached. Some annotation classes extend other annotations, which effectively means that the subclass annotation implies to effect of the parent annotation.
Annotations are serializable to JSON.
Below is an example annotation used to mark some module foo
{.firrtl}:
{
"class":"myannotationpackage.FooAnnotation",
"target":"~MyCircuit|MyModule>foo"
}
A circuit is described, stored, and optimized in a folded representation. For example, there may be multiple instances of a module which will eventually become multiple physical copies of that module on the die.
Targets are a mechanism to identify specific hardware in specific instances of modules in a FIRRTL circuit. A target consists of a circuit, a root module, an optional instance hierarchy, and an optional reference. A target can only identify hardware with a name, e.g., a circuit, module, instance, register, wire, or node. References may further refer to specific fields or subindices in aggregates. A target with no instance hierarchy is local. A target with an instance hierarchy is non-local.
Targets use a shorthand syntax of the form:
target = “~” , circuit ,
[ “|” , module , { “/” (instance) “:” (module) } , [ “>” , ref ] ]
A reference is a name inside a module and one or more qualifying tokens that encode subfields (of a bundle) or subindices (of a vector):
ref = name , { ( "[" , index , "]" ) | ( "." , field ) }
Targets are specific enough to refer to any specific module in a folded, unfolded, or partially folded representation.
To show some examples of what these look like, consider the following example
circuit. This consists of four instances of module Baz
{.firrtl}, two instances
of module Bar
{.firrtl}, and one instance of module Foo
{.firrtl}:
circuit Foo:
module Foo:
inst a of Bar
inst b of Bar
module Bar:
inst c of Baz
inst d of Baz
module Baz:
skip
This circuit can be represented in a folded, completely unfolded, or in some partially folded state. Figure [@fig:foo-folded] shows the folded representation. Figure [@fig:foo-unfolded] shows the completely unfolded representation where each instance is broken out into its own module.
Using targets (or multiple targets), any specific module, instance, or combination of instances can be expressed. Some examples include:
Target Description
~Foo
refers to the whole circuit
~Foo|Foo
refers to the top module
~Foo|Bar
refers to module Bar
{.firrtl} (or both instances of module Bar
{.firrtl})
~Foo|Foo/a:Bar
refers just to one instance of module Bar
{.firrtl}
~Foo|Foo/b:Bar/c:Baz
refers to one instance of module Baz
{.firrtl}
~Foo|Bar/d:Baz
refers to two instances of module Baz
{.firrtl}
If a target does not contain an instance path, it is a local target. A local target points to all instances of a module. If a target contains an instance path, it is a non-local target. A non-local target may not point to all instances of a module. Additionally, a non-local target may have an equivalent local target representation.
Annotations may be stored in one or more JSON files using an array-of-dictionaries format. The following shows a valid annotation file containing two annotations:
[
{
"class":"hello",
"target":"~Foo|Bar"
},
{
"class":"world",
"target":"~Foo|Baz"
}
]
Annotations may also be stored in-line along with the FIRRTL circuit by wrapping
Annotation JSON in %[ ... ]
{.firrtl}. The following shows the above
annotation file stored in-line:
circuit Foo: %[[
{
"class":"hello",
"target":"~Foo|Bar"
},
{
"class":"world",
"target":"~Foo|Baz"
}
]]
module Foo :
; ...
Any legal JSON is allowed, meaning that the above JSON may be stored "minimized" all on one line.
FIRRTL is defined for 2-state boolean logic. The behavior of a generated circuit in a language, such as Verilog or VHDL, which have multi-state logic, is undefined in the presence of values which are not 2-state. A FIRRTL compiler need only respect the 2-state behavior of a circuit. This is a limitation on the scope of what behavior is observable (i.e., a relaxation of the "as-if" rule).
An indeterminate value represents a value which is unknown or unspecified. Indeterminate values are generally implementation defined, with constraints specified below. An indeterminate value may be assumed to be any specific value (not necessarily literal), at an implementation's discretion, if, in doing so, all observable behavior is as if the indeterminate value always took the specific value.
This allows transformations such as the following, where when a
{.firrtl} has
an indeterminate value, the implementation chooses to consistently give it a
value of v
{.firrtl}. An alternate, legal mapping, lets the implementation
give it the value 42
{.firrtl}. In both cases, there is no visibility of
a
{.firrtl} when it has an indeterminate value which is not mapped to the value
the implementation chooses.
module IValue :
output o : UInt<8>
input c : UInt<1>
input v : UInt<8>
wire a : UInt<8>
invalidate a
when c :
connect a, v
connect o, a
is transformed to:
module IValue :
output o : UInt<8>
input c : UInt<1>
input v : UInt<8>
connect o, v
Note that it is equally correct to produce:
module IValue :
output o : UInt<8>
input c : UInt<1>
input v : UInt<8>
wire a : UInt<8>
when c :
connect a, v
else :
connect a, UInt<3>("h42")
connect o, a
The behavior of constructs which cause indeterminate values is implementation defined with the following constraints.
- Register initialization is done in a consistent way for all registers. If code is generated to randomly initialize some registers (or 0 fill them, etc), it should be generated for all registers.
- All observations of a unique instance of an expression with indeterminate value must see the same value at runtime. Multiple readers of a value will see the same runtime value.
- Indeterminate values captured in stateful elements are not time-varying. Time-aware constructs, such as registers, which hold an indeterminate value will return the same runtime value unless something changes the value in a normal way. For example, an uninitialized register will return the same value over multiple clock cycles until it is written (or reset).
- The value produced at runtime for an expression which produced an intermediate value shall only be a function of the inputs of the expression. For example, an out-of-bounds vector access shall produce the same value for a given out-of-bounds index and vector contents.
- Two constructs with indeterminate values place no constraint on the identity of their values. For example, two uninitialized registers, which therefore contain indeterminate values, do not need to be equal under comparison.
FIRRTL's syntax is designed to be human-readable but easily algorithmically parsed.
FIRRTL allows for two types of identifiers:
- Identifiers
- Literal Identifiers
Identifiers may only have the following characters: upper and lower case
letters, digits, and _
{.firrtl}. Identifiers cannot begin with a digit.
Literal identifiers allow for using an expanded set of characters in an
identifier. Such an identifier is encoded using leading and trailing backticks,
`
{.firrtl}. A literal identifier has the same restrictions as an
identifier, but it is allowed to start with a digit. E.g., it is legal to use
`0`
{.firrtl} as a literal identifier in a Bundle field (or anywhere else
an identifier may be used).
A FIRRTL compiler is allowed to change a literal identifier to a legal identifier in the target language (e.g., Verilog) if the literal identifier is not directly representable in the target language.
Comments begin with a semicolon and extend until the end of the line. Commas are treated as whitespace, and may be used by the user for clarity if desired.
In FIRRTL, indentation is significant. Indentation must consist of spaces
only---tabs are illegal characters. The number of spaces appearing before a
FIRRTL IR statement is used to establish its indent level. Statements with the
same indent level have the same context. The indent level of the
circuit
{.firrtl} declaration must be zero.
Certain constructs (circuit
{.firrtl}, module
{.firrtl}, when
{.firrtl}, and
else
{.firrtl}) create a new sub-context. The indent used on the first line of
the sub-context establishes the indent level. The indent level of a sub-context
is one higher than the parent. All statements in the sub-context must be
indented by the same number of spaces. To end the sub-context, a line must
return to the indent level of the parent.
Since conditional statements (when
{.firrtl} and else
{.firrtl}) may be
nested, it is possible to create a hierarchy of indent levels, each with its own
number of preceding spaces that must be larger than its parent's and consistent
among all direct child statements (those that are not children of an even deeper
conditional statement).
As a concrete guide, a few consequences of these rules are summarized below:
-
The
circuit
{.firrtl} keyword must not be indented. -
All
module
{.firrtl} keywords must be indented by the same number of spaces. -
In a module, all port declarations and all statements (that are not children of other statements) must be indented by the same number of spaces.
-
The number of spaces comprising the indent level of a module is specific to each module.
-
The statements comprising a conditional statement's branch must be indented by the same number of spaces.
-
The statements of nested conditional statements establish their own, deeper indent level.
-
Each
when
{.firrtl} and eachelse
{.firrtl} context may have a different number of non-zero spaces in its indent level.
As an example illustrating some of these points, the following is a legal FIRRTL circuit:
circuit Foo :
module Foo :
skip
module Bar :
input a: UInt<1>
output b: UInt<1>
when a:
connect b, a
else:
connect b, not(a)
All circuits, modules, ports and statements can optionally be followed with the
info token @[fileinfo]
{.firrtl} where fileinfo is a string containing the
source file information from where it was generated. The following characters
need to be escaped with a leading '\
': '\n
' (new line), '\t
' (tab), ']
'
and '\
' itself.
The following example shows the info tokens included:
circuit Top : @[myfile.txt 14:8]
module Top : @[myfile.txt 15:2]
output out: UInt @[myfile.txt 16:3]
input b: UInt<32> @[myfile.txt 17:3]
input c: UInt<1> @[myfile.txt 18:3]
input d: UInt<16> @[myfile.txt 19:3]
wire a: UInt @[myfile.txt 21:8]
when c : @[myfile.txt 24:8]
connect a, b @[myfile.txt 27:16]
else :
connect a, d @[myfile.txt 29:17]
connect out, add(a,a) @[myfile.txt 34:4]
This section provides auxiliary information necessary for developers of a FIRRTL Compiler implementation. A FIRRTL Compiler is a program that converts FIRRTL text to another representation, e.g., Verilog, VHDL, a programming language, or a binary program.
A module's convention describes how its ports are lowered to the output format, and serves as a kind of ABI for modules.
The scalarized convention lowers aggregate ports to ground values. The scalarized convention should be the default convention for "public" modules, such as the top module of a circuit, "device under test", or an extmodule.
The lowering algorithm for the scalarized convention operates as follows:
-
Ports are scalarized in the order they are declared.
-
Ground-typed ports' names are unmodified.
-
Vector-typed ports are scalarized to ground-typed ports by appending a suffix,
_<i>
, to the i^th^ element of the vector. Elements are scalarized recursively, depth-first, and left-to-right. -
Bundle-typed ports are scalarized to ground-typed ports by appending a suffix,
_<name>
, to the field calledname
. Fields are scalarized recursively, depth-first, and left-to-right.
E.g., consider the following port:
module Top :
input a : { b: UInt<1>, c: UInt<2> }[2]
Scalarization breaks a
into the following ports:
module Top :
input a_0_b : UInt<1> ; a[0].b
input a_0_c : UInt<2> ; a[0].c
input a_1_b : UInt<1> ; a[1].b
input a_1_c : UInt<2> ; a[1].c
The body of a module definition introduces a new, empty namespace. As new port names are added, these names must be unique with respect to this namespace. In the case of a collision during renaming, priority will be given to values that are converted first.
If a name is already taken, that name will be made unique by appending a suffix
_<i>
to the name, where i
is the lowest nonnegative integer that gives a
unique name.
E.g., consider the following ports:
module Top :
input a : { b: UInt<1>[2], b_0: UInt<2>, b_1: UInt<3> }
input a_b : UInt<4>[2]
input a_b_0 : UInt<5>
Scalarization breaks these ports into the following ports:
module Top :
input a_b_0: UInt<1> ; a.b[0]
input a_b_1: UInt<1> ; a.b[1]
input a_b_0_0: UInt<2> ; a.b_0
input a_b_1_0: UInt<3> ; a.b_1
input a_b_0_1: UInt<4> ; a_b[0]
input a_b_1_1: UInt<4> ; a_b[1]
input a_b_0_2: UInt<5> ; a_b_0
Named components in the body of a module will be renamed as needed to ensure port names follow this convention.
Private modules (i.e. modules that are not the top of a circuit, device under test, or an extmodule) have no specified ABI. The compiler is free to transform the ports of a private module in any way, or not at all. Private modules are said to have "internal" convention.
\clearpage
(* Whitespace definitions *)
indent = " " , { " " } ;
dedent = ? remove one level of indentation ? ;
newline = ? a newline character ? ;
(* Integer Literals *)
digit_bin = "0" | "1" ;
digit_oct = digit_bin | "2" | "3" | "4" | "5" | "6" | "7" ;
digit_dec = digit_oct | "8" | "9" ;
digit_hex = digit_dec
| "A" | "B" | "C" | "D" | "E" | "F"
| "a" | "b" | "c" | "d" | "e" | "f" ;
int = [ "-" ] , digit_dec , { digit_dec } ;
(* Radix-specified Integer Literals *)
rint =
[ "-" ] , "0b" , digit_bin , { digit_bin }
| [ "-" ] , "0o" , digit_oct , { digit_oct }
| [ "-" ] , "0d" , digit_oct , { digit_dec }
| [ "-" ] , "0h" , digit_hex , { digit_hex } ;
(* String Literals *)
string = ? a string ? ;
string_dq = '"' , string , '"' ;
string_sq = "'" , string , "'" ;
(* Identifiers define legal FIRRTL or Verilog names *)
letter = "A" | "B" | "C" | "D" | "E" | "F" | "G"
| "H" | "I" | "J" | "K" | "L" | "M" | "N"
| "O" | "P" | "Q" | "R" | "S" | "T" | "U"
| "V" | "W" | "X" | "Y" | "Z"
| "a" | "b" | "c" | "d" | "e" | "f" | "g"
| "h" | "i" | "j" | "k" | "l" | "m" | "n"
| "o" | "p" | "q" | "r" | "s" | "t" | "u"
| "v" | "w" | "x" | "y" | "z" ;
literal_id =
"`" , ( "_" | letter | digit_dec ), { "_" | letter | digit_dec } , "`" ;
id = ( "_" | letter ) , { "_" | letter | digit_dec } | literal_id ;
(* Fileinfo communicates Chisel source file and line/column info *)
linecol = digit_dec , { digit_dec } , ":" , digit_dec , { digit_dec } ;
lineinfo = string, " ", linecol
info = "@" , "[" , lineinfo, { ",", lineinfo }, "]" ;
(* Type definitions *)
width = "<" , int , ">" ;
type_ground = "Clock" | "Reset" | "AsyncReset"
| ( "UInt" | "SInt" | "Analog" ) , [ width ] ;
type_enum = "{|" , { field_enum } , "|}" ;
field_enum = id, [ ":" , type_simple_child ] ;
type_aggregate = "{" , field , { field } , "}"
| type , "[" , int , "]" ;
type_ref = ( "Probe" | "RWProbe" ) , "<", type , [ "," , id , "," ] ">" ;
field = [ "flip" ] , id , ":" , type ;
type_property = "Integer" ;
type_simple_child = type_ground | type_enum | type_aggregate | id ;
type = ( [ "const" ] , type_simple_child ) | type_ref ;
(* Type alias declaration *)
type_alias_decl = "type", id, "=", type ;
(* Primitive operations *)
primop_2expr_keyword =
"add" | "sub" | "mul" | "div" | "mod"
| "lt" | "leq" | "gt" | "geq" | "eq" | "neq"
| "dshl" | "dshr"
| "and" | "or" | "xor" | "cat" ;
primop_2expr =
primop_2expr_keyword , "(" , expr , "," , expr ")" ;
primop_1expr_keyword =
"asUInt" | "asSInt" | "asClock" | "asAsyncReset" | "cvt"
| "neg" | "not"
| "andr" | "orr" | "xorr" ;
primop_1expr =
primop_1expr_keyword , "(" , expr , ")" ;
primop_1expr1int_keyword =
"pad" | "shl" | "shr" | "head" | "tail" ;
primop_1expr1int =
primop_1exrp1int_keyword , "(", expr , "," , int , ")" ;
primop_1expr2int_keyword =
"bits" ;
primop_1expr2int =
primop_1expr2int_keyword , "(" , expr , "," , int , "," , int , ")" ;
primop = primop_2expr | primop_1expr | primop_1expr1int | primop_1expr2int ;
(* Expression definitions *)
expr =
( "UInt" | "SInt" ) , [ width ] , "(" , ( int | rint ) , ")"
| type_enum , "(" , id , [ "," , expr ] , ")"
| reference
| "mux" , "(" , expr , "," , expr , "," , expr , ")"
| "read" , "(" , ref_expr , ")"
| primop ;
static_reference = id
| static_reference , "." , id
| static_reference , "[" , int , "]" ;
reference = static_reference
| reference , "[" , expr , "]" ;
ref_expr = ( "probe" | "rwprobe" ) , "(" , static_reference , ")"
| static_reference ;
property_literal_expr = "Integer", "(", int, ")" ;
property_expr = static_reference | property_literal_expr ;
(* Memory *)
ruw = "old" | "new" | "undefined" ;
memory = "mem" , id , ":" , [ info ] , newline , indent ,
"data-type" , "=>" , type , newline ,
"depth" , "=>" , int , newline ,
"read-latency" , "=>" , int , newline ,
"write-latency" , "=>" , int , newline ,
"read-under-write" , "=>" , ruw , newline ,
{ "reader" , "=>" , id , newline } ,
{ "writer" , "=>" , id , newline } ,
{ "readwriter" , "=>" , id , newline } ,
dedent ;
(* Force and Release *)
force_release =
"force_initial" , "(" , ref_expr , "," , expr , ")"
| "release_initial" , "(" , ref_expr , ")"
| "force" , "(" , expr , "," , expr , "," , ref_expr , "," , expr , ")"
| "release" , "(" , expr , "," , expr , "," , ref_expr , ")" ;
(* Statements *)
statement =
"wire" , id , ":" , type , [ info ]
| "reg" , id , ":" , type , expr , [ info ]
| "regreset" , id , ":" , type , "," , expr , "," , expr , "," , expr ,
[info]
| memory
| "inst" , id , "of" , id , [ info ]
| "group" , id , "of" , id , ":" , [ info ] , newline ,
indent ,
{ port , newline } ,
{ statement , newline } ,
dedent
| "node" , id , "=" , expr , [ info ]
| "attach(" , reference , { "," , reference } , ")" , [ info ]
| "when" , expr , ":" [ info ] , newline ,
indent , statement, { statement } , dedent ,
[ "else" , ":" , indent , statement, { statement } , dedent ]
| "match" , expr , ":" , [ info ] , newline ,
[ indent ,
{ id , [ "(" , id , ")" ] , ":" , newline ,
[ indent , { statement } , dedent ]
} ,
dedent ]
| "stop(" , expr , "," , expr , "," , int , ")" , [ info ]
| "printf(" , expr , "," , expr , "," , string_dq ,
{ "," , expr } , ")" , [ ":" , id ] , [ info ]
| "skip" , [ info ]
| "define" , static_reference , "=" , ref_expr , [ info ]
| force_release , [ info ]
| "connect" , reference , "," , expr , [ info ]
| "invalidate" , reference , [ info ]
| "propassign" , static_reference , "," , property_expr , [ info ] ;
(* Module definitions *)
port = ( "input" | "output" ) , id , ":" , (type | type_property) , [ info ] ;
module = "module" , id , ":" , [ info ] , newline , indent ,
{ port , newline } ,
{ statement , newline } ,
dedent ;
type_param = int | string_dq | string_sq ;
extmodule = "extmodule" , id , ":" , [ info ] , newline , indent ,
{ port , newline } ,
[ "defname" , "=" , id , newline ] ,
{ "parameter" , id , "=" , type_param , newline } ,
{ "ref" , static_reference , "is" ,
'"' , static_reference , '"' , newline } ,
dedent ;
intmodule = "intmodule" , id , ":" , [ info ] , newline , indent ,
{ port , newline } ,
"intrinsic" , "=" , id , newline ,
{ "parameter" , "=" , ( int | string_dq ) , newline } ,
dedent ;
(* Group definitions *)
declgroup =
"declgroup" , id , string , ":" , [ info ] , newline , indent ,
{ declgroup , newline } ,
dedent ;
(* In-line Annotations *)
annotations = "%" , "[" , json_array , "]" ;
(* Version definition *)
sem_ver = int , "." , int , "." , int
version = "FIRRTL" , "version" , sem_ver ;
(* Circuit definition *)
circuit =
version , newline ,
"circuit" , id , ":" , [ annotations ] , [ info ] , newline , indent ,
{ module | extmodule | intmodule | declgroup | type_alias_decl } ,
dedent ;
This is the versioning scheme that applies to version 1.0.0 and later.
The versioning scheme complies with Semantic Versioning 2.0.0.
Specifically,
The PATCH digit is bumped upon release which only includes non-functional changes, such as grammar edits, further examples, and clarifications.
The MINOR digit is bumped for feature additions to the spec.
The MAJOR digit is bumped for backwards-incompatible changes such as features being removed from the spec, changing their interpretation, or new required features being added to the specification.
In other words, any .fir
file that was compliant with x.y.z
will be compliant
with x.Y.Z
, where Y >= y
, z
and Z
can be any number.