Skip to main content

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:

  1. A layer declaration
  2. 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:

  1. "Extract" Layers: layers whose blocks are lowered to bound-in modules, and
  2. "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:

  1. The built-in Verification layer
  2. The built-in Assert layer which is nested under the built-in Verification layer
  3. A user-defined Debug layer which is also nested under the built-in Verification 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:

  1. layers_Foo_Verification.sv
  2. layers_Foo_Verification_Assert.sv
  3. 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):

  1. Foo
  2. Foo_Verification
  3. Foo_Verification_Assert
  4. Foo_Verification_Cover
  5. 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