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:
- A layer declaration
- One or more layer blocks inside modules in the circuit
There are two kinds of layers, each kind is lowered to verilog under a different convention. The kinds of layers are:
- "Extract" Layers: layers whose blocks are lowered to bound-in modules, and
- "Inline" Layers: layers whose blocks are lowered to ifdefs macros.
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())
}
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
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.
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.
Examples
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 asserts and debug prints requires additional computation. All of this code should selectively included at Verilog elaboration time (not at Chisel elaboration time). The engineer can use three layers to do this.
There are three layers used in this example:
- The built-in
Verification
layer - The built-in
Assert
layer which is nested under the built-inVerification
layer - A user-defined
Debug
layer which is also nested under the built-inVerification
layer
The Verification
layer can be used to store common logic used by both the
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:
layers_Foo_Verification.sv
layers_Foo_Verification_Assert.sv
layers_Foo_Verification_Debug.sv
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
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
):
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
.
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.
Verilog Output
The complete Verilog output for this example is reproduced below:
// Generated by CIRCT firtool-1.94.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.94.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.94.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.94.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.94.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.94.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.94.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.94.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.94.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