Chisel provides facilities for creating both read only and read/write memories.

ROM

Users can define read only memories with a Vec:

VecInit(inits: Seq[T])
VecInit(elt0: T, elts: T*)

where inits is a sequence of initial Data literals that initialize the ROM. For example, users cancreate a small ROM initialized to 1, 2, 4, 8 and loop through all values using a counter as an address generator as follows:

val m = VecInit(Array(1.U, 2.U, 4.U, 8.U))
val r = m(counter(m.length.U))

We can create an n value sine lookup table using a ROM initialized as follows:

def sinTable(amp: Double, n: Int) = {
  val times =
    (0 until n).map(i => (i*2*Pi)/(n.toDouble-1) - Pi)
  val inits =
    times.map(t => round(amp * sin(t)).asSInt(32.W))
  VecInit(inits)
}
def sinWave(amp: Double, n: Int) =
  sinTable(amp, n)(counter(n.U))

where amp is used to scale the fixpoint values stored in the ROM.

Mem

Memories are given special treatment in Chisel since hardware implementations of memory vary greatly. For example, FPGA memories are instantiated quite differently from ASIC memories. Chisel defines a memory abstraction that can map to either simple Verilog behavioural descriptions or to instances of memory modules that are available from external memory generators provided by foundry or IP vendors.

Chisel supports random-access memories via the Mem construct. Writes to Mems are combinational/asynchronous-read, sequential/synchronous-write. These Mems will likely be synthesized to register banks, since most SRAMs in modern technologies (FPGA, ASIC) tend to no longer support combinational (asynchronous) reads.

Chisel also has a construct called SyncReadMem for sequential/synchronous-read, sequential/synchronous-write memories. These SyncReadMems will likely be synthesized to technology SRAMs (as opposed to register banks).

Ports into Mems are created by applying a UInt index. A 1024-entry register file with one write port and one sequential/synchronous read port might be expressed as follows:

val width:Int = 32
val addr = Wire(UInt(width.W))
val dataIn = Wire(UInt(width.W))
val dataOut = Wire(UInt(width.W))
val enable = Wire(Bool())

// assign data...

// Create a synchronous-read, synchronous-write memory (like in FPGAs).
val mem = SyncReadMem(1024, UInt(width.W))
// Create one write port and one read port.
mem.write(addr, dataIn)
dataOut := mem.read(addr, enable)

Creating an asynchronous-read version of the above simply involves replacing SyncReadMem with just Mem.

Chisel can also infer other features such as single ports and masks directly with Mem.

Single-ported SRAMs can be inferred when the read and write conditions are mutually exclusive in the same when chain:

val mem = SyncReadMem(2048, UInt(32.W))
when (write) { mem.write(addr, dataIn); dataOut := DontCare }
.otherwise { dataOut := mem.read(addr, read) }

(The DontCare is there to make Chisel’s unconnected wire detection aware that reading while writing is undefined.)

If the same Mem address is both written and sequentially read on the same clock edge, or if a sequential read enable is cleared, then the read data is undefined.

Masks

Chisel memories also support write masks for subword writes. Chisel will infer masks if the data type of the memory is a vector. To infer a mask, specify the mask argument of the write function which creates write ports. A given masked length is written if the corresponding mask bit is set. For example, in the example below, if the 0th bit of mask is true, it will write the lower 8 bits of the corresponding address.

val dataOut = Wire(Vec(4, UInt(8.W)))
val dataIn = Wire(Vec(4, UInt(8.W)))
val mask = Wire(Vec(4, Bool()))
val enable = Wire(Bool())
val readAddr = Wire(UInt(10.W))
val writeAddr = Wire(UInt(10.W))

// ... assign values ...

// Create a 32-bit wide memory that is byte-masked.
val mem = SyncReadMem(1024, Vec(4, UInt(8.W)))
// Create one masked write port and one read port.
mem.write(writeAddr, dataIn, mask)
dataOut := mem.read(readAddr, enable)