Layers
Layers are used to describe functionality of a Chisel circuit or module that a -user would like to optionally include at Verilog elaboration time. This -feature is intended to be used to optionally include verification or debug logic -that a user does not want to always have present.
-Each layer is broken into two pieces:
+Layers describe functionality of a Chisel generator that a user would like to
+optionally include at Verilog elaboration time. Pragmatically, they are a
+feature to access SystemVerilog's bind
construct and `ifdef
preprocessor
+macros. Layers are typically used to describe design verification code or
+debugging logic that a user would like to be able to later disable (for
+performance, verbosity, or cleanliness reasons) or use internally, but exclude
+from delivery to a customer.
Overview​
+A layer consists of two pieces:
-
-
- A layer declaration -
- One or more layer blocks inside modules in the circuit +
- A layer declaration, and +
- One or more layer blocks inside Chisel modules.
There are two kinds of layers, each kind is lowered to verilog under a different -convention. The kinds of layers are:
+The declaration indicates that optional functionality can exist. The layer +block contains the optional functionality.
+There are two kinds of layers. The layer kind determines the convention, +i.e., how the layer blocks of a layer are represented in Verilog and the +mechanism to enable a layer. Available layer kinds are:
-
-
- "Extract" Layers: layers whose blocks are lowered to bound-in modules, and -
- "Inline" Layers: layers whose blocks are lowered to ifdefs macros. +
- "Extract" Layers: layers whose blocks are lowered to modules that are
+instantiated using
bind
and can be enabled by including a file during +Verilog elaboration, and
+ - "Inline" Layers: layers whose blocks will be guarded with
`ifdef
macros +and can be enabled by setting a Verilog preprocessor define.
To declare a layer, create a singleton object
in scala that extends the
extend the abstract class chisel3.layer.Layer
, passing into the layer
-constructor either chisel3.layer.LayerConfig.Extract()
for an "extract" layer,
-or chisel3.layer.LayerConfig.Inline
for "inline" layers.
Layers may be nested. To declare a nested layer, extend the
-chisel3.layer.Layer
abstract class inside another declaration.
The following example declares four layers:
-import chisel3.layer.{Layer, LayerConfig}
object A extends Layer(LayerConfig.Extract()) {
object B extends Layer(LayerConfig.Extract()) {
object C extends Layer(LayerConfig.Extract())
}
object D extends Layer(LayerConfig.Extract())
}
Extract layers may also specify a directory into which their collateral are +written.
+For more information about these SystemVerilog concepts, the IEEE 1800-2023
+standard discusses bind
in Section 23.11 and `ifdef
in Section 23.6.
To declare a layer, create a singleton object
in scala that extends the
+abstract class chisel3.layer.Layer
, passing into the layer constructor either
+an object of class chisel3.layer.LayerConfig.Extract
for an extract layer, or
+the object chisel3.layer.LayerConfig.Inline
for an inline layer.
Below, an extract layer and an inline layer are declared:
+import chisel3.layer.{Layer, LayerConfig}
object A extends Layer(LayerConfig.Extract())
object B extends Layer(LayerConfig.Inline)
Layers may be nested. Nesting a child layer under a parent layer means that the
+child layer may access constructs in the parent layer. Put differently, the
+child layer will only be enabled if the parent layer is already enabled. To
+declare a nested layer, extend the chisel3.layer.Layer
abstract class inside
+another declaration.
The following example defines an extract layer with two nested layers inside it:
+object C extends Layer(LayerConfig.Extract()) {
object D extends Layer(LayerConfig.Extract())
object E extends Layer(LayerConfig.Inline) {
object F extends Layer(LayerConfig.Inline)
}
}
SystemVerilog prohibits a bind
instantiation under another bind
+instantiation. However, Chisel allows nesting of extract layers. This is
+resolved by the FIRRTL compiler to restructure nested extract layers to be
+sibling modules that communicate via ports.
Extract layers may not be nested under inline layers. However, inline layers +may be nested under extract layers.
Any module which contains layer blocks or transitively contains layer blocks in +its submodules may not be instantiated under a layer block.
A layer block, associated with a layer, adds optional functionality to a -module that is enabled if that layer is enabled. Each layer block must refer to -a pre-declared layer. Layer block nesting must match the nesting of declared -layers.
-To define a layer block, use the chisel3.layer.block
inside a Chisel module.
-An layer block may use any Chisel or Scala value visible to its Scala lexical
-scope.
The following example defines layer blocks inside module Foo
and declares
-wires which are connected to values captured from visible lexical scope:
import chisel3._
import chisel3.layer.block
class Foo extends RawModule {
val port = IO(Input(Bool()))
block(A) {
val a = WireInit(port)
block(A.B) {
val b = WireInit(a)
block(A.B.C) {
val c = WireInit(b)
}
}
block(A.D) {
val d = WireInit(port ^ a)
}
}
}
Conventions​
-Currently, there is only one supported convention, Bind
. This will cause layer
-blocks to be lowered to Verilog modules that are instantiated via the
-SystemVerilog bind
mechanism. The lowering to Verilog of layer blocks avoids
-illegal nested usage of bind
.
More conventions may be supported in the future.
-Built-in Layers and User-defined Layers​
+module that is enabled if that layer is enabled. To define a layer block, use +thechisel3.layer.block
inside a Chisel module and pass the layer that it
+should be associated with.
+Inside the layer block, any Chisel or Scala value visible in lexical scope may +be used. Layer blocks may not return values. Any values created inside a layer +block are not accessible outside the layer block, unless using layer-colored +probes.
+The following example defines layer blocks inside module Foo
. Each layer
+block contains a wire that captures a value from its visible lexical scope.
+(For nested layer blocks, this scope includes their parent layer blocks.):
import chisel3._
import chisel3.layer.block
class Foo extends RawModule {
val port = IO(Input(Bool()))
block(A) {
val a = WireInit(port)
}
block(B) {
val b = WireInit(port)
}
block(C) {
val c = WireInit(port)
block(C.D) {
val d = WireInit(port | c)
}
block(C.E) {
val e = WireInit(port ^ c)
block (C.E.F) {
val f = WireInit(port & e)
}
}
}
}
The layer block API will automatically create parent layer blocks for you if
+possible. In the following example, it is legal to directly create a layer
+block of C.D
directly in a module:
class Bar extends RawModule {
block (C.D) {}
}
Formally, it is legal to create a layer block associated with a layer as long as +the current scope is an ancestor of the request layer.
+The requirement is an ancestor relationship, not a proper ancestor +relationship. This means that it is legal to nest a layer block under a layer +block of the same layer like:
class Baz extends RawModule {
block(A) {
block(A) {}
}
}
Verilog ABI​
+Layers are compiled to SystemVerilog using the FIRRTL ABI. This ABI defines +what happens to layer blocks in a Chisel design and how a layer can be enabled +after a design is compiled to SystemVerilog.
+For the exact definition of the FIRRTL ABI for layers, see the latest version +of the FIRRTL ABI +Specification.
Extract Layers​
+Extract layers have their layer blocks removed from the design. To enable a
+layer, a file with a specific name should be included in the design. This file
+begins with layers-
and then includes the circuit name and all layer names
+delimited with dashes (-
).
For example, for module Foo
declared above, this will produce three files, one
+for each extract layer:
layers-Foo-A.sv
layers-Foo-C.sv
layers-Foo-C-D.sv
To enable any of these layers at compilation time, the appropriate file should +be included in the build. Any combination of files may be included. Including +only a child layer's file will automatically include its parent layer's file.
+Inline Layers​
+Inline layers have their layer blocks guarded with conditional compilation
+directives. To enable an inline layer, set a preprocessor define when compiling
+your design. The preprocessor define begins with layer_
and then includes the
+circuit name and all layer names delimited with dollar signs ($
). Parent
+extract layer names appear in the macro.
For example, for module Foo
declared above, this will be sensitive to three
+macros, one for each inline layer:
layer_Foo$B
layer_Foo$C$E
layer_Foo$C$E$F
User-defined Layers​
+A user is free to define as many layers as they want. All layers shown
+previously are user-defined, e.g., A
and C.E
are user-defined layers.
+User-defined layers are only emitted into FIRRTL if they have layer block users.
+To change this behavior and unconditionally emit a user-defined layer, use the
+chisel3.layer.addLayer
API.
Before creating new user-defined layers, consider using the built-in layers +defined below. Additionally, if working in a larger project, the project may +have it's own user-defined layers that you are expected to use. This is because +the ABIs affect the build system. Please consult with a technical lead of the +project to see if this is the case.
Built-in Layers​
Chisel provides several built-in layers. These are shown below with their full -Scala paths:
-`chisel3.layers.Verification`
├── `chisel3.layers.Verification.Assert`
├── `chisel3.layers.Verification.Assume`
└── `chisel3.layers.Verification.Cover`
These built-in layers are dual purpose. First, they are layers that match
-common use cases for sequestering verification code. The Verification
layer
-is for common verification collateral. The Assert
, Assume
, and Cover
-layers are for, respectively, assertions, assumptions, and cover statements.
-Second, the Chisel standard library uses them for some of its APIs.
chisel3.layers.Verification
├── chisel3.layers.Verification.Assert
├── chisel3.layers.Verification.Assume
└── chisel3.layers.Verification.Cover
These built-in layers are dual purpose. First, these layers match the common
+use case of sequestering verification code. The Verification
layer is for
+common verification collateral. The Assert
, Assume
, and Cover
layers are
+for, respectively, assertions, assumptions, and cover statements. Second, the
+Chisel standard library uses them for a number of its APIs. Unless otherwise
+wrapped in a different layer block, the following operations are automatically
+placed in layers:
-
+
- Prints are placed in the
Verification
layer
+ - Assertions are placed in the
Verification.Assert
layer
+ - Assumptions are placed in the
Verification.Assume
layer
+ - Covers are placed in the
Verification.Cover
layer
+
For predictability of output, these layers will always be show up in the FIRRTL -that Chisel emits.
-A user is free to define their own layers, as shown previously with layer A
,
-A.B
, etc. User-defined layers are only emitted into FIRRTL if they have layer
-block users. Layers can be unconditionally emitted using the
-chisel3.layer.addLayer
API.
firtool
command line options
+to specialize these layers (remove their optionality by making them always
+enabled or disabled). Use -enable-layers
to enable a layer, -disable-layers
+to disable a layer, or -default-layer-specialization
to set a default
+specialization.
+Users may extend built-in layers with user-defined layers using an advanced API. +To do this, the layer parent must be specified as an implicit value.
The following example nests the layer Debug
to the Verification
layer:
object UserDefined {
// Define an implicit val `root` of type `Layer` to cause layers which can see
// this to use `root` as their parent layer. This allows us to nest the
// user-defined `Debug` layer under the built-in `Verification` layer.
implicit val root: Layer = chisel3.layers.Verification
object Debug extends Layer(LayerConfig.Inline)
}
Examples​
+Simple Extract Layer​
+The design below has a single extract layer that, when enabled, will add an
+assert that checks for overflow. Based on the FIRRTL ABI, we can expect that a
+file called layers-Foo-A.sv
will be produced when we compile it.
import chisel3._
import chisel3.layer.{Layer, LayerConfig, block}
import chisel3.ltl.AssertProperty
object A extends Layer(LayerConfig.Extract())
class Foo extends Module {
val a, b = IO(Input(UInt(4.W)))
val sum = IO(Output(UInt(4.W)))
sum :<= a +% b
block(A) {
withDisable(Disable.Never) {
AssertProperty(!(a +& b)(4), "overflow occurred")
}
}
}
After compilation, we get the following SystemVerilog. Comments that include
+FILE
indicate the beginning of a new file:
// Generated by CIRCT firtool-1.98.0
module Foo(
input clock,
reset,
input [3:0] a,
b,
output [3:0] sum
);
wire [4:0] _sum_T = {1'h0, a} + {1'h0, b};
assign sum = _sum_T[3:0];
endmodule
// ----- 8< ----- FILE "A/layers-Foo-A.sv" ----- 8< -----
// Generated by CIRCT firtool-1.98.0
`ifndef layers_Foo_A
`define layers_Foo_A
bind Foo Foo_A a_0 (
._GEN (_sum_T),
.clock (clock)
);
`endif // layers_Foo_A
// ----- 8< ----- FILE "A/Foo_A.sv" ----- 8< -----
// Generated by CIRCT firtool-1.98.0
module Foo_A(
input [4:0] _GEN,
input clock
);
overflow_occurred: assert property (@(posedge clock) ~(_GEN[4]));
endmodule
The above example was compiled with the firtool options
+-enable-layers=Verification
, -enable-layers=Verification.Assert
,
+-enable-layers=Verification.Assume
, and -enable-layers=Verification.Cover
to
+make the output terser. Normally, bind files would show up for these built-in
+layers.
Note: the generated module, Foo_A
, and its file, Foo_A.sv
, are not part of
+the ABI. You should not rely on any generated module names or files other than
+the bind file, layers-Foo-A.sv
.
Simple Inline Layer​
+The design below is the same as the previous example, but uses an inline layer.
+Based on the FIRRTL ABI, we can expect that the body of the layer block will be
+guarded by an `ifdef
sensitive to the preprocessor macro layer_Foo$A
.
import chisel3._
import chisel3.layer.{Layer, LayerConfig, block}
import chisel3.ltl.AssertProperty
object A extends Layer(LayerConfig.Inline)
class Foo extends Module {
val a, b = IO(Input(UInt(4.W)))
val sum = IO(Output(UInt(4.W)))
sum :<= a +% b
block(A) {
withDisable(Disable.Never) {
AssertProperty(!(a +& b)(4), "overflow occurred")
}
}
}
After compilation, we get the following SystemVerilog.
+// Generated by CIRCT firtool-1.98.0
module Foo(
input clock,
reset,
input [3:0] a,
b,
output [3:0] sum
);
wire [4:0] _sum_T = {1'h0, a} + {1'h0, b};
`ifdef layer_Foo$A
overflow_occurred: assert property (@(posedge clock) ~(_sum_T[4]));
`endif // layer_Foo$A
assign sum = _sum_T[3:0];
endmodule
Design Verification Example​
Consider a use case where a design or design verification engineer would like to add some asserts and debug prints to a module. The logic necessary for the @@ -82,42 +184,49 @@
Assert
and Debug
layers. The latter two layers allow for separation of,
respectively, assertions from prints.
One way to write this in Scala is the following:
-import chisel3._
import chisel3.layer.{Layer, LayerConfig, block}
import chisel3.layers.Verification
// User-defined layers are declared here. Built-in layers do not need to be declared.
object UserDefined {
// Define an implicit val `root` of type `Layer` to cause layers which can see
// this to use `root` as their parent layer. This allows us to nest the
// user-defined `Debug` layer under the built-in `Verification` layer.
implicit val root: Layer = Verification
object Debug extends Layer(LayerConfig.Extract())
}
class Foo extends Module {
val a = IO(Input(UInt(32.W)))
val b = IO(Output(UInt(32.W)))
b := a +% 1.U
// This adds a `Verification` layer block inside Foo.
block(Verification) {
// Some common logic added here. The input port `a` is "captured" and
// used here.
val a_d0 = RegNext(a)
// This adds an `Assert` layer block.
block(Verification.Assert) {
chisel3.assert(a >= a_d0, "a must always increment")
}
// This adds a `Debug` layer block.
block(UserDefined.Debug) {
printf("a: %x, a_d0: %x", a, a_d0)
}
}
}
After compilation, this will produce three layer include files with the -following filenames. One file is created for each layer:
+import chisel3._
import chisel3.layer.{Layer, LayerConfig, block}
import chisel3.layers.Verification
// User-defined layers are declared here. Built-in layers do not need to be declared.
object UserDefined {
implicit val root: Layer = Verification
object Debug extends Layer(LayerConfig.Inline)
}
class Foo extends Module {
val a = IO(Input(UInt(32.W)))
val b = IO(Output(UInt(32.W)))
b := a +% 1.U
// This adds a `Verification` layer block inside Foo.
block(Verification) {
// Some common logic added here. The input port `a` is "captured" and
// used here.
val a_d0 = RegNext(a)
// This adds an `Assert` layer block.
block(Verification.Assert) {
chisel3.assert(a >= a_d0, "a must always increment")
}
// This adds a `Debug` layer block.
block(UserDefined.Debug) {
printf("a: %x, a_d0: %x", a, a_d0)
}
}
}
After compilation, this will produce two layer include files with the +following filenames. One file is created for each extract layer:
layers_Foo_Verification.sv
layers_Foo_Verification_Assert.sv
-layers_Foo_Verification_Debug.sv
Additionally, the resulting SystemVerilog will be sensitive to the preprocessor
+define layer_Foo$Verification$Debug
due to the one inline layer we added.
A user can then include any combination of these files in their design to
-include the optional functionality describe by the Verification
, Assert
, or
-Debug
layers. The Assert
and Debug
bind files automatically include the
+include the optional functionality described by the Verification
or
+Verification.Assert
layers and enable debugging by setting the preprocessor
+macro. The Verification.Assert
bind file automatically includes the
Verification
bind file for the user.
Implementation Notes​
-Note: the names of the modules and the names of any files that contain these -modules are FIRRTL compiler implementation defined! The only guarantee is the -existence of the three layer include files. The information in this subsection -is for informational purposes to aid understanding.
-In implementation, a FIRRTL compiler creates four Verilog modules for the
-circuit above (one for Foo
and one for each layer block in module Foo
):
This section describes the implementation of how layers are compiled. Anything +that is not a bind file name or a preprocessor macro should not be relied +upon! A FIRRTL compiler may implement this differently or may optimize layer +blocks in any legal way it chooses. E.g., layer blocks associated with the same +layer may be merged, layer blocks may be moved up or down the hierarchy, code +that only fans out to a layer block may be sunk into it, and unused layer blocks +may be deleted.
The information below is for user understanding and interest only.
In implementation, a FIRRTL compiler creates three Verilog modules for the
+circuit above (one for Foo
and one for each layer block associated with an
+extract layer in module Foo
):
Foo
Foo_Verification
Foo_Verification_Assert
-Foo_Verification_Cover
-Foo_Verification_Debug
These will typically be created in separate files with names that match the
-modules, i.e., Foo.sv
, Foo_Verification.sv
, Foo_Verification_Assert.sv
,
-Foo_Verification_Debug.sv
.
Foo.sv
, Foo_Verification.sv
, and
+Foo_Verification_Assert.sv
.
The ports of each module created from a layer block will be automatically
determined based on what that layer block captured from outside the layer block.
-In the example above, the Verification
layer block captured port a
. Both
-the Assert
and Debug
layer blocks captured a
and a_d0
. Layer blocks may
-be optimized to remove/add ports or to move logic into a layer block.
Verification
layer block captured port a
. The
+Assert
layer block captured captured a
and a_d0
.
+Even though there are no layer blocks that use the Verification.Assume
or
+Verification.Cover
layers, bind files which have no effect are produced in the
+output. This is due to the ABI which requires that layers that are defined in
+FIRRTL must produce these files.
Verilog Output​
The complete Verilog output for this example is reproduced below:
-// Generated by CIRCT firtool-1.98.0
module Foo(
input clock,
reset,
input [31:0] a,
output [31:0] b
);
assign b = a + 32'h1;
endmodule
// ----- 8< ----- FILE "verification/cover/layers-Foo-Verification-Cover.sv" ----- 8< -----
// Generated by CIRCT firtool-1.98.0
`include "verification/layers-Foo-Verification.sv"
`ifndef layers_Foo_Verification_Cover
`define layers_Foo_Verification_Cover
`endif // layers_Foo_Verification_Cover
// ----- 8< ----- FILE "verification/assume/layers-Foo-Verification-Assume.sv" ----- 8< -----
// Generated by CIRCT firtool-1.98.0
`include "verification/layers-Foo-Verification.sv"
`ifndef layers_Foo_Verification_Assume
`define layers_Foo_Verification_Assume
`endif // layers_Foo_Verification_Assume
// ----- 8< ----- FILE "verification/Debug/layers-Foo-Verification-Debug.sv" ----- 8< -----
// Generated by CIRCT firtool-1.98.0
`include "verification/layers-Foo-Verification.sv"
`ifndef layers_Foo_Verification_Debug
`define layers_Foo_Verification_Debug
bind Foo Foo_Verification_Debug verification_debug (
.reset (reset),
.clock (Foo.verification.clock_probe_0),
.a (Foo.verification.a_probe_0),
.a_d0 (Foo.verification.a_d0_probe_0)
);
`endif // layers_Foo_Verification_Debug
// ----- 8< ----- FILE "verification/assert/layers-Foo-Verification-Assert.sv" ----- 8< -----
// Generated by CIRCT firtool-1.98.0
`include "verification/layers-Foo-Verification.sv"
`ifndef layers_Foo_Verification_Assert
`define layers_Foo_Verification_Assert
bind Foo Foo_Verification_Assert verification_assert (
.a (Foo.verification.a_probe),
.a_d0 (Foo.verification.a_d0_probe),
.reset (reset),
.clock (Foo.verification.clock_probe)
);
`endif // layers_Foo_Verification_Assert
// ----- 8< ----- FILE "verification/layers-Foo-Verification.sv" ----- 8< -----
// Generated by CIRCT firtool-1.98.0
`ifndef layers_Foo_Verification
`define layers_Foo_Verification
bind Foo Foo_Verification verification (
.clock (clock),
.a (a)
);
`endif // layers_Foo_Verification
// ----- 8< ----- FILE "verification/assert/Foo_Verification_Assert.sv" ----- 8< -----
// Generated by CIRCT firtool-1.98.0
// Users can define 'STOP_COND' to add an extra gate to stop conditions.
`ifndef STOP_COND_
`ifdef STOP_COND
`define STOP_COND_ (`STOP_COND)
`else // STOP_COND
`define STOP_COND_ 1
`endif // STOP_COND
`endif // not def STOP_COND_
// Users can define 'ASSERT_VERBOSE_COND' to add an extra gate to assert error printing.
`ifndef ASSERT_VERBOSE_COND_
`ifdef ASSERT_VERBOSE_COND
`define ASSERT_VERBOSE_COND_ (`ASSERT_VERBOSE_COND)
`else // ASSERT_VERBOSE_COND
`define ASSERT_VERBOSE_COND_ 1
`endif // ASSERT_VERBOSE_COND
`endif // not def ASSERT_VERBOSE_COND_
module Foo_Verification_Assert(
input [31:0] a,
a_d0,
input reset,
clock
);
`ifndef SYNTHESIS
always @(posedge clock) begin
if (~reset & a < a_d0) begin
if (`ASSERT_VERBOSE_COND_)
$error("Assertion failed: a must always increment\n");
if (`STOP_COND_)
$fatal;
end
end // always @(posedge)
`endif // not def SYNTHESIS
endmodule
// ----- 8< ----- FILE "verification/Debug/Foo_Verification_Debug.sv" ----- 8< -----
// Generated by CIRCT firtool-1.98.0
// Users can define 'PRINTF_COND' to add an extra gate to prints.
`ifndef PRINTF_COND_
`ifdef PRINTF_COND
`define PRINTF_COND_ (`PRINTF_COND)
`else // PRINTF_COND
`define PRINTF_COND_ 1
`endif // PRINTF_COND
`endif // not def PRINTF_COND_
module Foo_Verification_Debug(
input reset,
clock,
input [31:0] a,
a_d0
);
`ifndef SYNTHESIS
always @(posedge clock) begin
if ((`PRINTF_COND_) & ~reset)
$fwrite(32'h80000002, "a: %x, a_d0: %x", a, a_d0);
end // always @(posedge)
`endif // not def SYNTHESIS
endmodule
// ----- 8< ----- FILE "verification/Foo_Verification.sv" ----- 8< -----
// Generated by CIRCT firtool-1.98.0
module Foo_Verification(
input clock,
input [31:0] a
);
wire clock_probe = clock;
wire [31:0] a_probe = a;
wire [31:0] a_probe_0 = a;
wire clock_probe_0 = clock;
reg [31:0] a_d0;
wire [31:0] a_d0_probe = a_d0;
wire [31:0] a_d0_probe_0 = a_d0;
always @(posedge clock)
a_d0 <= a;
endmodule