1
votes

I'd like to automate as much as possible the instantiation of an ILA directly from the Chisel code. This means instantiating a module that looks like this:

i_ila my_ila(
.clk(clock),
.probe0(a_signal_to_monitor),
.probe1(another_signal_to_monitor),
// and so on
);

I'm planning to store the signals that I want to monitor in a list of UInt so that at the end of module elaboration I can generate the instantiation code above, which I will copy/paste in the final Verilog code (or write a Python script that does that automatically).

First, is there a better way of doing this, perhaps at the level of FIRRTL?

Even if I go with this semi-manual approach, I need to know what would be the name of the signals in the final Verilog, which is not necessarily the name of the UInt vals in the code (and which, besides, I don't know how to get automatically without having to retype the name of the variable as a string somewhere). How can I get them?

1

1 Answers

3
votes

I'd like to provide a more complete example, but I wanted to make sure to at least write something up. This also needs to be fleshed out as a proper example/tutorial on the website.

FIRRTL has robust support for tracking names of signals across built-in and custom transformations. This is a case where the infrastructure is all there, but it's very much a power user API. In short, you can create FIRRTL Annotations that will track Targets. You can then emit custom metadata files or use the normal FIRRTL annotation file (try the CLI option -foaf / --output-annotation-file).

An example FIRRTL Annotation that has will emit a custom metadata file at the end of compilation:

// Example FIRRTL annotation with Custom serialization
// FIRRTL will track the name of this signal through compilation
case class MyMetadataAnno(target: ReferenceTarget)
    extends SingleTargetAnnotation[ReferenceTarget]
    with CustomFileEmission {
  def duplicate(n: ReferenceTarget) = this.copy(n)

  // API for serializing a custom metadata file
  // Note that multiple of this annotation will collide which is an error, not handled in this example
  protected def baseFileName(annotations: AnnotationSeq): String = "my_metadata"
  protected def suffix: Option[String] = Some(".txt")
  def getBytes: Iterable[Byte] =
    s"Annotated signal: ${target.serialize}".getBytes
}

The case class declaration and duplicate method are enough to track a single signal through compilation. The CustomFileEmission and related baseFileName, suffix, and getBytes methods define how to serialize my custom metadata file. As mentioned in the comment, as implemented in this example we can only have 1 instance of this MyMetadataAnno or they will try to write the same file which is an error. This can be handled by customizing the filename based on the Target, or writing a FIRRTL transform to aggregate multiple of this annotation into a single annotation.

We then need a way to create this annotation in Chisel:

  def markSignal[T <: Data](x: T): T = {
    annotate(new ChiselAnnotation {
      // We can't call .toTarget until end of Chisel elaboration
      // .toFirrtl is called by Chisel at the end of elaboration
      def toFirrtl = MyMetadataAnno(x.toTarget)
    })
    x
  }

Now all we need to do is use this simple API in our Chisel

// Simple example with a marked signal
class Foo extends MultiIOModule {
  val in = IO(Flipped(Decoupled(UInt(8.W))))
  val out = IO(Decoupled(UInt(8.W)))

  markSignal(out.valid)

  out <> in
}

This will result in writing the file my_metadata.txt to the target directory with the contents:

Annotated signal: ~Foo|Foo>out_valid

Note that this is special FIRRTL target syntax saying that out_valid is the annotated signal that lives in module Foo.

Complete code in an executable example: https://scastie.scala-lang.org/moEiIqZPTRCR5mLQNrV3zA