I took the latest firtool from master for a quick spin.
I see that the .sv files from CIRCT can be quite a bit longer than the .v files from Chisel.
I was wondering if this difference has to do with Chisel optimizing things in during Verilog generation or whether it is LLVM CIRCT that makes this choice.
Since GEN_2719 below doesn’t exist in the .fir file, I’m guessing these are choices done by LLVM CIRCT. The .fir file does the port assignment without temporary values and at a much higher level, with a concept of arrays and bundles.
An example I narrowed down.
Here we can see that in CIRCT .sv file the variable “foo” is never used:
This is intentional behavior, but you can turn it off with --disable-name-preservation.
There was a hard ask from users to “preserve all val names from Chisel”. This is temporarily implemented as a hack where each “named” thing (a FIRRTL operation that begins with a non-underscore character) will result in a dead wire tap with the same name. The original wire is renamed _<name> and will likely be optimized away. The intent here is to provide a seamless Chisel debug experience in a waveform viewer where users can see signals that have the exact names that they wrote in Chisel. The Scala FIRRTL Compiler does not have this property, but it has far fewer optimizations/canonicalizations so users trained themselves to rely on val preservation.
In the future we will change this behavior to not emit the dead wire and instead make this an option to preserve any named signal. The signal will always exist in the output Verilog, but it may be dead if optimizations make it so. However, it will always exist and users can rely on that behavior (unless they turn it off).
The line count of LLVM CIRCT is now 40% less than that of Chisel for our code now. That sounds too good to be true, so I guess I need to actually try it out…
Vector lookup is completely different with LLVM CIRCT, no if statements:
We did eventually add a FIRRTL Dialect RemoveUnusedPorts pass (#2522) which runs after inter-module constant prop (IMCP). However, the “preserve all val names” may work against this in some situations.
A simple example of this all working is:
circuit Foo :
module Bar :
input a: UInt<1>
output b: UInt<1>
wire w: UInt<1>
w <= a
b <= w
module Foo :
input a: UInt<1>
output b: UInt<1>
inst bar of Bar
bar.a <= xor(a, a)
b <= bar.b
firtool Foo.fir produces:
module Bar(); // Foo.fir:2:10
wire w; // Foo.fir:6:5
assign w = 1'h0; // Foo.fir:2:10, :6:5
endmodule
module Foo( // Foo.fir:10:10
input a,
output b);
Bar bar (); // Foo.fir:15:5
assign b = 1'h0; // Foo.fir:10:10, :16:14
endmodule
If the ports are made dead after the circuit is out of FIRRTL Dialect, then the port may hang around. (Meaning, we probably need module port elimination inside later dialects.)