Demo backend of the Tywaves project: a type based waveform viewer for Chisel and Tydi-Chisel circuits.
This repo contains functions that parse the information of a Chisel circuit and the debug info emitted by the firtool compiler, simulate a circuit using ChiselSim and combine all the information and pass them to the surfer-tywaves-demo frontend for visualization (an extension of the Surfer waveform viewer written in Rust to support Chisel constructs).
Since it is a demo project, it has been tested and developed for a restricted set of examples (it may not work with all circuits). In the features section, you can find the already supported features. This demo served only as an example to show how the waveform viewer can potentially look like and for initial feedback and suggestions from the community.
If you are interested in using the tool and have any feedback on its implementation, please open an issue or contact me.
Starting from this demo project, a better implementation and integration in the Chisel/Firrtl infrastructure will be developed with the aim of solving the issues addressed here.
Do not use Verilog / System Verilog reserved keywords in your Chisel circuit (i.e.
wire
,reg
). In that case,firtool
will change thevar_name
(which should bewire
) field of the emitted HGLDD to get a legal name as explained in the comments here. Thus, it is not possible to match Chisel/FIRRTL with thevar_name
in HGLDD.HGLDD is a file format emitted for other existing tools based on verilog simulations and verilog keywords. I am using it temporarily and in the future firtool will be able to emit a new file format more consistent with tywaves.
You can run make all
to install all the pre-requisites and this library.
Prerequisite: Install surfer-tywaves-demo
The makefile contains a rule to clone the frontend repository, build and install it.
make install-surfer-tywaves
make clean # To remove the cloned repository
The frontend will be installed as surfer-tywaves
executable.
make install-chisel-fork # TEMPORARY NEEDED: Install the chisel fork with the needed changes in the development branch
make install-tywaves-backend
Once published locally, the tywaves-demo-backend
can be used by adding the following line to the build.sbt
file:
libraryDependencies += "com.github.rameloni" %% "tywaves-demo-backend" % "0.1.0-SNAPSHOT"
The TywavesBackend
provides 2 simulators with functionalities to simulate a circuit
through svsim, emit VCD
traces and generate the symbol table for the surfer-tywaves waveform viewer itself automatically:
- ParametricSimulator: provides some generic features such as VCD trace emission, name the trace file, pass additional arguments to firtool before simulation, save the workspace of svsim
- TywavesSimulator: it extends the parametric simulator in order to generate the symbol table for Tywaves waveform viewer and provides an option to launch the waveform viewer after the simulation
While
TywavesSimulator
is a central part of the Tywaves project and its functionalities are not fully supported yet, theParametricSimulator
is able to simulate any Chisel circuit. In case you need to simulate a circuit that is not supported byTywavesSimulator
, you can useParametricSimulator
to emit a VCD trace (however, you will not have a "chisel" representation of the signals in the waveform viewer).If you want to try the functionalities of
Tywaves
thenTywavesSimulator
is the right choice. But, if you want to visualize waveforms of any chisel circuit without issues related to features not supported yet, you should make use ofParametricSimulator
.
The following example shows how it is possible also to:
- Enable the trace of the simulation
- Set the name of the simulation (it will be used to create a folder with a user defined name for the traces and workspace of svsim)
- Launch the waveform viewer after the simulation
- Use tywaves and expect API to test the circuit
import tywaves.simulator.TywavesSimulator._
import tywaves.simulator.simulatorSettings._
import org.scalatest.flatspec.AnyFlatSpec
class GCDTest extends AnyFunSpec with Matchers {
describe("TywavesSimulator") {
it("runs GCD correctly") {
simulate(new GCD(), Seq(VcdTrace, WithTywavesWaveforms(true)), simName = "runs_GCD_correctly_launch_tywaves") {
gcd =>
gcd.io.a.poke(24.U)
gcd.io.b.poke(36.U)
gcd.io.loadValues.poke(1.B)
gcd.clock.step()
gcd.io.loadValues.poke(0.B)
gcd.clock.stepUntil(sentinelPort = gcd.io.resultIsValid, sentinelValue = 1, maxCycles = 10)
gcd.io.resultIsValid.expect(true.B)
gcd.io.result.expect(12)
}
}
}
}
import tywaves.simulator.ParametricSimulator._
import tywaves.simulator.simulatorSettings._
import org.scalatest.flatspec.AnyFlatSpec
class GCDTest extends AnyFunSpec with Matchers {
describe("ParametricSimulator") {
it("runs GCD correctly") {
simulate(new GCD(), Seq(VcdTrace, SaveWorkdirFile("GCD_parametricSimulator_workdir")), simName = "runs_GCD_correctly") {
gcd =>
gcd.io.a.poke(24.U)
gcd.io.b.poke(36.U)
gcd.io.loadValues.poke(1.B)
gcd.clock.step()
gcd.io.loadValues.poke(0.B)
gcd.clock.stepUntil(sentinelPort = gcd.io.resultIsValid, sentinelValue = 1, maxCycles = 10)
gcd.io.resultIsValid.expect(true.B)
gcd.io.result.expect(12)
}
}
}
}
The following images show the classic and tywaves waveform visualization of the GCD module. It is possible to see that the left picture does not provide any information about Chisel level types and hierarchy.
class GCD extends Module {
val io = IO(new Bundle {
val a = Input(UInt(32.W))
val b = Input(UInt(32.W))
val loadValues = Input(Bool())
val result = Output(UInt(32.W))
val resultIsValid = Output(Bool())
})
val x = Reg(UInt(32.W))
val y = Reg(UInt(32.W))
when(x > y)(x := x -% y).otherwise(y := y -% x)
when(io.loadValues) {
x := io.a
y := io.b
}
io.result := x
io.resultIsValid := y === 0.U
}
Only VCD loaded | Tywaves (VCD + symbol table) |
---|---|
![]() |
![]() |
- Parse and map Chisel/FIRRTL/Verilog circuits
- Emit VCD traces from the simulator (both with and without underscores in the signal names)
- Automatically generate the symbol table for the waveform viewer
- Dump Chisel types in the final symbol table
- Represent hierarchical structures of bundles
- Represent vectors
- Represent enums
- Represent hierarchical modules
- Generic submodules (all different types of modules)
- Variants of the same module (i.e. parametric module)
- Instances of the same module
- For loops code generation
- Reg with init
The following diagram shows the main components of the demo project and how they interact with each other.
This backend retrieves, parses and finally maps together the Intermediate Representations (IR) of the Chisel, Firrtl and debug info emitted by the firtool (HGLDD) to output a symbol table that can be used by the frontend to display the waveform. It aims to map each high level signal (Chisel) to the low level signal (in System Verilog and in the VCD/FST trace) and vice versa. In this way it would be possible to access a variable/signal value from any waveform viewer able to support a multi-level typed view. For this demo I managed to do so by:
- parsing Chisel IR, Firrtl IR, and the HGLDD (debug info emitted by firtool to link verilog and firrtl)
- retrieving and joining signals together by identifying IDs (shared between IRs) based on signal names and source locators
- emitting the symbol table suited for the waveform viewer
However, this approach has some issues associated with the IRs parsing and mapping. The drawbacks section explains them and suggests a solution to them that I will implement.
Considering only Firrtl, using the HGLDD file would be enough, but it does provide only information about FIRRTL-to-SystemVerilog mapping, so it does not contain user types information.
In this small example if I use only HGLDD I would be able to see that they are both bundles, but it is not possible to
see that they are actually MyFloat
and IntCoordinates
respectively. Also Bool
, UInt
, SInt
would not be
retrieved from HGLDD/FIRRTL only. From here the reason to use Chisel IR to get the user types information.
class MyFloat extends Bundle {
val sign = Bool()
val exponent = UInt(8.W)
val significand = UInt(23.W)
}
class IntCoordinates extends Bundle {
val x = SInt(32.W)
val y = SInt(32.W)
}
The current approach accesses and uses the Chisel IR which is private to the package chisel3
since it is part of the
chisel internals, and it is not meant to be used by external tools. It is not stable, it may change and this may
compromise compatibility with future chisel versions. Using Chisel IR in this way will make relatively hard to maintain
this project for future Chisel versions.
Mapping different IRs together requires to find common characteristics between them. Different IRs have different
information, reserved keywords and syntax. This can lead to different ways to represent variables and changes to their
names. For example, a variable x
child of a bundle b
may be represented with b_x
in Verilog, but it is not
guaranteed when there are some conflicting variables.
Therefore, this methodology requires to find an ID for each signal which is unique within the same IR, but it
is shared between IRs. Finding an ID with these characteristics is not trivial at all since it really depends on the
characteristics emitted during the different elaboration/compilation phases. These information, even if available,
should remain consistent between different versions of the tools but this may not be guaranteed.
Currently, the IDs (ElId
) used by the tool to join the different IRs are based on source locators (where a variable is
declared in a source module, not instance) contained in the IRs and names of the variables. However, this may cause
issues when signals and modules are generated using for loops. This explains why multiple instances of the same module
are not supported yet (the source locators of internal signals of multiple instances of the same module are the same).
Furthermore, finding the original chisel name from HGLDD requires manipulation based on the transform function used.
case class ElId(source: String, row: Int, col: Int, name: String)
Finally, this tool relies on HGLDD which is a file realized for Synopsys tools and its format is not stable since it mainly depends on what Synopsys will need in the future.
These issues reveal the need for a more stable and consistent way to map different IRs together. Parsing the IRs
externally and joining them basing on a "guessed and unstable" ID is not an optimal solution (guessed and unstable since
it depends on internal characteristics of compilers).
Therefore, I planned to "integrate" a functionality to directly transfer Chisel information to FIRRTL. In this way,
the firtool
would be able to access all the needed information for surfer-tywaves-demo
to render the signals.
This would also allow to simplify the process that tywaves-demo-backend
currently does to generate the symbol table,
improving performances. And it may extend the support to other languages/dialects in
the CIRCT ecosystem.
Despite the drawbacks, this demo successfully shows the potential of the Tywaves project and the feasibility of a Typed Waveform Viewer for Chisel circuits.