Skip to content

Commit

Permalink
SRAM API: Add multiple-clocked port API (#3383)
Browse files Browse the repository at this point in the history
This PR adds new APIs for the SRAM module to instantiate a number of ports connected to individual clocks. This allows the creation of memories driven by multiple clocks, for use cases like clock domain crossover.

(cherry picked from commit d79ae71)

# Conflicts:
#	src/test/scala/chiselTests/Mem.scala
  • Loading branch information
jared-barocsi authored and mergify[bot] committed Jul 1, 2023
1 parent abadbbe commit 858b3df
Show file tree
Hide file tree
Showing 2 changed files with 310 additions and 103 deletions.
329 changes: 238 additions & 91 deletions src/main/scala/chisel3/util/SRAM.scala
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,19 @@ object SRAM {
numReadwritePorts: Int
)(
implicit sourceInfo: SourceInfo
): SRAMInterface[T] =
memInterface_impl(size, tpe)(numReadPorts, numWritePorts, numReadwritePorts, Builder.forcedClock, None)
): SRAMInterface[T] = {
val clock = Builder.forcedClock
memInterface_impl(
size,
tpe,
Seq.fill(numReadPorts)(clock),
Seq.fill(numWritePorts)(clock),
Seq.fill(numReadwritePorts)(clock),
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,
Expand All @@ -180,8 +191,91 @@ object SRAM {
memoryFile: MemoryFile
)(
implicit sourceInfo: SourceInfo
): SRAMInterface[T] = {
val clock = Builder.forcedClock
memInterface_impl(
size,
tpe,
Seq.fill(numReadPorts)(clock),
Seq.fill(numWritePorts)(clock),
Seq.fill(numReadwritePorts)(clock),
Some(memoryFile),
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
*
* @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,
readPortClocks: Seq[Clock],
writePortClocks: Seq[Clock],
readwritePortClocks: Seq[Clock]
)(
implicit sourceInfo: SourceInfo
): SRAMInterface[T] =
memInterface_impl(size, tpe)(numReadPorts, numWritePorts, numReadwritePorts, Builder.forcedClock, Some(memoryFile))
memInterface_impl(
size,
tpe,
readPortClocks,
writePortClocks,
readwritePortClocks,
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,
readPortClocks: Seq[Clock],
writePortClocks: Seq[Clock],
readwritePortClocks: Seq[Clock],
memoryFile: MemoryFile
)(
implicit sourceInfo: SourceInfo
): SRAMInterface[T] =
memInterface_impl(
size,
tpe,
readPortClocks,
writePortClocks,
readwritePortClocks,
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 @@ -207,8 +301,19 @@ object SRAM {
)(
implicit evidence: T <:< Vec[_],
sourceInfo: SourceInfo
): SRAMInterface[T] =
masked_memInterface_impl(size, tpe)(numReadPorts, numWritePorts, numReadwritePorts, Builder.forcedClock, None)
): SRAMInterface[T] = {
val clock = Builder.forcedClock
memInterface_impl(
size,
tpe,
Seq.fill(numReadPorts)(clock),
Seq.fill(numWritePorts)(clock),
Seq.fill(numReadwritePorts)(clock),
None,
Some(evidence),
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 Down Expand Up @@ -236,26 +341,108 @@ object SRAM {
)(
implicit evidence: T <:< Vec[_],
sourceInfo: SourceInfo
): SRAMInterface[T] = {
val clock = Builder.forcedClock
memInterface_impl(
size,
tpe,
Seq.fill(numReadPorts)(clock),
Seq.fill(numWritePorts)(clock),
Seq.fill(numReadwritePorts)(clock),
Some(memoryFile),
Some(evidence),
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.
* Each port is clocked with its own explicit `Clock`, rather than being given the implicit clock.
*
* @param size The desired size of the inner `SyncReadMem`
* @tparam T The data type of the memory element
* @param readPortClocks A sequence of clocks for each read port; and (numReadPorts + numReadwritePorts) > 0
* @param writePortClocks A sequence of clocks for each write port; and (numWritePorts + numReadwritePorts) > 0
* @param readwritePortClocks A sequence of clocks for each read-write port; and the above two conditions must hold
*
* @return A new `SRAMInterface` wire containing the control signals for each instantiated port
* @note The size of each `Clock` sequence determines the corresponding number of read, write, and read-write ports
* @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,
readPortClocks: Seq[Clock],
writePortClocks: Seq[Clock],
readwritePortClocks: Seq[Clock]
)(
implicit evidence: T <:< Vec[_],
sourceInfo: SourceInfo
): SRAMInterface[T] =
masked_memInterface_impl(size, tpe)(
numReadPorts,
numWritePorts,
numReadwritePorts,
Builder.forcedClock,
Some(memoryFile)
memInterface_impl(
size,
tpe,
readPortClocks,
writePortClocks,
readwritePortClocks,
None,
Some(evidence),
sourceInfo
)

private def memInterface_impl[T <: Data](
size: BigInt,
tpe: T
)(numReadPorts: Int,
numWritePorts: Int,
numReadwritePorts: Int,
clock: Clock,
memoryFile: Option[MemoryFile]
/** 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.
* Each port is clocked with its own explicit `Clock`, rather than being given the implicit clock.
*
* @param size The desired size of the inner `SyncReadMem`
* @tparam T The data type of the memory element
* @param readPortClocks A sequence of clocks for each read port; and (numReadPorts + numReadwritePorts) > 0
* @param writePortClocks A sequence of clocks for each write port; and (numWritePorts + numReadwritePorts) > 0
* @param readwritePortClocks A sequence of clocks for each read-write port; 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 The size of each `Clock` sequence determines the corresponding number of read, write, and read-write ports
* @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,
readPortClocks: Seq[Clock],
writePortClocks: Seq[Clock],
readwritePortClocks: Seq[Clock],
memoryFile: MemoryFile
)(
implicit sourceInfo: SourceInfo
implicit evidence: T <:< Vec[_],
sourceInfo: SourceInfo
): SRAMInterface[T] =
memInterface_impl(
size,
tpe,
readPortClocks,
writePortClocks,
readwritePortClocks,
Some(memoryFile),
Some(evidence),
sourceInfo
)

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

if (!isValidSRAM) {
Expand All @@ -269,89 +456,49 @@ object SRAM {
)
}

val _out = Wire(new SRAMInterface(size, tpe, numReadPorts, numWritePorts, numReadwritePorts))
val _out = Wire(new SRAMInterface(size, tpe, numReadPorts, numWritePorts, numReadwritePorts, isVecMem))
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 ((clock, port) <- readPortClocks.zip(_out.readPorts)) {
port.data := mem.read(port.address, port.enable, clock)
}

for (i <- 0 until numWritePorts) {
when(_out.writePorts(i).enable) {
mem.write(_out.writePorts(i).address, _out.writePorts(i).data, clock)
for ((clock, port) <- writePortClocks.zip(_out.writePorts)) {
when(port.enable) {
if (isVecMem) {
mem.write(
port.address,
port.data,
port.mask.get,
clock
)(evidenceOpt.get)
} else {
mem.write(port.address, port.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
)
}

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

_out
}

private def masked_memInterface_impl[T <: Data](
size: BigInt,
tpe: T
)(numReadPorts: Int,
numWritePorts: Int,
numReadwritePorts: Int,
clock: Clock,
memoryFile: Option[MemoryFile]
)(
implicit sourceInfo: SourceInfo,
evidence: T <:< Vec[_]
): 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, true))
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,
_out.writePorts(i).mask.get,
for ((clock, port) <- readwritePortClocks.zip(_out.readwritePorts)) {
if (isVecMem) {
port.readData := mem.readWrite(
port.address,
port.writeData,
port.mask.get,
port.enable,
port.isWrite,
clock
)(evidenceOpt.get)
} else {
port.readData := mem.readWrite(
port.address,
port.writeData,
port.enable,
port.isWrite,
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
)
}

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

Expand Down
Loading

0 comments on commit 858b3df

Please sign in to comment.