Skip to content

Changes in Chisel

Raffaele Meloni edited this page Jun 22, 2024 · 2 revisions

Table of Contents

Changes in Chisel: generating the tywaves annotation for FIRRTL

As shown in the Chisel simulation pipeline section, a circuit needs be compiled to Verilog in order to be simulated. This compilation process consists of two main steps:

  • Chisel elaboration: the Chisel code is executed and elaborated to an equivalent firrtl circuit (flexible intermediate representation).
  • Firrtl compilation: the firrtl circuit is lowered to Verilog by firtool.

Thus, the missing debug information must be first generated during the first step, emitted together with firrtl and propagated further through firtool.

This section focuses on the Chisel elaboration step. First, a short analysis of the process to get firrtl from chisel is presented. Secondly, the phases and phase manager, core engine for the transformation, are explained. Finally, an updated phase pipeline is proposed for the emission of the Chisel type information encoded in firrtl in the form of an annotation.

From Chisel to Firrtl

The translation to firrtl is managed within the Chisel library by ChiselStage, which acts as an entry point and bridge between Chisel and the CIRCT/MLIR compiler. Specifically, this component is responsible for analyzing and preparing the source scala code for firrtl-verilog compilation. Internally, before calling firtool, ChiselStage executes several steps (or Phases) to elaborate Chisel source to firrtl. These phases are handled by the PhaseManager, which is responsible for storing, executing, and checking the dependencies and order between multiple elaboration steps.

The elaboration process is driven by the annotation-transformation mechanism, where the annotations are scala case classes storing generic information for the compiler within Chisel. Each phase implements mathematical transformation of a sequence of annotations as shown in Fig. 1.

Mathematical transform of the phase trait
Fig. 1 - Mathematical transformation implemented by the phase trait: transform(A) -> A'

ChiselStage implements an execute method which is used to run the full compilation process from scala. The method does 3 main operations:

  1. It first collects a set of input string arguments (CLI-like) and converts them to the corresponding annotation case classes;
  2. Then, it checks the validity of the sequence, if so the phases are guaranteed to succeed;
  3. Finally, it executes a sequence of transformation on those annotations through the phase manager.

The snippet below shows an example of calling this function to generate Verilog where the argument --target specifies the target language and ChiselGeneratorAnnotation is used to pass the circuit to the underlying compiler.

val chiselStage = new circt.stage.ChiselStage
val target = "verilog" // OPTIONS: --target {chirrtl|firrtl|hw|verilog|systemverilog}

chiselStage.execute(
    // CLI-like arguments
    args = Array("--target", target),
    // Annotations
    annotations = Seq(chisel3.stage.ChiselGeneratorAnnotation(() => new MyChiselModule()))
)

PhaseManager and Phases executed

The PhaseManager executes is the core of the elaboration since it performs the transformations.

In the sequence of phases specified by ChiselStage there are 2 phases of particular interest for the translation to firrtl: the Elaborate and Convert phases. The body of a module is executed and elaborated into a circuit hardware graph (stored in a ChiselCircuitAnnotation) which is subsequently converted into a firrtl hardware graph (stored in a FirrtlCircuitAnnotation). This output firrtl representation is finally passed to the firtool through the CIRCT phase.

Currently, this pipeline does not support the generation of "chisel" debug information. Indeed, during the convert phase, any additional non-relevant information for a firrtl representation is skipped, such as the scala type information.

Phase manager and phases executed
Fig. 2 - Highlighting the phases responsible for the transformation from Chisel to firrtl in the PhaseManager

Updated phases to include source language type information

With the pipeline above, the firtool would have only the information intrisic to a firrtl representation, missing details about the source language that generated it. Firrtl is an intermediate representation (IR) used to standardize circuits produced by Chisel. Although firrtl is able to preserve the structure of a circuit and signal hierarchies, it cannot express or associate its elements with original Scala types. For instance, user defined bundles are always converted to anonymous bundles, making difficult their identification. This limitation arises because firrtl is generated once the scala meta-programming is executed, and there is no support for reconstructing it from firrtl. For more information about firrtl, please refer to its specification.

Therefore, the type information needs to be passed to firrtl without changing the behaviour while keeping compatibility with the existing pipeline.

In order to do so, I propose the following update to the existing phase manager of figure 2. A new phase, AddTywavesAnnotation (figure 3), is added to the pipeline between the Elaborate and Convert phases. It parses the circuit graph emitted by the elaborate phase and annotates each component with its respective type information stored in the form of a FIRRTL annotation. The FIRRTL annotations are a mechanism to associate arbitrary metadata with zero or more target objects (i.e. signals, modules etc...) of a firrtl circuit. The resulting scala annotation to encode the extra debug information is the TywavesAnnotation case class reported below. The type name and fields of constructor parameters are implemented as strings in order to cover any possible type. In addition the annotation is declared as private to keep its usage internal only, since it is something that the compiler should automatically generate and not the user.

case class ClassParam(name: String, typeName: String, value: Option[String])

private[chisel3] case class TywavesAnnotation[T <: IsMember](
  target:   T,
  typeName: String,
  // encode params as an option so if the class has no parameters, there is no field in the FIRRTL
  params: Option[Seq[ClassParam]])
    extends SingleTargetAnnotation[T] {
  def duplicate(n: T) = this.copy(n)
}

The annotated circuit is then passed to the Convert phase which will serialize the TywavesAnnotation to a JSON string during the conversion to firrtl. From here the firtool can consume it and operate specific debug transformations.

Phase manager and phases updated
Fig. 3 - The updated PhaseManager for passing type information to firtool

The only drawback of annotations is that they must be supported by all firrtl compilers, this means that if an annotation is present in a firrtl output, the firrtl compiler used must handle it or abort. So, if other compilers of firrtl are used, they may not work properly. However, I proposed and implemented updates of firtool to handle that.

Finally, in the updated phase pipeline this information is generated only when a debug flag is set, indicating that the circuit should be elaborated in a "debug compilation mode". In fact, debug information is not always necessary, for instance, when the logic is compiled for synthesis. Hence, this addition will not affect the compilation time if the flag is not set.

Tywaves Annotation: types and constructor parameters

The TywavesAnnotation stores the type and constructor parameters of a Chisel component. Here an example of its serialization to JSON:

{
  "class":"chisel3.tywaves.TywavesAnnotation",
  "target":"~MyModule|MyModule",
  "typeName":"MyModule"
},


{
  "class":"chisel3.tywaves.TywavesAnnotation",
  "target":"~MyModule|MyModule>inBundle",
  "typeName":"IO[MyBundle]",
  "params":[
    {
      "name":"size",
      "typeName":"Int",
      "value":"10"
    }
  ]
},


{
  "class":"chisel3.tywaves.TywavesAnnotation",
  "target":"~MyModule|MyModule>wireBundle.a",
  "typeName":"Wire[Bool]"
},


{
  "class":"chisel3.tywaves.TywavesAnnotation",
  "target":"~MyModule|MyModule>outBundle.a",
  "typeName":"IO[SInt<9>]"
},