1
votes

I am using SyncReadMem() for sram behavioral simulation. With the generated Verilog by verilator, I hope to replace it with a commercial sram compiler compiled verilog such that I can do synthesis for the whole design including sram.

However, I noticed that the verilog emitted by SyncReadMem() is not with uniform IOs just like the sram emitted in rocketchip. I wonder how do we generate some sram verilog just like the rocketchip one, using chisel mem API like SyncReadMem()?

1

1 Answers

1
votes

You can use the Scala FIRRTL Compiler's "Replace Sequential Memories" pass to blackbox the memories. This is exactly what is happening with Rocket Chip.

Note that this is limited to only work if the memories have a single read port and a single write port and with read latency 1 and write latency 1.

As an example, consider the following 1r1w (one read, one write) SyncReadMem:

import chisel3._ 

class Foo extends MultiIOModule {
  val read = IO(new Bundle {
    val en = Input(Bool())
    val addr = Input(UInt(8.W))
    val data = Output(UInt(1.W))
  })
  val write = IO(new Bundle{
    val en = Input(Bool())
    val addr = Input(UInt(8.W))
    val data = Input(UInt(1.W))
  })
  val bar = SyncReadMem(256, UInt(1.W))
  
  read.data := bar.read(read.addr, read.en)
  when (write.en) {
    bar.write(write.addr, write.data)
  }
}

If you compile this with a request to run the replace sequential memories pass:

(new ChiselStage)
  .emitVerilog(new Foo, Array("--repl-seq-mem", "-c:Foo:-o:Foo.mem.conf"))

The arguments used there are -c:<circuit> where <circuit> is the name of the circuit you want to run on and -o:<mem-conf-file> is the name of a file to generate that will contain information (e.g., name, width, and depth) of the memories that were blackboxed.

You wind up with the memory blackboxed inside a new module bar called bar_ext:

module bar(
  input  [7:0] R0_addr,
  input        R0_en,
  input        R0_clk,
  output       R0_data,
  input  [7:0] W0_addr,
  input        W0_en,
  input        W0_clk,
  input        W0_data
);
  wire [7:0] bar_ext_R0_addr;
  wire  bar_ext_R0_en;
  wire  bar_ext_R0_clk;
  wire  bar_ext_R0_data;
  wire [7:0] bar_ext_W0_addr;
  wire  bar_ext_W0_en;
  wire  bar_ext_W0_clk;
  wire  bar_ext_W0_data;
  bar_ext bar_ext (
    .R0_addr(bar_ext_R0_addr),
    .R0_en(bar_ext_R0_en),
    .R0_clk(bar_ext_R0_clk),
    .R0_data(bar_ext_R0_data),
    .W0_addr(bar_ext_W0_addr),
    .W0_en(bar_ext_W0_en),
    .W0_clk(bar_ext_W0_clk),
    .W0_data(bar_ext_W0_data)
  );
  assign bar_ext_R0_clk = R0_clk;
  assign bar_ext_R0_en = R0_en;
  assign bar_ext_R0_addr = R0_addr;
  assign R0_data = bar_ext_R0_data;
  assign bar_ext_W0_clk = W0_clk;
  assign bar_ext_W0_en = W0_en;
  assign bar_ext_W0_addr = W0_addr;
  assign bar_ext_W0_data = W0_data;
endmodule

You can then run a memory compiler to consume the information in the memory configuration file and drop the output in place of bar_ext.