From b1ef8e16dc2460b4c18e5110121e0692916d4ad0 Mon Sep 17 00:00:00 2001 From: Jesse De Meulemeester Date: Wed, 24 Dec 2025 16:15:15 +0100 Subject: [PATCH 1/7] Add artificial delay in cache response --- src/main/scala/riscv/plugins/Cache.scala | 37 ++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/main/scala/riscv/plugins/Cache.scala b/src/main/scala/riscv/plugins/Cache.scala index 60bc2bd..a087119 100644 --- a/src/main/scala/riscv/plugins/Cache.scala +++ b/src/main/scala/riscv/plugins/Cache.scala @@ -10,7 +10,8 @@ class Cache( busFilter: ((Stage, MemBus, MemBus) => Unit) => Unit, prefetcher: Option[PrefetchService] = None, maxPrefetches: Int = 1, - cacheable: (UInt => Bool) = (_ => True) + cacheable: (UInt => Bool) = (_ => True), + delay: Int = 3 )(implicit config: Config) extends Plugin[Pipeline] { private val byteIndexBits = log2Up(config.xlen / 8) @@ -104,9 +105,15 @@ class Cache( private val rspBuffer = Reg(MemBusRsp(internal.config)) private val returningCache = Reg(Bool()).init(False) + // Delay cache response with a fixed delay + // Note that a minimal delay of 1 clock cycle is required to prevent + // combinatorial loops in case of multiple dbus filters. + private val internalRspBuffer = Stream(MemBusRsp(internal.config)) + internal.rsp << internalRspBuffer.delay(delay) + // initial state: not sending or acknowledging anything - internal.rsp.valid := False - internal.rsp.payload.assignDontCare() + internalRspBuffer.valid := False + internalRspBuffer.payload.assignDontCare() internal.cmd.ready := False external.cmd.valid := False external.cmd.payload.assignDontCare() @@ -126,13 +133,13 @@ class Cache( private def forwardRspToInternal(): Unit = { sendingRsp := True - internal.rsp.valid := True + internalRspBuffer.valid := True - internal.rsp.rdata := external.rsp.rdata + internalRspBuffer.rdata := external.rsp.rdata // the index of 1's in internalIds indicate to which internal ids the response should be forwarded val internalId = OHToUInt(OHMasking.first(outstandingLoads(external.rsp.id).internalIds)) - internal.rsp.id := internalId - when(internal.rsp.ready) { + internalRspBuffer.id := internalId + when(internalRspBuffer.ready) { // set the bit to 0 once it has been forwarded outstandingLoads(external.rsp.id).internalIds(internalId) := False } @@ -188,7 +195,7 @@ class Cache( forwardRspToInternal() when( // when there is only one id left to forward, put result in cache and inform external bus we are done - internal.rsp.ready && CountOne(outstandingLoads(external.rsp.id).internalIds) === 1 + internalRspBuffer.ready && CountOne(outstandingLoads(external.rsp.id).internalIds) === 1 ) { insertRspInCache(address) alreadySendingRsp := False @@ -206,10 +213,10 @@ class Cache( rspBuffer.id := internal.cmd.id rspBuffer.rdata := cacheLine.value when(!sendingRsp) { - internal.rsp.valid := True - internal.rsp.id := internal.cmd.id - internal.rsp.rdata := cacheLine.value - when(!internal.rsp.ready) { + internalRspBuffer.valid := True + internalRspBuffer.id := internal.cmd.id + internalRspBuffer.rdata := cacheLine.value + when(!internalRspBuffer.ready) { returningCache := True } } otherwise { @@ -221,9 +228,9 @@ class Cache( when(returningCache && !sendingRsp) { // when not forwarding rsp but have a stored cache hit, return that - internal.rsp.valid := True - internal.rsp.payload := rspBuffer - when(internal.rsp.ready) { + internalRspBuffer.valid := True + internalRspBuffer.payload := rspBuffer + when(internalRspBuffer.ready) { returningCache := False } } From 806c8b84a8abef97a3351404aa2b4e7ea0d59f3d Mon Sep 17 00:00:00 2001 From: Jesse De Meulemeester Date: Wed, 24 Dec 2025 16:16:25 +0100 Subject: [PATCH 2/7] Support multiple dbus filters This enables multiple cache levels. --- .../memory/DynamicMemoryBackbone.scala | 18 +++++++++++++++++- .../riscv/plugins/memory/MemoryBackbone.scala | 12 ++---------- .../plugins/memory/StaticMemoryBackbone.scala | 19 ++++++++++++++++++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala index b262fcb..f1aaa1d 100644 --- a/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala +++ b/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala @@ -194,7 +194,23 @@ class DynamicMemoryBackbone(implicit config: Config) extends MemoryBackbone with } } - dbusFilter.foreach(_(internalWriteDBusStage, unifiedInternalDBus, externalDBus)) + if (dbusFilters.nonEmpty) { + var previous_level = unifiedInternalDBus + + dbusFilters.zipWithIndex.foreach { case (f, i) => + if (i < dbusFilters.size - 1) { + val intermediateDBus = Stream(MemBus(config.dbusConfig)).setName("intermediate_dbus" + i) + f(internalWriteDBusStage, previous_level, intermediateDBus) + + previous_level = intermediateDBus + } else { + f(internalWriteDBusStage, previous_level, externalDBus) + } + } + } else { + unifiedInternalDBus.payload <> externalDBus + } + dbusObservers.foreach(_(internalWriteDBusStage, unifiedInternalDBus)) } } diff --git a/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala index e2d56f3..1f8a7a3 100644 --- a/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala +++ b/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala @@ -15,7 +15,7 @@ abstract class MemoryBackbone(implicit config: Config) extends Plugin with Memor var internalWriteDBus: MemBus = null var internalReadDBusStages: Seq[Stage] = null var internalWriteDBusStage: Stage = null - var dbusFilter: Option[MemBusFilter] = None + var dbusFilters = mutable.ArrayBuffer[MemBusFilter]() var ibusFilter: Option[MemBusFilter] = None val dbusObservers = mutable.ArrayBuffer[MemBusObserver]() @@ -45,13 +45,6 @@ abstract class MemoryBackbone(implicit config: Config) extends Plugin with Memor override def finish(): Unit = { setupIBus() - - // DBUS - if (dbusFilter.isEmpty) { - dbusFilter = Some((_, idbus, edbus) => { - idbus <> edbus - }) - } } override def getExternalIBus: MemBus = { @@ -80,8 +73,7 @@ abstract class MemoryBackbone(implicit config: Config) extends Plugin with Memor } override def filterDBus(filter: MemBusFilter): Unit = { - assert(dbusFilter.isEmpty) - dbusFilter = Some(filter) + dbusFilters += filter } override def filterIBus(filter: MemBusFilter): Unit = { diff --git a/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala index d2f210b..33bce7f 100644 --- a/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala +++ b/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala @@ -13,7 +13,24 @@ class StaticMemoryBackbone(implicit config: Config) extends MemoryBackbone { pipeline plug new Area { externalDBus = master(new MemBus(config.dbusConfig)).setName("dbus") - dbusFilter.foreach(_(internalWriteDBusStage, internalWriteDBus, externalDBus)) + + if (dbusFilters.nonEmpty) { + var previous_level = internalWriteDBus + + dbusFilters.zipWithIndex.foreach { case (f, i) => + if (i < dbusFilters.size - 1) { + val intermediateDBus = Stream(MemBus(config.dbusConfig)).setName("intermediate_dbus" + i) + f(null, previous_level, intermediateDBus) + + previous_level = intermediateDBus + } else { + f(null, previous_level, externalDBus) + } + } + } else { + internalWriteDBus <> externalDBus + } + dbusObservers.foreach(_(internalWriteDBusStage, internalWriteDBus)) } } From a0fd240a6933a5e9e07ce2d3361c499627be42ea Mon Sep 17 00:00:00 2001 From: Jesse De Meulemeester Date: Wed, 24 Dec 2025 16:30:06 +0100 Subject: [PATCH 3/7] Change default cache latency to 1 cycle Technicaly can also be zero for single-level caches, but this introduces combinatorial loops with multi-level caches --- src/main/scala/riscv/plugins/Cache.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/riscv/plugins/Cache.scala b/src/main/scala/riscv/plugins/Cache.scala index a087119..c135ae8 100644 --- a/src/main/scala/riscv/plugins/Cache.scala +++ b/src/main/scala/riscv/plugins/Cache.scala @@ -11,7 +11,7 @@ class Cache( prefetcher: Option[PrefetchService] = None, maxPrefetches: Int = 1, cacheable: (UInt => Bool) = (_ => True), - delay: Int = 3 + delay: Int = 1 )(implicit config: Config) extends Plugin[Pipeline] { private val byteIndexBits = log2Up(config.xlen / 8) From e8403fbf4969b2ef0028d5ee720849d3b9f44c84 Mon Sep 17 00:00:00 2001 From: Jesse De Meulemeester Date: Wed, 24 Dec 2025 16:42:54 +0100 Subject: [PATCH 4/7] Fix formatting --- .../scala/riscv/plugins/memory/DynamicMemoryBackbone.scala | 3 ++- .../scala/riscv/plugins/memory/StaticMemoryBackbone.scala | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala index f1aaa1d..449ceac 100644 --- a/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala +++ b/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala @@ -199,7 +199,8 @@ class DynamicMemoryBackbone(implicit config: Config) extends MemoryBackbone with dbusFilters.zipWithIndex.foreach { case (f, i) => if (i < dbusFilters.size - 1) { - val intermediateDBus = Stream(MemBus(config.dbusConfig)).setName("intermediate_dbus" + i) + val intermediateDBus = + Stream(MemBus(config.dbusConfig)).setName("intermediate_dbus" + i) f(internalWriteDBusStage, previous_level, intermediateDBus) previous_level = intermediateDBus diff --git a/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala index 33bce7f..18bf721 100644 --- a/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala +++ b/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala @@ -13,13 +13,14 @@ class StaticMemoryBackbone(implicit config: Config) extends MemoryBackbone { pipeline plug new Area { externalDBus = master(new MemBus(config.dbusConfig)).setName("dbus") - + if (dbusFilters.nonEmpty) { var previous_level = internalWriteDBus dbusFilters.zipWithIndex.foreach { case (f, i) => if (i < dbusFilters.size - 1) { - val intermediateDBus = Stream(MemBus(config.dbusConfig)).setName("intermediate_dbus" + i) + val intermediateDBus = + Stream(MemBus(config.dbusConfig)).setName("intermediate_dbus" + i) f(null, previous_level, intermediateDBus) previous_level = intermediateDBus From 6659777342d6cc0269cc44fd0dd2c77b6390354c Mon Sep 17 00:00:00 2001 From: Jesse De Meulemeester Date: Wed, 7 Jan 2026 12:21:41 +0100 Subject: [PATCH 5/7] Refactor dbus filter connection logic to MemoryBackbone --- .../memory/DynamicMemoryBackbone.scala | 27 +++---------------- .../riscv/plugins/memory/MemoryBackbone.scala | 26 ++++++++++++++++++ .../plugins/memory/StaticMemoryBackbone.scala | 24 +---------------- 3 files changed, 31 insertions(+), 46 deletions(-) diff --git a/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala index 449ceac..3cc277c 100644 --- a/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala +++ b/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala @@ -9,6 +9,7 @@ import scala.collection.mutable class DynamicMemoryBackbone(implicit config: Config) extends MemoryBackbone with Resettable { private var activeFlush: Bool = null + private var unifiedInternalDBus: Stream[MemBus] = null override def build(): Unit = { pipeline plug new Area { @@ -22,9 +23,7 @@ class DynamicMemoryBackbone(implicit config: Config) extends MemoryBackbone with super.finish() pipeline plug new Area { - externalDBus = master(new MemBus(config.dbusConfig)).setName("dbus") - - private val unifiedInternalDBus = Stream(MemBus(config.dbusConfig)) + unifiedInternalDBus = Stream(MemBus(config.dbusConfig)) unifiedInternalDBus.cmd.valid := False unifiedInternalDBus.cmd.address.assignDontCare() @@ -193,27 +192,9 @@ class DynamicMemoryBackbone(implicit config: Config) extends MemoryBackbone with } } } - - if (dbusFilters.nonEmpty) { - var previous_level = unifiedInternalDBus - - dbusFilters.zipWithIndex.foreach { case (f, i) => - if (i < dbusFilters.size - 1) { - val intermediateDBus = - Stream(MemBus(config.dbusConfig)).setName("intermediate_dbus" + i) - f(internalWriteDBusStage, previous_level, intermediateDBus) - - previous_level = intermediateDBus - } else { - f(internalWriteDBusStage, previous_level, externalDBus) - } - } - } else { - unifiedInternalDBus.payload <> externalDBus - } - - dbusObservers.foreach(_(internalWriteDBusStage, unifiedInternalDBus)) } + + setupExternalDBus(unifiedInternalDBus) } override def createInternalDBus( diff --git a/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala index 1f8a7a3..055bd3f 100644 --- a/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala +++ b/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala @@ -43,6 +43,32 @@ abstract class MemoryBackbone(implicit config: Config) extends Plugin with Memor } } + def setupExternalDBus(internalDBus: MemBus): Unit = { + pipeline plug new Area { + externalDBus = master(new MemBus(config.dbusConfig)).setName("dbus") + + if (dbusFilters.nonEmpty) { + var previous_level = internalDBus + + dbusFilters.zipWithIndex.foreach { case (f, i) => + if (i < dbusFilters.size - 1) { + val intermediateDBus = + Stream(MemBus(config.dbusConfig)).setName("intermediate_dbus" + i) + f(internalWriteDBusStage, previous_level, intermediateDBus) + + previous_level = intermediateDBus + } else { + f(internalWriteDBusStage, previous_level, externalDBus) + } + } + } else { + internalDBus <> externalDBus + } + + dbusObservers.foreach(_(internalWriteDBusStage, internalDBus)) + } + } + override def finish(): Unit = { setupIBus() } diff --git a/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala index 18bf721..fc106fd 100644 --- a/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala +++ b/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala @@ -11,29 +11,7 @@ class StaticMemoryBackbone(implicit config: Config) extends MemoryBackbone { override def finish(): Unit = { super.finish() - pipeline plug new Area { - externalDBus = master(new MemBus(config.dbusConfig)).setName("dbus") - - if (dbusFilters.nonEmpty) { - var previous_level = internalWriteDBus - - dbusFilters.zipWithIndex.foreach { case (f, i) => - if (i < dbusFilters.size - 1) { - val intermediateDBus = - Stream(MemBus(config.dbusConfig)).setName("intermediate_dbus" + i) - f(null, previous_level, intermediateDBus) - - previous_level = intermediateDBus - } else { - f(null, previous_level, externalDBus) - } - } - } else { - internalWriteDBus <> externalDBus - } - - dbusObservers.foreach(_(internalWriteDBusStage, internalWriteDBus)) - } + setupExternalDBus(internalWriteDBus) } override def createInternalDBus( From a7dfb70190fa15a043dc37901c66c7d67acc3429 Mon Sep 17 00:00:00 2001 From: marton bognar Date: Wed, 7 Jan 2026 12:54:16 +0100 Subject: [PATCH 6/7] Comment to explain cache plugin configuration --- src/main/scala/riscv/Core.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/scala/riscv/Core.scala b/src/main/scala/riscv/Core.scala index f04a04c..95fec09 100644 --- a/src/main/scala/riscv/Core.scala +++ b/src/main/scala/riscv/Core.scala @@ -60,6 +60,9 @@ object createStaticPipeline { prefetcher, new Cache(sets = 2, ways = 2, backbone.filterIBus, Some(prefetcher), maxPrefetches = 2), new Cache(sets = 8, ways = 2, backbone.filterDBus, cacheable = (_ >= 0x80000000L)), + // Different cache levels can be specified by adding more caches plugins. + // The order in which you add the caches is the order in which they will be connected: + // new Cache(sets = 16, ways = 4, backbone.filterDBus, delay = 5), new CsrFile(pipeline.writeback, pipeline.writeback), // TODO: ugly new Timers, new MachineMode(pipeline.execute), From 12938ffe2a7d55c2357176c46b15bd2e692315c4 Mon Sep 17 00:00:00 2001 From: marton bognar Date: Wed, 7 Jan 2026 12:58:32 +0100 Subject: [PATCH 7/7] Typo and formatting --- src/main/scala/riscv/Core.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/riscv/Core.scala b/src/main/scala/riscv/Core.scala index 95fec09..6b6dd0e 100644 --- a/src/main/scala/riscv/Core.scala +++ b/src/main/scala/riscv/Core.scala @@ -60,7 +60,7 @@ object createStaticPipeline { prefetcher, new Cache(sets = 2, ways = 2, backbone.filterIBus, Some(prefetcher), maxPrefetches = 2), new Cache(sets = 8, ways = 2, backbone.filterDBus, cacheable = (_ >= 0x80000000L)), - // Different cache levels can be specified by adding more caches plugins. + // Different cache levels can be specified by adding more cache plugins. // The order in which you add the caches is the order in which they will be connected: // new Cache(sets = 16, ways = 4, backbone.filterDBus, delay = 5), new CsrFile(pipeline.writeback, pipeline.writeback), // TODO: ugly