chisel

Exposing Simulation-only behavior in Chisel3


I want to expose certain signals only during simulation (for performance monitoring purposes, etc...) that can be used during the debug process for various purposes. I could use the dontTouch annotation to ensure that the counter, which is necessarily eliminated by DCE, is preserved in simulation and visible in the final waveform, but this would mean that the component would also be preserved during synthesis.

Chisel3 generates 'ifndef SYNTHESIS verilog macros for cases of VerificationStatement(e.g. $printf, $stop, ...). Is there a way for a user to more generally use the 'ifndef SYNTHESIS macro within their designs or would I need to write my own FIRRTL/CIRCT annotations/passes to provide this functionality?


Solution

  • An alternative approach would be to probe the internals of the design from the testbench. This uses a new feature of Chisel 6 (BoringUtils.tapAndRead) to create cross-module references.

    Here's a small example:

    //> using scala "2.13.10"
    //> using repository "https://s01.oss.sonatype.org/content/repositories/snapshots"
    //> using lib "org.chipsalliance::chisel::6.0.0-M1+3-35860474-SNAPSHOT"
    //> using plugin "org.chipsalliance:::chisel-plugin::6.0.0-M1+3-35860474-SNAPSHOT"
    //> using options "-unchecked", "-deprecation", "-language:reflectiveCalls", "-feature", "-Xcheckinit", "-Ywarn-dead-code", "-Ywarn-unused", "-Ymacro-annotations"
    
    import chisel3._
    import chisel3.util.experimental.BoringUtils
    import circt.stage.ChiselStage
    
    class Bar extends RawModule {
      val b = WireInit(Bool(), DontCare)
      dontTouch(b)
    }
    
    class Foo extends RawModule {
      val bar = Module(new Bar)
    
      val a = WireInit(Bool(), BoringUtils.tapAndRead(bar.b))
      dontTouch(a)
    }
    
    object Main extends App {
      println(ChiselStage.emitSystemVerilog(new Foo))
    }
    

    Running this (scala-cli Foo.scala) produces the following Verilog:

    module Bar();   
      wire b = 1'h0;        
    endmodule
    
    module Foo();   
      wire a = Foo.bar.b;   
      Bar bar ();   
    endmodule
    

    You could also do this by creating a "debug module" inside Bar with all the signals you want. This module can then be excluded from synthesis. There is no great mechanism to exclude a module from synthesis (either via emitting it as a Verilog bind to a specific bind file or with ifdefs). Support for instantiating a module via a bind is expected soon-ish.