scalachiselchiseltest

Providing a simulation model for a chisel blackbox


I am trying to simulate a system using chisel 3. The system has a blackbox that has a verilog. The verilog code is not behavioural, it simply instantiate a module that the synthesizer configures.I know the behaviour of the module and want to write a code in chisel to simulate the behaviour.

So basically how to extend a blackbox in chisel 3 with a behaviour that could be used in simulation.


Solution

  • Currently there is no built-in way to directly provide a behavioral model to substitute a blackbox when testing chisel3 code. However, there are some options that could work in your situation:

    Option 1: Use a Chisel SyncReadMem and substitute it with --repl-seq-mem

    For memories in particular, you can used the built-in SyncReadMem which will work fine in all simulation and even formal verification backends. Then for your "tape-out" / FPGA synthesis you replace all synchronous read memories with Verilog code that uses the Vendor provided memory. This flow is used by the open-source chipyard project for ASIC tapeout. You essentially need to pass the following flags to the firrtl compiler: --infer-rw --repl-seq-mem which will then automatically blackbox all SyncReadMem instances and generate description files for them. From these files you can write Verilog implementations using the vendor provided RTL. Hint: You can use --gen-mem-verilog to get a blueprint for the Verilog modules you need to implement in terms of the Xilinx block.

    Option 2: Use a Chisel SyncReadMem and try to get BRAM interference working

    You should be able to get Chisel SyncReadMem to be correctly inferred as BRAM by the Xilinx tool. This options is afaik used by the open-source firesim project to generate RTL for Xilinx FPGAs. The flags you would want to pass to the firrtl compiler are: --infer-rw --target:fpga

    Option 3: Use a generator parameter to choose between the behavioral and the synthesizable model

    This option is the most versatile, but also requires the most work. Here is a quick draft of what that may look like:

    import chisel3._
    
    
    class MemIO extends Bundle {
      val addr = Input(UInt(4.W))
      val doWrite = Input(Bool())
      val dataIn = Input(UInt(8.W))
      val dataOut = Output(UInt(8.W))
    }
    
    class MemBlackBox extends BlackBox {
      val io = IO(new MemIO)
    
      // ...
    }
    
    class MemBehavioral extends Module {
      val io = IO(new MemIO)
    
      // ...
      io <> DontCare // to make things compile
    }
    
    class Memory(simulation: Boolean) extends Module {
      val io = IO(new MemIO)
    
      if(simulation) {
        val inner = Module(new MemBehavioral) ; inner.io <> io
      } else {
        val inner = Module(new MemBlackBox) ; inner.io <> io
      }
      
    }
    
    
    
    
    val pretty = Array(
      "--emission-options", "disableMemRandomization,disableRegisterRandomization"
    )
    println("Behavioral")
    println(getVerilogString(new Memory(simulation = true), pretty))
    println("\n\nSynthesizable")
    println(getVerilogString(new Memory(simulation = false), pretty))
    

    You can see the output on scasti.