Is there a way to generate Verilog stripped of non-synthesis code?

I would be nice to be able to generate Verilog code all the non-synthesizable code stripped, such as removing all the asserts().

This would need to include a secondary round of cleaning up all unused signals and logic at that point, because there’s logic that exists only to generate the assert()s in Verilator.

The motivation is to improve readability and make it easier to correlate what’s synthesized to the Verilog code.

I don’t think we have a transformation that can strip all non-synthesizable code. There is a pass that can extract some non-synthesizable constructs into another module and create System Verilog bind statements, but I’m not sure if that is going to be useful to you: Passes - CIRCT

Great thinking, @mikeurbach. We do have the --extract-test-code pass available for people to use. (For background, this was a SiFive-internal thing with SFC that we just put into CIRCT.)

Example:

circuit Bar:
  module Bar:
    input clock: Clock
    input reset: UInt<1>
    input a: UInt<8>
    output b: UInt<8>

    assert(clock, reset, eq(a, UInt<8>(0)), "hello")
    b <= a

If you compile this with firtool --strip-debug-info --extract-test-code -o build/ --split-verilog Bar.fir you will get the following directory structure (tree build/):

build
├── Bar.sv
├── Bar_assert.sv
├── bindfile
└── filelist.f

This will extract all the test code (asserts, assumes, and covers) into separate files that can be bound back in using the provided bindfile.

Bar.sv:

module Bar(
  input        clock, reset,
  input  [7:0] a,
  output [7:0] b);

  /* This instance is elsewhere emitted as a bind statement.
    Bar_assert InvisibleBind_assert (
      .a     (a),
      .reset (reset),
      .clock (clock)
    );
  */
  assign b = a;
endmodule

Bar_assert.sv:

module Bar_assert(
  input [7:0] a,
  input       reset, clock);

  always @(posedge clock) begin
    if (a == 8'h0)
      assert(reset) else $error("hello");
  end // always @(posedge)
endmodule

bindfile:

bind Bar Bar_assert InvisibleBind_assert (
  .a     (a),
  .reset (reset),
  .clock (clock)
);

Nice!

At first try, that seemed to work.

The only non-synthesizable remnants I see in the first file I looked at are these at the top of the file:

...
`ifdef RANDOMIZE_MEM_INIT
  `define RANDOMIZE
`endif
...

and some in the middle:

`ifndef SYNTHESIS
    `ifdef RANDOMIZE_REG_INIT
      reg [31:0] _RANDOM;
      reg [31:0] _RANDOM_5;
      reg [31:0] _RANDOM_6;

    `endif
    initial begin
      `INIT_RANDOM_PROLOG_
      `ifdef RANDOMIZE_REG_INIT
        _RANDOM = {`RANDOM};
        _RANDOM_5 = {`RANDOM};
        outputRegister = {_RANDOM_5, _RANDOM};
        _RANDOM_6 = {`RANDOM};
      `endif
    end // initial
  `endif


The code looks much more readable to me… The immediate thing is that strikes me now… and this is a Chisel observation, but I thought I’d mention it as it does need some sort of support in firtool.

It would be nice if I could have Chisel comments appear in the generated Verilog…

Does .fir have a construct to inject comments into the Verilog code?

The actual .fir does not. DocStringAnnotation/AttributeAnnotation do exist, but those are not widely used nor do they capture Scala-level comments.

I’ve thought that this might eventually be a good idea and there is some existing infrastructure on the Chisel side to do these types of things. The Chisel naming plugin could, I think, capture this.

Lower-level dialects, e.g., HW modules, do support a comment which I originally wanted to roll out to all operations as well as native support for Verilog attributes. E.g., [HW][SV][ExportVerilog] Add Comment to HWModuleOp, Verilog Emission by seldridge · Pull Request #2066 · llvm/circt · GitHub

1 Like

Thanks!

Hmm… I see now that the generated Verilog code is not valid. There’s lots of things that aren’t connected. I was considering saying nothing since I don’t have an example ready, but I mention it here in the fine print for those that happen to be reading my comments and are wondering what’s next for my testing…

If you’re referring to lots of “dead wires”, that may be fixed as of yesterday via [SV] Use Better Named Wires When Possible by seldridge · Pull Request #2821 · llvm/circt · GitHub. The current behavior is to guarantee that any Chisel val with a non-underscore name will show up as a wire in the Verilog. (This was done to make Chisel to Verilog debugging easier as this now guarantees that any Chisel “thing” is viewable in your waveform.) If you prefer more aggressive optimizations you can use --disable-name-preservation. Dead wires can still show up for situations where the Chisel val is dead via constant propagation, though.

No, that’s not it. I need to investigate further, but as far as I can tell there’s a wire that should be connected that isn’t. The next step would be to try to compile everything with Verilator. Even if I should bottom out on this particular problem(hopefully a mistake on my part), I still need to run all the tests we have…