Skip to content

Commit

Permalink
SRAM API: Add a parameter to initialize the memory (#3364)
Browse files Browse the repository at this point in the history
  • Loading branch information
jared-barocsi committed Jun 23, 2023
1 parent 7bd37e9 commit 7c5bb18
Showing 1 changed file with 154 additions and 65 deletions.
219 changes: 154 additions & 65 deletions src/main/scala/chisel3/util/SRAM.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import chisel3._
import chisel3.internal.Builder
import chisel3.experimental.SourceInfo
import chisel3.internal.sourceinfo.{MemTransform, SourceInfoTransform}
import chisel3.util.experimental.loadMemoryFromFileInline
import firrtl.annotations.MemoryLoadFileType
import scala.language.reflectiveCalls
import scala.language.experimental.macros

Expand Down Expand Up @@ -104,6 +106,30 @@ class SRAMInterface[T <: Data](
Vec(numReadwritePorts, new MemoryReadWritePort(tpe, addrWidth, masked))
}

/** A memory file with which to preload an [[SRAM]]
*
* See concrete subclasses [[BinaryMemoryFile]] and [[HexMemoryFile]]
*/
sealed abstract class MemoryFile(private[chisel3] val fileType: MemoryLoadFileType) {

/** The path to the memory contents file */
val path: String
}

/** A binary memory file to preload an [[SRAM]] with, represented by a filesystem path. This will annotate
* the inner [[SyncReadMem]] with `loadMemoryFromFile` using `MemoryLoadFileType.Binary` as the file type.
*
* @param path The path to the binary file
*/
case class BinaryMemoryFile(path: String) extends MemoryFile(MemoryLoadFileType.Binary)

/** A hex memory file to preload an [[SRAM]] with, represented by a filesystem path. This will annotate
* the inner [[SyncReadMem]] with `loadMemoryFromFile` using `MemoryLoadFileType.Hex` as the file type.
*
* @param path The path to the hex file
*/
case class HexMemoryFile(path: String) extends MemoryFile(MemoryLoadFileType.Hex)

object SRAM {

/** Generates a [[SyncReadMem]] within the current module, connected to an explicit number
Expand All @@ -130,7 +156,53 @@ object SRAM {
)(
implicit sourceInfo: SourceInfo
): SRAMInterface[T] =
memInterface_impl(size, tpe)(numReadPorts, numWritePorts, numReadwritePorts, Builder.forcedClock)
memInterface_impl(size, tpe)(
numReadPorts,
numWritePorts,
numReadwritePorts,
Builder.forcedClock,
None
)(
None,
sourceInfo
)

/** Generates a [[SyncReadMem]] within the current module, connected to an explicit number
* of read, write, and read/write ports. This SRAM abstraction has both read and write capabilities: that is,
* it contains at least one read accessor (a read-only or read-write port), and at least one write accessor
* (a write-only or read-write port).
*
* @param size The desired size of the inner `SyncReadMem`
* @tparam T The data type of the memory element
* @param numReadPorts The number of desired read ports >= 0, and (numReadPorts + numReadwritePorts) > 0
* @param numWritePorts The number of desired write ports >= 0, and (numWritePorts + numReadwritePorts) > 0
* @param numReadwritePorts The number of desired read/write ports >= 0, and the above two conditions must hold
* @param memoryFile A memory file whose path is emitted as Verilog directives to initialize the inner `SyncReadMem`
*
* @return A new `SRAMInterface` wire containing the control signals for each instantiated port
* @note This does *not* return the `SyncReadMem` itself, you must interact with it using the returned bundle
* @note Read-only memories (R >= 1, W === 0, RW === 0) and write-only memories (R === 0, W >= 1, RW === 0) are not supported by this API, and will result in an error if declared.
*/
def apply[T <: Data](
size: BigInt,
tpe: T,
numReadPorts: Int,
numWritePorts: Int,
numReadwritePorts: Int,
memoryFile: MemoryFile
)(
implicit sourceInfo: SourceInfo
): SRAMInterface[T] =
memInterface_impl(size, tpe)(
numReadPorts,
numWritePorts,
numReadwritePorts,
Builder.forcedClock,
Some(memoryFile)
)(
None,
sourceInfo
)

/** Generates a [[SyncReadMem]] within the current module, connected to an explicit number
* of read, write, and read/write ports, with masking capability on all write and read/write ports.
Expand All @@ -157,68 +229,68 @@ object SRAM {
implicit evidence: T <:< Vec[_],
sourceInfo: SourceInfo
): SRAMInterface[T] =
masked_memInterface_impl(size, tpe)(numReadPorts, numWritePorts, numReadwritePorts, Builder.forcedClock)
masked_memInterface_impl(size, tpe)(
numReadPorts,
numWritePorts,
numReadwritePorts,
Builder.forcedClock,
None
)(
Some(evidence),
sourceInfo
)

private def memInterface_impl[T <: Data](
/** Generates a [[SyncReadMem]] within the current module, connected to an explicit number
* of read, write, and read/write ports, with masking capability on all write and read/write ports.
* This SRAM abstraction has both read and write capabilities: that is, it contains at least one read
* accessor (a read-only or read-write port), and at least one write accessor (a write-only or read-write port).
*
* @param size The desired size of the inner `SyncReadMem`
* @tparam T The data type of the memory element
* @param numReadPorts The number of desired read ports >= 0, and (numReadPorts + numReadwritePorts) > 0
* @param numWritePorts The number of desired write ports >= 0, and (numWritePorts + numReadwritePorts) > 0
* @param numReadwritePorts The number of desired read/write ports >= 0, and the above two conditions must hold
* @param memoryFile A memory file whose path is emitted as Verilog directives to initialize the inner `SyncReadMem`
*
* @return A new `SRAMInterface` wire containing the control signals for each instantiated port
* @note This does *not* return the `SyncReadMem` itself, you must interact with it using the returned bundle
* @note Read-only memories (R >= 1, W === 0, RW === 0) and write-only memories (R === 0, W >= 1, RW === 0) are not supported by this API, and will result in an error if declared.
*/
def masked[T <: Data](
size: BigInt,
tpe: T
)(numReadPorts: Int,
tpe: T,
numReadPorts: Int,
numWritePorts: Int,
numReadwritePorts: Int,
clock: Clock
memoryFile: MemoryFile
)(
implicit sourceInfo: SourceInfo
): SRAMInterface[T] = {
val isValidSRAM = ((numReadPorts + numReadwritePorts) > 0) && ((numWritePorts + numReadwritePorts) > 0)

if (!isValidSRAM) {
val badMemory =
if (numReadPorts + numReadwritePorts == 0)
"write-only SRAM (R + RW === 0)"
else
"read-only SRAM (W + RW === 0)"
Builder.error(
s"Attempted to initialize a $badMemory! SRAMs must have both at least one read accessor and at least one write accessor."
)
}

val _out = Wire(new SRAMInterface(size, tpe, numReadPorts, numWritePorts, numReadwritePorts))
val mem = SyncReadMem(size, tpe)

for (i <- 0 until numReadPorts) {
_out.readPorts(i).data := mem.read(_out.readPorts(i).address, _out.readPorts(i).enable, clock)
}

for (i <- 0 until numWritePorts) {
when(_out.writePorts(i).enable) {
mem.write(_out.writePorts(i).address, _out.writePorts(i).data, clock)
}
}

for (i <- 0 until numReadwritePorts) {
_out.readwritePorts(i).readData := mem.readWrite(
_out.readwritePorts(i).address,
_out.readwritePorts(i).writeData,
_out.readwritePorts(i).enable,
_out.readwritePorts(i).isWrite,
clock
)
}

_out
}
implicit evidence: T <:< Vec[_],
sourceInfo: SourceInfo
): SRAMInterface[T] =
memInterface_impl(size, tpe)(
numReadPorts,
numWritePorts,
numReadwritePorts,
Builder.forcedClock,
Some(memoryFile)
)(
Some(evidence),
sourceInfo
)

private def masked_memInterface_impl[T <: Data](
private def memInterface_impl[T <: Data](
size: BigInt,
tpe: T
)(numReadPorts: Int,
numWritePorts: Int,
numReadwritePorts: Int,
clock: Clock
clock: Clock,
memoryFile: Option[MemoryFile]
)(
implicit sourceInfo: SourceInfo,
evidence: T <:< Vec[_]
implicit evidenceOpt: Option[T <:< Vec[_]],
sourceInfo: SourceInfo
): SRAMInterface[T] = {
val isVecMem = evidenceOpt.isDefined
val isValidSRAM = ((numReadPorts + numReadwritePorts) > 0) && ((numWritePorts + numReadwritePorts) > 0)

if (!isValidSRAM) {
Expand All @@ -232,7 +304,7 @@ object SRAM {
)
}

val _out = Wire(new SRAMInterface(size, tpe, numReadPorts, numWritePorts, numReadwritePorts, true))
val _out = Wire(new SRAMInterface(size, tpe, numReadPorts, numWritePorts, numReadwritePorts, isVecMem))
val mem = SyncReadMem(size, tpe)

for (i <- 0 until numReadPorts) {
Expand All @@ -241,26 +313,43 @@ object SRAM {

for (i <- 0 until numWritePorts) {
when(_out.writePorts(i).enable) {
mem.write(
_out.writePorts(i).address,
_out.writePorts(i).data,
_out.writePorts(i).mask.get,
clock
)
if (isVecMem) {
mem.write(
_out.writePorts(i).address,
_out.writePorts(i).data,
_out.writePorts(i).mask.get,
clock
)(evidenceOpt.get)
} else {
mem.write(_out.writePorts(i).address, _out.writePorts(i).data, clock)
}
}
}

for (i <- 0 until numReadwritePorts) {
_out.readwritePorts(i).readData := mem.readWrite(
_out.readwritePorts(i).address,
_out.readwritePorts(i).writeData,
_out.readwritePorts(i).mask.get,
_out.readwritePorts(i).enable,
_out.readwritePorts(i).isWrite,
clock
)
if (isVecMem) {
_out.readwritePorts(i).readData := mem.readWrite(
_out.readwritePorts(i).address,
_out.readwritePorts(i).writeData,
_out.readwritePorts(i).mask.get,
_out.readwritePorts(i).enable,
_out.readwritePorts(i).isWrite,
clock
)(evidenceOpt.get)
} else {
_out.readwritePorts(i).readData := mem.readWrite(
_out.readwritePorts(i).address,
_out.readwritePorts(i).writeData,
_out.readwritePorts(i).enable,
_out.readwritePorts(i).isWrite,
clock
)
}
}

// Emit Verilog for preloading the memory from a file if requested
memoryFile.foreach { file: MemoryFile => loadMemoryFromFileInline(mem, file.path, file.fileType) }

_out
}

Expand Down

0 comments on commit 7c5bb18

Please sign in to comment.