Annotations
Annotation
s are metadata containers associated with zero or more “things” in a FIRRTL circuit.
Commonly, Annotation
s are used to communicate information from Chisel to a specific, custom FIRRTL Transform
.
In this way Annotation
s can be viewed as the “arguments” that a specific Transform
consumes.
This article focuses on the approach to building a basic library that contains Annotation
s and Transform
s.
Imports
We need a few basic imports to reference the components we need.
import chisel3._
import chisel3.experimental.{annotate, ChiselAnnotation, RunFirrtlTransform}
import chisel3.internal.InstanceId
import firrtl._
import firrtl.annotations.{Annotation, SingleTargetAnnotation}
import firrtl.annotations.{CircuitTarget, ModuleTarget, InstanceTarget, ReferenceTarget, Target}
import scala.annotation.nowarn
Define an Annotation
and a Transform
First, define an Annotation
that contains a string associated with a Target
thing in the Chisel circuit.
This InfoAnnotation
extends SingleTargetAnnotation
, an Annotation
associated with one thing in a FIRRTL circuit:
/** An annotation that contains some string information */
case class InfoAnnotation(target: Target, info: String) extends SingleTargetAnnotation[Target] {
def duplicate(newTarget: Target) = this.copy(target = newTarget)
}
Second, define a Transform
that consumes this InfoAnnotation
.
This InfoTransform
simply reads all annotations, prints any InfoAnnotation
s it finds, and removes them.
/** A transform that reads InfoAnnotations and prints information about them */
class InfoTransform() extends Transform with DependencyAPIMigration {
override def prerequisites = firrtl.stage.Forms.HighForm
override def execute(state: CircuitState): CircuitState = {
println("Starting transform 'IdentityTransform'")
val annotationsx = state.annotations.flatMap{
case InfoAnnotation(a: CircuitTarget, info) =>
println(s" - Circuit '${a.serialize}' annotated with '$info'")
None
case InfoAnnotation(a: ModuleTarget, info) =>
println(s" - Module '${a.serialize}' annotated with '$info'")
None
case InfoAnnotation(a: InstanceTarget, info) =>
println(s" - Instance '${a.serialize}' annotated with '$info'")
None
case InfoAnnotation(a: ReferenceTarget, info) =>
println(s" - Component '${a.serialize} annotated with '$info''")
None
case a =>
Some(a)
}
state.copy(annotations = annotationsx)
}
}
Note:
inputForm
andoutputForm
will be deprecated in favor of a new dependency API that allows transforms to specify their dependencies more specifically than with circuit forms. Full backwards compatibility forinputForm
andoutputForm
will be maintained, however.
Create a Chisel API/Annotator
Now, define a Chisel API to annotate Chisel things with this InfoAnnotation
.
This is commonly referred to as an “annotator”.
Here, define an object, InfoAnnotator
with a method info
that generates InfoAnnotation
s.
This uses the chisel3.experimental.annotate
passed an anonymous ChiselAnnotation
object.
The need for this ChiselAnnotation
(which is different from an actual FIRRTL Annotation
) is that no FIRRTL circuit exists at the time the info
method is called.
This is delaying the generation of the InfoAnnotation
until the full circuit is available.
This annotator also mixes in the RunFirrtlTransform
trait (abstract in the transformClass
method) because this annotator, whenever used, should result in the FIRRTL compiler running the custom InfoTransform
.
@nowarn("msg=InstanceId")
object InfoAnnotator {
def info(component: InstanceId, info: String): Unit = {
annotate(new ChiselAnnotation with RunFirrtlTransform {
def toFirrtl: Annotation = InfoAnnotation(component.toTarget, info)
def transformClass = classOf[InfoTransform]
})
}
}
Note: there are a number of different approaches to writing an annotator. You could use a trait that you mix into a
Module
, an object (like is done above), or any other software approach. The specific choice of how you implement this is up to you!
Using the Chisel API
Now, we can use the method InfoAnnotation.info
to create annotations that associate strings with specific things in a FIRRTL circuit.
Below is a Chisel Module
, ModC
, where both the actual module is annotated as well as an output.
class ModC(widthC: Int) extends Module {
val io = IO(new Bundle {
val in = Input(UInt(widthC.W))
val out = Output(UInt(widthC.W))
})
io.out := io.in
InfoAnnotator.info(this, s"ModC($widthC)")
InfoAnnotator.info(io.out, s"ModC(ignore param)")
}
Running the Compilation
Compiling this circuit to Verilog will then result in the InfoTransform
running and the added println
s showing information about the components annotated.
import chisel3.stage.ChiselGeneratorAnnotation
import circt.stage.ChiselStage
(new ChiselStage).execute(Array.empty, Seq(ChiselGeneratorAnnotation(() => new ModC(4))))