Passing Chisel information to Firrtl and emit debug info for the Tywaves waveform viewer (original) (raw)
April 15, 2024, 12:59pm 1
@fabianschuiki I started to implement the new (final) version of my backend for tywaves. As you suggested me during the CIRCT meeting, I am trying to emit the high-level information such as types of the objects together with Firrtl, so firtool would be able to emit a new file for debug information rather than HGLDD.
I think I can exploit the FIRRTL annotations FIRRTL Annotations - CIRCT to make firtool able to access the needed information.
Yeah that sounds great! You might also be able to use FIRRTL intrinsics to do this more accurately, since annotations are a somewhat weird mechanism.
rameloni April 16, 2024, 7:22pm 3
Is it difficult to process annotation in firtool? I found emittin them from Chisel elaboration side pretty easy. In what sense may they be weird?
Are intrinsics able to associate arbitrary information with arbitrary firrtl targets similarly to annotations? I don’t know much about them, I didn’t look into them and I have only found them mentioned in a small paragraph here: https://github.com/chipsalliance/firrtl-spec/releases/latest/download/spec.pdf (section 5.5).
No, you should be able to tweak the LowerAnnotations
pass in CIRCT (part of the FIRRTL dialect), which takes the annotations and applies them to the IR. You’ll probably want to take your debug info annotations and convert them to corresponding debug ops. Similar to what MaterializeDebugInfo
(I think that’s the name) does in the FIRRTL dialect.
rameloni April 25, 2024, 12:28pm 5
I made some progress about passing chisel information to firtool. I’m now able to annotate a whole circuit and I already support the majority of type features. Pass Chisel information to firtool to generate debug information for the Tywaves project · Issue #4015 · chipsalliance/chisel · GitHub
I want to move now on to the next step, consuming this annotation in firtool. I’m waiting for my university’s server access to compile circt/llvm since the project is too large to fit on my laptop.
In the meantime, I started inspecting the code (starting from LoweAnnotations
) on my laptop. I’m trying to use Clion as IDE, but I found that it doesn’t index properly files inside llvm
, so I can’t really inspect sources from that.
@fabianschuiki do you have any guidelines on what specific files to look at for consuming the annotation?
LowerAnnotations
is a great starting point. That is where you will have to add a bit of code that makes sense of your annotations. MaterializeDebugInfo
in FIRRTL is also a good thing to look at to see how you can create operations from the Debug dialect (dbg.*
) to track debug information. That pass simply looks at the FIRRTL ports and types and creates corresponding tracker ops, such that the FIRRTL perspective is preserved throughout the pipeline. You’ll want to take your annotations and create the corresponding debug ops for them.
Is it difficult to process annotation in firtool? I found emittin them from Chisel elaboration side pretty easy. In what sense may they be weird?
It’s more that annotations are generally considered a “bad” design in retrospect.
Annotations are typically encoding one of several things:
- They are shadow FIRRTL operations.
- They are missing first-class language features of existing operations. (They are non-discardable attributes.)
- They are discardable attributes on operations.
In almost all situations, annotations have been used for (1) or (2). E.g., DontTouchAnnotation
would be better as an attribute on an operation or broken into several operations based on usage (a probe type and an optimization barrier operation).
That said, your narrow use case may actually be (3) which is reasonable! The main problem with (3) is that they should be actually co-located with the operation as opposed to going through the whole “target” syntax. E.g., you cannot annotate an expression. However, it is reasonable that an expression has discardable attributes on it. Additionally, annotations must be known to the compiler or known via the pass plugin extension mechanism. Put differently, annotations effectively become part of the spec and something that any FIRRTL compiler must implement. This doesn’t fit well with a notion of “discardable attribute”.
There is likely a “discardable attribute” syntax that will replace (3).
An alternative might also be to use intrinsics from the beginning, since that corresponds more closely to what happens in the compiler with the debug dialect ops. That would allow you to make Chisel emit the debug variables, structs, and arrays to convey the original Chisel type to firtool.
To use intrinsics would I need to generate intrisic modules from Chisel and storing the metadata into parameter fields?
I’ve never thought to use intrinsics, since I only found this little paragraph about them. I didn’t know what they were exactly. I haven’t look into them.
So, basing on what you said, using intrinsic may simplify the process in firtool, although increasing the complexity (maybe idk) on chisel side. Am I correct?
That sounds about right . Although, @dtzWill has done a really nice piece of work together with @uenoku to make intrinsics easier to use. If you look through the Chisel source code, the
LTLIntrinsics.scala
file contains a few pointers on how you can use the (old style) intrinsic modules. @dtzWill probably can point you to how you can use the newer expression-like intrinsics.
rameloni April 29, 2024, 8:26pm 11
I’ve inspected a bit LowerAnnotations
, MaterializeDebugInfo
and EmitHGLDD
a bit today, and I think now I know what you mean with “annotation are weird”. @fabianschuiki
I started playing with firtool and implementing the apply
method of the annotation. I immediately found finding a good way to convert targets to proper operations here would be difficult. I’m not completely sure but it seems there is no direct relationship: for example, a target could be an input port but if I execute target.ref.getOp()
, it returns firrtl.module
.
Also, I didn’t find an annotation similar to what I’d like to implement. Probably, it’s because my use case doesn’t fit the usage of annotations in firtool, since a “discardable attribute” @seldridge.
I initially thought of annotations, since the target mechanism seemed a good approach to uniquely identify circuit components (modules, wires, reg etc…). But if in firtool these targets should be translated to operations and there’s no actual way to do so, it’s probably better to use intrinsics as you recommended.
I started to take a look to them, and I’ve been thinking to implement something similar to this (where each signal is connected to an intrinsic module):
class MyCustomBundle(val size: Int) extends Bundle {
val a = UInt(size.W)
val b = Bool()
}
class TopCircuitWithIntrinsics extends RawModule {
val b = IO(Output(new MyCustomBundle(8)))
}
intmodule createIntrinsic_Anon :
input source : { a : UInt<8>, b : UInt<1>}
output dbgVal : { a : UInt<8>, b : UInt<1>}
intrinsic = TywavesIntrinsic
parameter PARAMS = "{\"name\":\"size\", \"type\":\"Int\", \"value\":\"8\"}"
parameter TYPENAME = "TopCircuitWithIntrinsics.b: IO[MyCustomBundle]"
intmodule createIntrinsic_Anon_1 :
input source : UInt<1>
output dbgVal : UInt<1>
intrinsic = TywavesIntrinsic
parameter PARAMS = ""
parameter TYPENAME = "TopCircuitWithIntrinsics.b.b: IO[Bool]"
intmodule createIntrinsic_Anon_2 :
input source : UInt<8>
output dbgVal : UInt<8>
intrinsic = TywavesIntrinsic
parameter PARAMS = ""
parameter TYPENAME = "TopCircuitWithIntrinsics.b.a: IO[UInt<8>]"
public module TopCircuitWithIntrinsics :
output b : { a : UInt<8>, b : UInt<1>}
inst intrinsic of createIntrinsic_Anon
connect intrinsic.source.b, b.b
connect intrinsic.source.a, b.a
inst intrinsic_1 of createIntrinsic_Anon_1
connect intrinsic_1.source, b.b
inst intrinsic_2 of createIntrinsic_Anon_2
connect intrinsic_2.source, b.a
At the moment I didn’t check how intrinsics are handled by firtool and how they can transformed to dbg operations. I only check LTL
intrinsic from scala code. Tomorrow, I think I’ll check it in firtool as well.
That’s a cool example. It’s nice to see that you can get the constructor arguments for MyCustomBundle
into your intrinsics. The way I would expect this to look like expressed in the debug dialect is something like the following:
%0 = firrtl.constant 8
%1 = firrtl.extractfield %b[a]
%2 = firrtl.extractfield %b[b]
%3 = dbg.struct {"size": %0, "a": %1, "b": %2}
dbg.variable "b", %3 {typeName = "MyCustomBundle"}
There is no real support for that typeName
attribute in HGLDD emission yet, but this is definitely something we should add!
The idea behind the debug dialect is to be able to describe the structure of the types in the high-level language. In this case for example, it would be great if things like constructor argument values are part of that structured description, like with the “size” field above.
Yes indeed. Also, the parameters of a constructor can be any scala type, including classes.
But, I can’t access the value of parameters that are not fields (val
), only the name and type.
I’m also trying to extract type parameters of a constructor such that:
class A[T, U](val x: T) extends Bundle
val a = new A[Int, Char](10) // typeName = A[Int, Char], params = "{"name":"x", "type":"Int", "value":"10"}"