From d79ae71e70b1b69dc316cee44710d99420f5ff5c Mon Sep 17 00:00:00 2001 From: Jared Barocsi <82000041+jared-barocsi@users.noreply.github.com> Date: Fri, 30 Jun 2023 23:08:04 -0700 Subject: [PATCH] SRAM API: Add multiple-clocked port API (#3383) 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. --- src/main/scala/chisel3/util/SRAM.scala | 329 ++++++++++++++++++------- src/test/scala/chiselTests/Mem.scala | 92 +++++-- 2 files changed, 305 insertions(+), 116 deletions(-) diff --git a/src/main/scala/chisel3/util/SRAM.scala b/src/main/scala/chisel3/util/SRAM.scala index 5aebaf8780f..edcfb679971 100644 --- a/src/main/scala/chisel3/util/SRAM.scala +++ b/src/main/scala/chisel3/util/SRAM.scala @@ -155,8 +155,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, @@ -183,8 +194,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. @@ -210,8 +304,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. @@ -239,26 +344,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) { @@ -272,89 +459,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) } diff --git a/src/test/scala/chiselTests/Mem.scala b/src/test/scala/chiselTests/Mem.scala index 3859b37ba8e..a854a0195c7 100644 --- a/src/test/scala/chiselTests/Mem.scala +++ b/src/test/scala/chiselTests/Mem.scala @@ -512,40 +512,82 @@ class SRAMSpec extends ChiselFunSpec { } } } - } - it(s"should support masking with Vec-valued data") { - class TestModule(val wr: Int, val rw: Int) extends Module { - val mem = SRAM.masked(32, Vec(3, UInt(8.W)), 0, wr, rw) + it(s"should support masking with Vec-valued data") { + class TestModule(val wr: Int, val rw: Int) extends Module { + val mem = SRAM.masked(32, Vec(3, UInt(8.W)), 0, wr, rw) - dontTouch(mem) + dontTouch(mem) - for (i <- 0 until wr) { - mem.writePorts(i) := DontCare - } - for (i <- 0 until rw) { - mem.readwritePorts(i) := DontCare + for (i <- 0 until wr) { + mem.writePorts(i) := DontCare + } + for (i <- 0 until rw) { + mem.readwritePorts(i) := DontCare + } } - } - val chirrtl = ChiselStage.emitCHIRRTL(new TestModule(1, 1), args = Array("--full-stacktrace")) + val chirrtl = ChiselStage.emitCHIRRTL(new TestModule(1, 1), args = Array("--full-stacktrace")) - chirrtl should include( - "writePorts : { flip address : UInt<6>, flip enable : UInt<1>, flip data : UInt<8>[3], flip mask : UInt<1>[3]}[1]" - ) - chirrtl should include( - "readwritePorts : { flip address : UInt<6>, flip enable : UInt<1>, flip isWrite : UInt<1>, readData : UInt<8>[3], flip writeData : UInt<8>[3], flip mask : UInt<1>[3]}[1]" - ) - - for (i <- 0 until 3) { - chirrtl should include(s"when mem.writePorts[0].mask[$i]") - chirrtl should include(s"connect mem_MPORT[$i], mem.writePorts[0].data[$i]") - - chirrtl should include(s"when mem.readwritePorts[0].mask[$i]") chirrtl should include( - s"connect mem_out_readwritePorts_0_readData_MPORT[$i], mem.readwritePorts[0].writeData[$i]" + "writePorts : { flip address : UInt<6>, flip enable : UInt<1>, flip data : UInt<8>[3], flip mask : UInt<1>[3]}[1]" ) + chirrtl should include( + "readwritePorts : { flip address : UInt<6>, flip enable : UInt<1>, flip isWrite : UInt<1>, readData : UInt<8>[3], flip writeData : UInt<8>[3], flip mask : UInt<1>[3]}[1]" + ) + + for (i <- 0 until 3) { + chirrtl should include(s"when mem.writePorts[0].mask[$i]") + chirrtl should include(s"connect mem_MPORT[$i], mem.writePorts[0].data[$i]") + + chirrtl should include(s"when mem.readwritePorts[0].mask[$i]") + chirrtl should include( + s"connect mem_out_readwritePorts_0_readData_MPORT[$i], mem.readwritePorts[0].writeData[$i]" + ) + } + } + + it(s"should support multiple clocks driving different ports") { + class TestModule extends Module { + val (counter, _) = Counter(true.B, 11) + + val readClocks = IO(Input(Vec(3, Clock()))) + val writeClocks = IO(Input(Vec(3, Clock()))) + val readwriteClocks = IO(Input(Vec(3, Clock()))) + + val mem = SRAM( + 32, + Vec(3, UInt(8.W)), + readClocks, + writeClocks, + readwriteClocks + ) + + dontTouch(mem) + + for (i <- 0 until 3) { + mem.readPorts(i) := DontCare + mem.writePorts(i) := DontCare + mem.readwritePorts(i) := DontCare + } + } + val chirrtl = ChiselStage.emitCHIRRTL(new TestModule, args = Array("--full-stacktrace")) + + for (i <- 0 until 3) { + val wrIndexSuffix = if (i == 0) "" else s"_$i" + + chirrtl should include( + s"read mport mem_out_readPorts_${i}_data_MPORT = mem_mem[_mem_out_readPorts_${i}_data_T], readClocks[${i}]" + ) + chirrtl should include( + s"write mport mem_MPORT${wrIndexSuffix} = mem_mem[_mem_T${wrIndexSuffix}], writeClocks[${i}]" + ) + chirrtl should include( + s"rdwr mport mem_out_readwritePorts_${i}_readData_MPORT = mem_mem[_mem_out_readwritePorts_${i}_readData_T], readwriteClocks[${i}]" + ) + } } } + describe("Read-only SRAM") { it(s"should error") { class TestModule extends Module {