From a5b77ab15ef0b8f61508ee6440551003b563b1d0 Mon Sep 17 00:00:00 2001 From: marton bognar Date: Wed, 31 Jan 2024 19:16:58 +0100 Subject: [PATCH] Pluggable memory bus cache and sequential instruction prefetcher --- sim/Makefile | 2 +- sim/main.cpp | 63 +++- src/main/scala/riscv/Config.scala | 13 +- src/main/scala/riscv/Core.scala | 36 +- src/main/scala/riscv/DynamicPipeline.scala | 9 +- src/main/scala/riscv/MemBus.scala | 265 ++------------ src/main/scala/riscv/Services.scala | 10 +- .../riscv/plugins/BranchTargetPredictor.scala | 11 - src/main/scala/riscv/plugins/Cache.scala | 337 ++++++++++++++++++ .../riscv/plugins/DynamicMemoryBackbone.scala | 220 ------------ .../riscv/plugins/NoPredictionPredictor.scala | 4 - .../riscv/plugins/StaticMemoryBackbone.scala | 101 ------ src/main/scala/riscv/plugins/cheri/Core.scala | 7 +- .../memory/DynamicMemoryBackbone.scala | 193 ++++++++++ .../riscv/plugins/{ => memory}/Fetcher.scala | 8 +- .../riscv/plugins/{ => memory}/Lsu.scala | 17 +- .../riscv/plugins/memory/MemoryBackbone.scala | 95 +++++ .../SequentialInstructionPrefetcher.scala | 23 ++ .../plugins/memory/StaticMemoryBackbone.scala | 38 ++ .../scheduling/dynamic/PcManager.scala | 4 +- .../scheduling/dynamic/ReorderBuffer.scala | 15 +- .../scheduling/dynamic/Scheduler.scala | 3 +- src/main/scala/riscv/soc/SoC.scala | 6 +- .../scala/riscv/soc/devices/Apb3ByteDev.scala | 2 +- .../scala/riscv/soc/devices/Apb3CharDev.scala | 4 +- .../riscv/soc/devices/Apb3MachineTimers.scala | 2 +- .../scala/riscv/soc/devices/Apb3TestDev.scala | 2 +- 27 files changed, 840 insertions(+), 650 deletions(-) create mode 100644 src/main/scala/riscv/plugins/Cache.scala delete mode 100644 src/main/scala/riscv/plugins/DynamicMemoryBackbone.scala delete mode 100644 src/main/scala/riscv/plugins/StaticMemoryBackbone.scala create mode 100644 src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala rename src/main/scala/riscv/plugins/{ => memory}/Fetcher.scala (84%) rename src/main/scala/riscv/plugins/{ => memory}/Lsu.scala (97%) create mode 100644 src/main/scala/riscv/plugins/memory/MemoryBackbone.scala create mode 100644 src/main/scala/riscv/plugins/memory/SequentialInstructionPrefetcher.scala create mode 100644 src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala diff --git a/sim/Makefile b/sim/Makefile index c822ca3..b217cdc 100644 --- a/sim/Makefile +++ b/sim/Makefile @@ -15,7 +15,7 @@ $(EXE_FILE): $(BUILD_DIR)/$(VERILATOR_NAME).mk main.cpp make -C $(BUILD_DIR) -f $(VERILATOR_NAME).mk $(EXE_NAME) $(BUILD_DIR)/$(VERILATOR_NAME).mk: $(VERILOG_INPUT) - verilator --cc --exe --trace -O3 -CFLAGS "-O3 -std=c++11" -Wno-WIDTH -Wno-UNOPTFLAT -Wno-CMPCONST -Wno-UNSIGNED -Mdir $(BUILD_DIR) $(VERILOG_INPUT) main.cpp -o $(EXE_NAME) + verilator --cc --exe --trace -O3 -CFLAGS "-O3 -std=c++20" -Wno-WIDTH -Wno-UNOPTFLAT -Wno-CMPCONST -Wno-UNSIGNED -Mdir $(BUILD_DIR) $(VERILOG_INPUT) main.cpp -o $(EXE_NAME) $(VERILOG_INPUT): $(BASE_DIR)/$(VERILOG_FILE_NAME) mkdir -p $(@D) diff --git a/sim/main.cpp b/sim/main.cpp index 3381dd7..e958d2d 100644 --- a/sim/main.cpp +++ b/sim/main.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include #include @@ -20,6 +22,9 @@ const int CLOCK_PERIOD = 1/(CLOCK_FREQUENCY*TIMESCALE); const std::uint64_t MAX_CYCLES = 1000000000ULL; +const unsigned int MEMBUS_WORDS = 4; +const unsigned int MEMBUS_OFFSET = 2 + std::bit_width(MEMBUS_WORDS) - 1; + class Memory { public: @@ -56,7 +61,9 @@ class Memory if (nextReadCycle_ == cycle) { - top_.io_axi_r_payload_data = nextReadWord_; + for (unsigned i = 0; i < MEMBUS_WORDS; ++i) { + top_.io_axi_r_payload_data[i] = nextReadData_[i]; + } top_.io_axi_r_payload_id = nextReadId_; top_.io_axi_r_payload_last = true; top_.io_axi_r_valid = true; @@ -77,7 +84,7 @@ class Memory } else { - nextReadWord_ = read(top_.io_axi_arw_payload_addr); + read(top_.io_axi_arw_payload_addr, nextReadData_); nextReadCycle_ = cycle + 1; nextReadId_ = top_.io_axi_arw_payload_id; } @@ -88,43 +95,67 @@ class Memory using Address = std::uint32_t; using Word = std::uint32_t; - using Mask = std::uint8_t; + using Mask = std::uint32_t; - Word read(Address address) + void read(Address address, WDataOutP value) { ensureEnoughMemory(address); - return memory_[(address >> 2)]; + + auto baseAddress = (address >> MEMBUS_OFFSET) << (MEMBUS_OFFSET - 2); + + for (unsigned i = 0; i < MEMBUS_WORDS; ++i) { + value[i] = memory_[baseAddress + i]; + } } - void write(Address address, Mask mask, Word value) + void write(Address address, Mask mask, WDataInP value) { ensureEnoughMemory(address); auto bitMask = Word{0}; - if (mask & 0x1) bitMask |= 0x000000ff; - if (mask & 0x2) bitMask |= 0x0000ff00; - if (mask & 0x4) bitMask |= 0x00ff0000; - if (mask & 0x8) bitMask |= 0xff000000; + unsigned extra = 0; + for (unsigned i = 0; i < MEMBUS_WORDS; ++i) { + if (mask & (1 << (4 * i + 0))) { + extra = i; + bitMask |= 0x000000ff; + } + if (mask & (1 << (4 * i + 1))) { + extra = i; + bitMask |= 0x0000ff00; + } + if (mask & (1 << (4 * i + 2))) { + extra = i; + bitMask |= 0x00ff0000; + } + if (mask & (1 << (4 * i + 3))) { + extra = i; + bitMask |= 0xff000000; + } + } + + auto baseAddress = (address >> MEMBUS_OFFSET) << (MEMBUS_OFFSET - 2); - auto& memoryValue = memory_[(address >> 2)]; + auto& memoryValue = memory_[baseAddress + extra]; memoryValue &= ~bitMask; - memoryValue |= value & bitMask; + memoryValue |= value[0] & bitMask; } void ensureEnoughMemory(Address address) { - if ((address >> 2) >= memory_.size()) + auto baseAddress = ((address >> MEMBUS_OFFSET) + 1) << (MEMBUS_OFFSET - 2); + + if ((baseAddress) >= memory_.size()) { - memory_.reserve((address >> 2) + 1); + memory_.reserve(baseAddress + 1); - while ((address >> 2) >= memory_.size()) + while ((baseAddress) >= memory_.size()) memory_.push_back(0xcafebabe); } } VCore& top_; std::vector memory_; - Word nextReadWord_; + uint32_t nextReadData_[MEMBUS_WORDS]; vluint64_t nextReadCycle_ = 0; vluint8_t nextReadId_; }; diff --git a/src/main/scala/riscv/Config.scala b/src/main/scala/riscv/Config.scala index b6d59b3..402c44c 100644 --- a/src/main/scala/riscv/Config.scala +++ b/src/main/scala/riscv/Config.scala @@ -22,20 +22,25 @@ object BaseIsa { class Config(val baseIsa: BaseIsa, val debug: Boolean = true) { def xlen = baseIsa.xlen - def numRegs = baseIsa.xlen + def numRegs = baseIsa.numRegs + + def memBusWidth: Int = 128 def ibusConfig = MemBusConfig( addressWidth = baseIsa.xlen, - dataWidth = baseIsa.xlen, + idWidth = 2, + dataWidth = memBusWidth, readWrite = false ) def readDbusConfig = MemBusConfig( addressWidth = baseIsa.xlen, - dataWidth = baseIsa.xlen, + idWidth = 2, + dataWidth = memBusWidth, readWrite = false ) def dbusConfig = MemBusConfig( addressWidth = baseIsa.xlen, - dataWidth = baseIsa.xlen + idWidth = 2, + dataWidth = memBusWidth ) } diff --git a/src/main/scala/riscv/Core.scala b/src/main/scala/riscv/Core.scala index 05af656..b725f8e 100644 --- a/src/main/scala/riscv/Core.scala +++ b/src/main/scala/riscv/Core.scala @@ -40,18 +40,25 @@ object createStaticPipeline { ) } + val backbone = new memory.StaticMemoryBackbone + + val prefetcher = new memory.SequentialInstructionPrefetcher() + pipeline.addPlugins( Seq( - new StaticMemoryBackbone, - new Fetcher(pipeline.fetch), + backbone, + new memory.Fetcher(pipeline.fetch), new Decoder(pipeline.decode), new RegisterFileAccessor(pipeline.decode, pipeline.writeback), new IntAlu(Set(pipeline.execute)), new Shifter(Set(pipeline.execute)), - new Lsu(Set(pipeline.memory), Seq(pipeline.memory), pipeline.memory), + new memory.Lsu(Set(pipeline.memory), Seq(pipeline.memory), pipeline.memory), new BranchUnit(Set(pipeline.execute)), new PcManager(0x80000000L), new BranchTargetPredictor(pipeline.fetch, pipeline.execute, 8, conf.xlen), + prefetcher, + new Cache(sets = 2, ways = 2, backbone.filterIBus, Some(prefetcher)), + new Cache(sets = 8, ways = 2, backbone.filterDBus), new CsrFile(pipeline.writeback, pipeline.writeback), // TODO: ugly new Timers, new MachineMode(pipeline.execute), @@ -201,21 +208,25 @@ object createDynamicPipeline { val loadStage3 = new Stage("LOAD3") override val loadStages: Seq[Stage] = Seq(loadStage1, loadStage2, loadStage3) override val retirementStage = new Stage("RET") - override val unorderedStages: Seq[Stage] = rsStages ++ loadStages - override val stages = issuePipeline.stages ++ unorderedStages :+ retirementStage + override val parallelStages: Seq[Stage] = rsStages ++ loadStages + override val stages = issuePipeline.stages ++ parallelStages :+ retirementStage + override val backbone = + new memory.DynamicMemoryBackbone( + loadStages.size + 1 + ) // +1 for write stage (which also uses an ID currently) } pipeline.issuePipeline.addPlugins( Seq( new scheduling.static.Scheduler(canStallExternally = true), new scheduling.static.PcManager(0x80000000L), - new DynamicMemoryBackbone( - pipeline.loadStages.size + 1 - ), // +1 for write stage (which also uses an ID currently) - new Fetcher(pipeline.issuePipeline.fetch) + pipeline.backbone, + new memory.Fetcher(pipeline.issuePipeline.fetch) ) ) + val prefetcher = new memory.SequentialInstructionPrefetcher() + pipeline.addPlugins( Seq( new Decoder(pipeline.issuePipeline.decode), // TODO: ugly alert!! @@ -228,7 +239,7 @@ object createDynamicPipeline { readStage = pipeline.issuePipeline.decode, writeStage = pipeline.retirementStage ), - new Lsu( + new memory.Lsu( Set(pipeline.intAlu1, pipeline.intAlu2, pipeline.intAlu3, pipeline.intAlu4), pipeline.loadStages, pipeline.retirementStage @@ -239,6 +250,9 @@ object createDynamicPipeline { 8, conf.xlen ), + prefetcher, + new Cache(sets = 2, ways = 2, pipeline.backbone.filterIBus, Some(prefetcher)), + new Cache(sets = 8, ways = 2, pipeline.backbone.filterDBus), new IntAlu(Set(pipeline.intAlu1, pipeline.intAlu2, pipeline.intAlu3, pipeline.intAlu4)), new Shifter(Set(pipeline.intAlu1, pipeline.intAlu2, pipeline.intAlu3, pipeline.intAlu4)), new MulDiv(Set(pipeline.intMul1)), @@ -299,7 +313,7 @@ object CoreDynamicSim { object CoreDynamicExtMem { def main(args: Array[String]) { - SpinalVerilog(SoC.dynamic(RamType.ExternalAxi4(10 MiB), 64)) + SpinalVerilog(SoC.dynamic(RamType.ExternalAxi4(10 MiB), 32)) } } diff --git a/src/main/scala/riscv/DynamicPipeline.scala b/src/main/scala/riscv/DynamicPipeline.scala index c7a1c77..c5c70df 100644 --- a/src/main/scala/riscv/DynamicPipeline.scala +++ b/src/main/scala/riscv/DynamicPipeline.scala @@ -11,8 +11,9 @@ trait DynamicPipeline extends Pipeline { var rob: ReorderBuffer = null val loadStages: Seq[Stage] - val unorderedStages: Seq[Stage] - var components: Seq[Resettable] = null + val parallelStages: Seq[Stage] + var resettables: Seq[Resettable] = null + val backbone: MemoryService with Resettable var pipelineRegs: Map[Stage, PipelineRegs] = null @@ -44,7 +45,7 @@ trait DynamicPipeline extends Pipeline { override def connectStages(): Unit = { - for (stage <- unorderedStages :+ retirementStage) { + for (stage <- parallelStages :+ retirementStage) { stage.output(data.PC) stage.output(data.IR) @@ -86,7 +87,7 @@ trait DynamicPipeline extends Pipeline { val issueStage = issuePipeline.stages.last - for (stage <- unorderedStages :+ retirementStage) { + for (stage <- parallelStages :+ retirementStage) { // FIXME copy-pasted from StaticPipeline for (valueData <- stage.lastValues.keys) { if (!stage.outputs.contains(valueData)) { diff --git a/src/main/scala/riscv/MemBus.scala b/src/main/scala/riscv/MemBus.scala index d7211ad..04c02bf 100644 --- a/src/main/scala/riscv/MemBus.scala +++ b/src/main/scala/riscv/MemBus.scala @@ -7,6 +7,7 @@ import spinal.lib.bus.amba4.axi._ case class MemBusConfig( addressWidth: Int, + idWidth: Int, dataWidth: Int, readWrite: Boolean = true ) { @@ -14,24 +15,22 @@ case class MemBusConfig( def word2ByteAddress(wa: UInt): UInt = wa << log2Up(dataWidth / 8) } -case class MemBusCmd(config: MemBusConfig, idWidth: BitCount) extends Bundle { +case class MemBusCmd(config: MemBusConfig) extends Bundle { val address = UInt(config.addressWidth bits) - val id = UInt(idWidth) + val id = UInt(config.idWidth bits) val write = if (config.readWrite) Bool() else null val wdata = if (config.readWrite) UInt(config.dataWidth bits) else null val wmask = if (config.readWrite) Bits(config.dataWidth / 8 bits) else null } -case class MemBusRsp(config: MemBusConfig, idWidth: BitCount) extends Bundle { +case class MemBusRsp(config: MemBusConfig) extends Bundle { val rdata = UInt(config.dataWidth bits) - val id = UInt(idWidth) + val id = UInt(config.idWidth bits) } -case class MemBus(val config: MemBusConfig, val idWidth: BitCount) - extends Bundle - with IMasterSlave { - val cmd = Stream(MemBusCmd(config, idWidth)) - val rsp = Stream(MemBusRsp(config, idWidth)) +case class MemBus(val config: MemBusConfig) extends Bundle with IMasterSlave { + val cmd = Stream(MemBusCmd(config)) + val rsp = Stream(MemBusRsp(config)) override def asMaster(): Unit = { master(cmd) @@ -60,7 +59,7 @@ case class MemBus(val config: MemBusConfig, val idWidth: BitCount) def toAxi4ReadOnly(): Axi4ReadOnly = { assert(isMasterInterface) - val axi4Config = MemBus.getAxi4Config(config, idWidth) + val axi4Config = MemBus.getAxi4Config(config) val axi4Bus = Axi4ReadOnly(axi4Config) axi4Bus.readCmd.valid := cmd.valid @@ -79,7 +78,7 @@ case class MemBus(val config: MemBusConfig, val idWidth: BitCount) def toAxi4Shared(): Axi4Shared = { assert(isMasterInterface) - val axi4Config = MemBus.getAxi4Config(config, idWidth) + val axi4Config = MemBus.getAxi4Config(config) val axi4Bus = Axi4Shared(axi4Config) axi4Bus.sharedCmd.valid := cmd.valid @@ -112,11 +111,11 @@ object MemBus { useSlaveError = false ) - def getAxi4Config(config: MemBusConfig, idWidth: BitCount) = Axi4Config( + def getAxi4Config(config: MemBusConfig) = Axi4Config( addressWidth = config.addressWidth, dataWidth = config.dataWidth, useId = true, - idWidth = idWidth.value, + idWidth = config.idWidth, useRegion = false, useBurst = false, useLock = false, @@ -139,7 +138,7 @@ class MemBusControl(bus: MemBus)(implicit config: Config) extends Area { val currentCmd = new Bundle { val valid = Reg(Bool()).init(False) val ready = Reg(Bool()).init(False) - val cmd = Reg(MemBusCmd(bus.config, bus.idWidth)) + val cmd = Reg(MemBusCmd(bus.config)) def isIssued = valid || ready def isWrite = if (bus.config.readWrite) cmd.write else False @@ -209,13 +208,14 @@ class MemBusControl(bus: MemBus)(implicit config: Config) extends Area { } def invalidate(): Unit = { + bus.cmd.valid := False currentCmd.valid := False currentCmd.ready := False } def read(address: UInt): (Bool, UInt) = { val valid = False - val rdata = U(0, bus.config.dataWidth bits) + val rdata = U(0, config.xlen bits) val dropRsp = False val issuedThisCycle = False @@ -232,7 +232,12 @@ class MemBusControl(bus: MemBus)(implicit config: Config) extends Area { when(issuedThisCycle || (!dropRsp && !currentCmd.isWrite)) { valid := True - rdata := bus.rsp.rdata + val addressOffset: Int = log2Up(config.memBusWidth / 8) - 1 + val byteOffset: Int = log2Up(config.xlen / 8) + val offset: UInt = address(addressOffset downto byteOffset) + val shifted: Int = log2Up(config.xlen) + val startingBit: UInt = offset << shifted + rdata := (bus.rsp.rdata >> startingBit) (config.xlen - 1 downto 0) } } @@ -265,231 +270,3 @@ class MemBusControl(bus: MemBus)(implicit config: Config) extends Area { accepted } } - -// TODO: can the following be simplified by using AXI transaction IDs such as for parallel loads? -class IBusControl( - bus: MemBus, - ibusLatency: Int, - irQueueSize: Int = 4 -)(implicit config: Config) - extends Area { - assert(!bus.config.readWrite) - assert(ibusLatency > 0) - - case class Cmd() extends Bundle { - val address = UInt(bus.config.addressWidth bits) - } - - case class Rsp() extends Bundle { - val address = UInt(bus.config.addressWidth bits) - val ir = UInt(bus.config.dataWidth bits) - } - - // FIFO that holds the memory commands that are currently in flight (command sent but no response - // yet). This is used to match incoming responses to the address to which they correspond. - // We use a StreamFifoLowLatency instead of StreamFifo as the latter has a 2-cycle delay between - // a push and the value being available to be popped. This causes problems when the latency is - // only 1 cycle. - // We use a depth of one more than the latency because the FIFO doesn't allow pushes while full - // even when we are popping at the same time. - // We use a minimum depth of 3 as the CPU starts with prefetching two instructions before starting - // to execute them. (This could also be fixed by addressing the FIXME of bus.rsp.ready below.) - val cmdFifo = - new StreamFifoLowLatency(Cmd(), depth = scala.math.max(3, ibusLatency + 1), latency = 1) - cmdFifo.io.push.valid := False - cmdFifo.io.push.payload.assignDontCare() - cmdFifo.io.pop.ready := False - cmdFifo.io.flush := False - - // FIFO that holds memory responses that have not been consumed yet. This will only fill when the - // pipeline stalls. - val rspFifo = new StreamFifoLowLatency(Rsp(), depth = irQueueSize, latency = 1) - rspFifo.io.push.valid := False - rspFifo.io.push.payload.assignDontCare() - rspFifo.io.pop.ready := False - rspFifo.io.flush := False - - // The AXI4 spec says that when the valid signal is raised, it should stay high until it is - // acknowledged by the corresponding ready signal. Since our client might not wait for a response - // (e.g., the fetch stage gets invalidated due to a jump), we have to buffer incoming commands - // to make sure we can finish them. - val currentCmd = new Area { - val valid = RegInit(False) - val accepted = RegInit(False) - val address = Reg(UInt(bus.config.addressWidth bits)) - } - - bus.cmd.payload.address := currentCmd.address - bus.cmd.valid := currentCmd.valid && !currentCmd.accepted - - // FIXME We always accept responses. This means that responses get dropped when rspFifo is full. - bus.rsp.ready := True - - // Address to restart fetching from - val restartAddress = Flow(UInt(bus.config.addressWidth bits)) - restartAddress.valid := False - restartAddress.payload.assignDontCare() - - // When restarting from a new address, we have to flush all in-flight commands. To make sure we - // can immediately issue a command for the restarted address, we keep track of how many commands - // are currently in-flight and simply ignore their responses. The maximum number of in-flight - // commands is the depth of cmdFifo plus one for nextCmd. - val restartCmdsToFlush = Reg(UInt(log2Up(cmdFifo.depth + 1) bits)).init(0) - val restarting = restartCmdsToFlush =/= 0 - - // Did we accept restartAddress? - val restartAccepted = False - - // Next command to be put on the bus - val nextCmd = Flow(UInt(bus.config.addressWidth bits)) - nextCmd.valid := False - nextCmd.payload.assignDontCare() - - // Are we popping a value from cmdFifo? - val poppingCmd = False - - when(restartAddress.valid) { - nextCmd.push(restartAddress.payload) - } elsewhen (currentCmd.valid) { - // We speculatively prefetch the next word when no new address is requested - nextCmd.push(currentCmd.address + bus.config.dataWidth / 8) - } - - when(nextCmd.valid) { - when(!currentCmd.valid || currentCmd.accepted || bus.cmd.ready) { - // We start a new command when 1) it is the first command, or 2) the previous command was - // previously accepted on the bus, or 3) the previous command is accepted during the current - // cycle. - currentCmd.address := nextCmd.payload - currentCmd.valid := True - currentCmd.accepted := False - - // We accepted a new command which is a restart if restartAddress is valid. - restartAccepted := restartAddress.valid - } - - when(!currentCmd.valid || currentCmd.accepted) { - // When the previous command was already accepted, we immediately put the next one on the bus - // to prevent a cycle delay. - bus.cmd.payload.address := nextCmd.payload - bus.cmd.valid := True - - // Make sure the status of currentCmd is correctly set when the command is immediately - // accepted. - currentCmd.accepted := bus.cmd.ready - } - } elsewhen (currentCmd.valid && !currentCmd.accepted) { - // When we don't have a new command, update the current command's status based on the bus. - currentCmd.accepted := bus.cmd.ready - } - - when(restartAccepted) { - // The amount of commands to flush when restarting is the amount currently in cmdFifo, minus one - // if we are popping from cmdFifo in this cycle, and plus one if currentCmd is valid but not yet - // accepted (as it will be pushed on cmdFifo later). - restartCmdsToFlush := - cmdFifo.io.occupancy - U(poppingCmd) + U(currentCmd.valid && !currentCmd.accepted) - } - - // Should a memory response be pushed on rspFifo? When it's accepted immediately by the fetch - // stage, we don't have to store it. - val pushRsp = True - - // Store a memory response - when(bus.rsp.valid) { - val rsp = Rsp() - - // Pop the address off the cmdFifo - rsp.address := cmdFifo.io.pop.payload.address - cmdFifo.io.pop.ready := True - poppingCmd := True - - // ir is on the bus - rsp.ir := bus.rsp.payload.rdata - - // Push it only when pushRsp is true - rspFifo.io.push.payload := rsp - rspFifo.io.push.valid := pushRsp - - when(restarting) { - // We just got a response for a command while restarting, one less to flush. - restartCmdsToFlush := restartCmdsToFlush - 1 - } - } - - // Push accepted commands on cmdFifo - when(bus.cmd.valid && bus.cmd.ready) { - val toPush = (currentCmd.valid && !currentCmd.accepted) ? currentCmd.address | nextCmd.payload - cmdFifo.io.push.payload.address := toPush - cmdFifo.io.push.valid := True - } - - // Return and remove the oldest response. This can be either from rspFifo or directly from the - // memory bus (in the latter case, it's simply not stored in rspFifo). - def popRsp(): Flow[Rsp] = { - val result = Flow(Rsp()) - result.valid := False - result.payload.assignDontCare() - - when(rspFifo.io.pop.valid) { - // If there is a response in rspFifo, return and pop it (toFlow sets ready). - result := rspFifo.io.pop.toFlow - } elsewhen (bus.rsp.valid) { - // Otherwise, return the response on the bus if there is one. - result.payload.address := cmdFifo.io.pop.payload.address - cmdFifo.io.pop.ready := True - result.payload.ir := bus.rsp.payload.rdata - result.valid := True - - // Make sure the response is not pushed on rspFifo. - pushRsp := False - } - - when(restarting) { - // Make sure responses are ignored while restarting. If we don't do this, we can get in a - // "restart loop" when near forward jumps happen because the restarted address is already - // in-flight and added again to cmdFifo. When the second response comes in, we have to restart - // again and this cycle continues until we have a jump to an address that is not yet in - // cmdFifo. - result.valid := False - } - - result - } - - def read(address: UInt): (Bool, UInt) = { - val valid = False - val ir = UInt(config.xlen bits).assignDontCare() - val restartNeeded = False - - val rsp = popRsp().setPartialName("nextIBusRsp") - - when(rsp.valid) { - when(rsp.payload.address === address) { - // We have a valid response that matches the requested address, return it. - valid := True - ir := rsp.payload.ir - } otherwise { - // We have a valid response that doesn't match the requested address. This means a jump - // happened and we need to restart fetching from the new address. - restartNeeded := True - } - } elsewhen (!cmdFifo.io.pop.valid || cmdFifo.io.pop.payload.address =/= address) { - // We don't have any responses and we either 1) don't have any in-flight requests, or 2) the - // oldest in-flight request is for an address we don't need. Restart fetching from the - // requested address. - restartNeeded := True - } - - when(restartNeeded && !restarting) { - // Request restart from address - restartAddress.push(address) - - // Clear rspFifo because it doesn't contain anything we can use. Note that for short forward - // jumps, it might actually contain the requested instruction. Maybe we could optimize here. - rspFifo.io.flush := True - } - - (valid, ir) - } -} diff --git a/src/main/scala/riscv/Services.scala b/src/main/scala/riscv/Services.scala index 8842deb..6ed1d92 100644 --- a/src/main/scala/riscv/Services.scala +++ b/src/main/scala/riscv/Services.scala @@ -43,6 +43,8 @@ trait MemoryService { * `observer` function is called in the context of the top-level Pipeline component. */ def observeDBus(observer: MemBusObserver): Unit + + def filterIBus(filter: MemBusFilter): Unit } trait FetchAddressTranslator { @@ -306,12 +308,14 @@ trait JumpService { trait BranchTargetPredictorService { def predictedPc(stage: Stage): UInt - def predictionForAddress( - address: UInt - ): UInt // TODO: should it be Flow[UInt] to signal no prediction instead of forcing + 4? def setPredictedPc(stage: Stage, pc: UInt): Unit } +trait PrefetchService { + def updatePrefetcherState(address: UInt, insignificantBits: Int): Unit + def getPrefetchTarget: UInt +} + trait TrapService { def trap(stage: Stage, cause: TrapCause): Unit def hasTrapped(stage: Stage): Bool diff --git a/src/main/scala/riscv/plugins/BranchTargetPredictor.scala b/src/main/scala/riscv/plugins/BranchTargetPredictor.scala index a2eff64..fde03f9 100644 --- a/src/main/scala/riscv/plugins/BranchTargetPredictor.scala +++ b/src/main/scala/riscv/plugins/BranchTargetPredictor.scala @@ -75,15 +75,4 @@ class BranchTargetPredictor( } } } - - override def predictionForAddress(address: UInt): UInt = { - val entry = predictorComponent.findEntry(address) - val result = UInt(config.xlen bits) - when(entry.valid) { - result := entry.payload - } otherwise { - result := address + 4 - } - result - } } diff --git a/src/main/scala/riscv/plugins/Cache.scala b/src/main/scala/riscv/plugins/Cache.scala new file mode 100644 index 0000000..9b17343 --- /dev/null +++ b/src/main/scala/riscv/plugins/Cache.scala @@ -0,0 +1,337 @@ +package riscv.plugins + +import riscv._ +import spinal.core._ +import spinal.lib._ + +class Cache( + sets: Int, + ways: Int, + busFilter: ((Stage, MemBus, MemBus) => Unit) => Unit, + prefetcher: Option[PrefetchService] = None +)(implicit config: Config) + extends Plugin[Pipeline] { + private val byteIndexBits = log2Up(config.xlen / 8) + private val wordIndexBits = log2Up(config.memBusWidth / config.xlen) + private val setIndexBits = log2Up(sets) + + private case class CacheEntry() extends Bundle { + val tag: UInt = UInt(config.xlen - (byteIndexBits + wordIndexBits + setIndexBits) bits) + val value: UInt = UInt(config.memBusWidth bits) + val age: UInt = UInt(log2Up(ways) bits) + val valid: Bool = Bool() + } + + private def getSetIndex(address: UInt): UInt = { + address(byteIndexBits + wordIndexBits, log2Up(sets) bits) + } + + private def getTagBits(address: UInt): UInt = { + address(byteIndexBits + wordIndexBits + setIndexBits until config.xlen) + } + + // get all address bits that determine whether two addresses fall into the same cache line + private def getSignificantBits(address: UInt): UInt = { + U(getTagBits(address) ## getSetIndex(address)) + } + + private def connect(_s: Stage, internal: MemBus, external: MemBus): Unit = { + val cacheArea = pipeline plug new Area { + private val idWidth = internal.config.idWidth + private val maxId = UInt(idWidth bits).maxValue.intValue() + + private val cache = Vec.fill(sets)(Vec.fill(ways)(RegInit(CacheEntry().getZero))) + + private val cacheHits = RegInit(UInt(config.xlen bits).getZero) + private val cacheMisses = RegInit(UInt(config.xlen bits).getZero) + + private val pendingPrefetch = RegInit(Flow(UInt(idWidth bits)).setIdle()) + + private def oldestWay(set: UInt): UInt = { + val result = UInt(log2Up(ways) bits) + result := 0 + for (i <- 0 until ways) { + when(cache(set)(i).age === ways - 1 || !cache(set)(i).valid) { + result := i + } + } + result + } + + private def increaseAgesUpTo(set: UInt, oldest: UInt): Unit = { + for (i <- 0 until ways) { + when(cache(set)(i).age < oldest) { + cache(set)(i).age := cache(set)(i).age + 1 + } + } + } + + private def decreaseAgesUntil(set: UInt, youngest: UInt): Unit = { + for (i <- 0 until ways) { + when(cache(set)(i).age > youngest) { + cache(set)(i).age := cache(set)(i).age - 1 + } + } + } + + private val sendingImmediateCmd = Bool() + private val sendingBufferedCmd = Reg(Bool()).init(False) + private val cmdBuffer = Reg(MemBusCmd(internal.config)) + + // rsp sending buffer + private val sendingRsp = Bool() + sendingRsp := False + private val rspBuffer = Reg(MemBusRsp(internal.config)) + private val returningCache = Reg(Bool()).init(False) + + // initial state: not sending or acknowledging anything + internal.rsp.valid := False + internal.rsp.payload.assignDontCare() + internal.cmd.ready := False + external.cmd.valid := False + external.cmd.payload.assignDontCare() + external.rsp.ready := False + + sendingImmediateCmd := False + + private case class OutstandingTracker() extends Bundle { + val address: UInt = UInt(config.xlen bits) + val storeInvalidated: Bool = Bool() + val pending: Bool = Bool() + } + + private val outstandingLoads = Vec.fill(maxId + 1)(RegInit(OutstandingTracker().getZero)) + + private def forwardRspToInternal(): Unit = { + sendingRsp := True + internal.rsp.valid := True + internal.rsp.payload := external.rsp.payload + } + + private def insertRspInCache(address: UInt): Unit = { + val setIndex = getSetIndex(address) + val tag = getTagBits(address) + + outstandingLoads(external.rsp.id).pending := False + outstandingLoads(external.rsp.id).storeInvalidated := False + when(!outstandingLoads(external.rsp.id).storeInvalidated) { + val way = oldestWay(setIndex) + cache(setIndex)(way).valid := True + cache(setIndex)(way).tag := tag + cache(setIndex)(way).value := external.rsp.payload.rdata + cache(setIndex)(way).age := U(0).resized + increaseAgesUpTo(setIndex, ways - 1) + } + external.rsp.ready := True + } + + // handling an incoming result from the memory + when(external.rsp.valid) { + val address = outstandingLoads(external.rsp.id).address + + prefetcher match { + case None => + forwardRspToInternal() + when(internal.rsp.ready) { + insertRspInCache(address) + } + case Some(_) => + when(pendingPrefetch.valid && pendingPrefetch.payload === external.rsp.id) { + // handle prefetch response without forwarding + pendingPrefetch.setIdle() + insertRspInCache(address) + } otherwise { + forwardRspToInternal() + when(internal.rsp.ready) { + insertRspInCache(address) + } + } + } + } + + private def returnFromCache(cacheLine: CacheEntry): Unit = { + // result served from cache + when(!returningCache) { + internal.cmd.ready := True + 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) { + returningCache := True + } + } otherwise { + returningCache := True + } + } + // if buffer is currently full, we do not ack the cmd, it will stay on the bus for the next cycle + } + + 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) { + returningCache := False + } + } + + private def initiateCmdForwarding(): Unit = { + when(!sendingBufferedCmd) { + sendingImmediateCmd := True + cacheMisses := cacheMisses + 1 + internal.cmd.ready := True + external.cmd.valid := True + + cmdBuffer := internal.cmd.payload + external.cmd.payload := internal.cmd.payload + + if (internal.config.readWrite) { + when(!internal.cmd.write) { + outstandingLoads(internal.cmd.id).address := internal.cmd.address + outstandingLoads(internal.cmd.id).pending := True + } + } else { + outstandingLoads(internal.cmd.id).address := internal.cmd.address + outstandingLoads(internal.cmd.id).pending := True + } + when(!external.cmd.ready) { + sendingBufferedCmd := True + } + } + } + + when(sendingBufferedCmd) { + external.cmd.valid := True + external.cmd.payload := cmdBuffer + when(external.cmd.ready) { + sendingBufferedCmd := False + } + } + + private def wayForAddress(address: UInt): Flow[UInt] = { + val set = cache(getSetIndex(address)) + val tag = getTagBits(address) + val result = Flow(UInt(log2Up(ways) bits)) + result.setIdle() + for (i <- 0 until ways) { + when(set(i).valid && set(i).tag === tag) { + result.push(i) + } + } + result + } + + prefetcher foreach { pref => + when(!sendingBufferedCmd && !sendingImmediateCmd && !pendingPrefetch.valid) { + val prefetchAddress = pref.getPrefetchTarget + + val targetWay = wayForAddress(prefetchAddress) + + val emptySlot = Flow(UInt(idWidth bits)).setIdle() + // send a prefetch command on the unused external bus if there's an unused ID and no pending prefetch + for (i <- 0 until outstandingLoads.length) { + when(!outstandingLoads(i).pending) { + emptySlot.push(i) + } + } + + when(emptySlot.valid && !targetWay.valid) { + pendingPrefetch := emptySlot + + external.cmd.valid := True + external.cmd.address := prefetchAddress + external.cmd.id := emptySlot.payload + cmdBuffer := external.cmd + + outstandingLoads(emptySlot.payload).address := prefetchAddress + outstandingLoads(emptySlot.payload).pending := True + + when(!external.cmd.ready) { + sendingBufferedCmd := True + } + } + } + } + + private def getResult(address: UInt): Unit = { + prefetcher foreach { pref => + pref.updatePrefetcherState(internal.cmd.address, byteIndexBits + wordIndexBits) + } + + when(!outstandingLoads(internal.cmd.id).pending) { + val targetWay = wayForAddress(address) + val setIndex = getSetIndex(address) + val cacheSet = cache(setIndex) + val tagBits = getTagBits(address) + + when(targetWay.valid) { + cacheSet(targetWay.payload).age := U(0).resized + increaseAgesUpTo(setIndex, cacheSet(targetWay.payload).age) + returnFromCache(cacheSet(targetWay.payload)) + cacheHits := cacheHits + 1 + } otherwise { + val alreadyPending = False + for (i <- 0 until outstandingLoads.length) { + val load = outstandingLoads(i) + when( + getSignificantBits(load.address) === U( + tagBits ## setIndex + ) && load.pending && !load.storeInvalidated + ) { + alreadyPending := True + } + } + when(!alreadyPending) { + // forward cmd to external bus if there's no pending load for the same cache line + initiateCmdForwarding() + } + } + } + } + + // handling a load/write request from the CPU + when(internal.cmd.valid) { + val indexBits = getSetIndex(internal.cmd.address) + val tagBits = getTagBits(internal.cmd.address) + + if (internal.config.readWrite) { + when(internal.cmd.write) { + // write command: invalidates line and forwards to external bus + for (i <- 0 until ways) { + when(cache(indexBits)(i).tag === tagBits) { + cache(indexBits)(i).valid := False + cache(indexBits)(i).age := ways - 1 + decreaseAgesUntil(indexBits, cache(indexBits)(i).age) + } + } + + for (i <- 0 until outstandingLoads.length) { + when( + getSignificantBits(outstandingLoads(i).address) === getSignificantBits( + internal.cmd.address + ) && outstandingLoads(i).pending + ) { + outstandingLoads(i).storeInvalidated := True + } + } + + initiateCmdForwarding() + // if currently forwarding a cmd, we do not ack it, it will stay on the bus for the next cycle + } otherwise { + getResult(internal.cmd.address) + } + } else { + getResult(internal.cmd.address) + } + } + } + cacheArea.setName("cache_" + external.name) + } + + override def build(): Unit = { + busFilter(connect) + } +} diff --git a/src/main/scala/riscv/plugins/DynamicMemoryBackbone.scala b/src/main/scala/riscv/plugins/DynamicMemoryBackbone.scala deleted file mode 100644 index 970838f..0000000 --- a/src/main/scala/riscv/plugins/DynamicMemoryBackbone.scala +++ /dev/null @@ -1,220 +0,0 @@ -package riscv.plugins - -import riscv._ -import spinal.core._ -import spinal.lib._ - -import scala.collection.mutable - -class DynamicMemoryBackbone(stageCount: Int)(implicit config: Config) - extends Plugin - with MemoryService { - private val idWidth: BitCount = log2Up(stageCount) bits - - private var externalIBus: MemBus = null - private var internalIBus: MemBus = null - private var externalDBus: MemBus = null - private var internalReadDBuses: Seq[MemBus] = null - private var internalWriteDBus: MemBus = null - private var internalReadDBusStages: Seq[Stage] = null - private var internalWriteDBusStage: Stage = null - private var dbusFilter: Option[MemBusFilter] = None - private val dbusObservers = mutable.ArrayBuffer[MemBusObserver]() - - override def finish(): Unit = { - def dummyConnect(bus: MemBus) = { - bus.cmd.valid := False - bus.cmd.payload.assignDontCare() - bus.rsp.ready := False - } - - // IBUS - pipeline plug new Area { - externalIBus = master(new MemBus(config.ibusConfig, idWidth)).setName("ibus") - - if (internalIBus != null) { - externalIBus <> internalIBus - } else { - internalIBus = externalIBus - dummyConnect(internalIBus) - } - } - - // DBUS - if (dbusFilter.isEmpty) { - dbusFilter = Some((_, idbus, edbus) => { - idbus <> edbus - }) - } - - pipeline plug new Area { - externalDBus = master(new MemBus(config.dbusConfig, idWidth)).setName("dbus") - - val pendingCount = Vec.fill(stageCount)(RegInit(UInt(3 bits).getZero)) // 3 bits: guess - val increaseCount = Vec.fill(stageCount)(False) - val decreaseCount = Vec.fill(stageCount)(False) - - // Create a RW version of the RO internalReadDBus so that we can use it with - // StreamArbiterFactory - val fullDBusCmds = internalReadDBuses.zipWithIndex.map { case (internalReadDBus, index) => - val fullReadDBusCmd = Stream(MemBusCmd(config.dbusConfig, idWidth)) - fullReadDBusCmd.valid := internalReadDBus.cmd.valid - fullReadDBusCmd.id := internalReadDBus.cmd.id - internalReadDBus.cmd.ready := fullReadDBusCmd.ready - fullReadDBusCmd.write := False - fullReadDBusCmd.wmask.assignDontCare() - fullReadDBusCmd.wdata.assignDontCare() - fullReadDBusCmd.address := internalReadDBus.cmd.address - - val busValid = Bool() - busValid := False - - // only set valid bit for the corresponding load bus - when(externalDBus.rsp.id === index && externalDBus.rsp.valid) { - when(pendingCount(index) === 1 && !internalReadDBus.cmd.valid) { // only forward the response if there's no contending load and it also wasn't issued in this same cycle - busValid := externalDBus.rsp.valid - } - decreaseCount(index) := True - } - - busValid <> internalReadDBus.rsp.valid - externalDBus.rsp.payload <> internalReadDBus.rsp.payload - - fullReadDBusCmd - - // TODO filter and observers - } - - val rspReady = Bool() - rspReady := False - - // check whether the correct load bus is ready to receive - when(externalDBus.rsp.valid) { - rspReady := internalReadDBuses(externalDBus.rsp.id.resized).rsp.ready - } - - externalDBus.rsp.ready <> rspReady - - // TODO: is it possible to do the following with an arbiter instead of this manual mess? - - val cmds = fullDBusCmds :+ internalWriteDBus.cmd - - val cmdValid = Bool() - cmdValid := False - val cmdAddress = UInt(config.xlen bits) - cmdAddress.assignDontCare() - val cmdId = UInt(idWidth) - cmdId.assignDontCare() - val cmdWrite = Bool() - cmdWrite := False - val cmdWdata = UInt(config.dbusConfig.dataWidth bits) - cmdWdata.assignDontCare() - val cmdWmask = Bits(config.dbusConfig.dataWidth / 8 bits) - cmdWmask.assignDontCare() - - var context = when(False) {} - - cmds.zipWithIndex.foreach { case (cmd, index) => - val ready = Bool() - ready := False - - when(externalDBus.cmd.id === index) { - ready := externalDBus.cmd.ready - } - - cmd.ready := ready - - context = context.elsewhen( - cmd.valid && ((pendingCount(index) < pendingCount(index).maxValue) || cmd.write) - ) { // prevent overflowing the pending counter for loads - cmdValid := True - cmdAddress := cmd.address - cmdId := index - cmdWrite := cmd.write - cmdWdata := cmd.wdata - cmdWmask := cmd.wmask - when(ready) { - increaseCount(index) := True - } - } - } - - externalDBus.cmd.valid <> cmdValid - externalDBus.cmd.address <> cmdAddress - externalDBus.cmd.id <> cmdId - externalDBus.cmd.write <> cmdWrite - externalDBus.cmd.wdata <> cmdWdata - externalDBus.cmd.wmask <> cmdWmask - - for (i <- 0 until stageCount) { - when(increaseCount(i) && !decreaseCount(i)) { - pendingCount(i) := pendingCount(i) + 1 - } - when(!increaseCount(i) && decreaseCount(i)) { - pendingCount(i) := pendingCount(i) - 1 - } - } - } - } - - override def getExternalIBus: MemBus = { - assert(externalIBus != null) - externalIBus - } - - override def getExternalDBus: MemBus = { - assert(externalDBus != null) - externalDBus - } - - override def createInternalIBus(stage: Stage): MemBus = { - assert(internalIBus == null) - - stage plug new Area { - internalIBus = master(new MemBus(config.ibusConfig, idWidth)) - internalIBus.cmd.id.assignDontCare() - } - - internalIBus - } - - override def createInternalDBus( - readStages: Seq[Stage], - writeStage: Stage - ): (Seq[MemBus], MemBus) = { - assert(!readStages.contains(writeStage)) - - internalReadDBuses = readStages.map(readStage => { - val readArea = readStage plug new Area { - val dbus = master(new MemBus(config.readDbusConfig, idWidth)) - } - readArea.dbus - }) - - writeStage plug new Area { - internalWriteDBus = master(new MemBus(config.dbusConfig, idWidth)) - } - - pipeline plug { - internalWriteDBus.rsp.valid := False - } - - internalReadDBusStages = readStages - internalWriteDBusStage = writeStage - - (internalReadDBuses, internalWriteDBus) - } - - override def getDBusStages: Seq[Stage] = { - internalReadDBusStages.filter(_ != null).distinct // TODO: not sure what this does - } - - override def filterDBus(filter: MemBusFilter): Unit = { - assert(dbusFilter.isEmpty) - dbusFilter = Some(filter) - } - - override def observeDBus(observer: MemBusObserver): Unit = { - dbusObservers += observer - } -} diff --git a/src/main/scala/riscv/plugins/NoPredictionPredictor.scala b/src/main/scala/riscv/plugins/NoPredictionPredictor.scala index 0cf969b..ce88a97 100644 --- a/src/main/scala/riscv/plugins/NoPredictionPredictor.scala +++ b/src/main/scala/riscv/plugins/NoPredictionPredictor.scala @@ -27,8 +27,4 @@ class NoPredictionPredictor(fetchStage: Stage, executeStage: Stage) override def setPredictedPc(stage: Stage, pc: UInt): Unit = { stage.input(Data.PREDICTED_PC) := pc } - - override def predictionForAddress(address: UInt): UInt = { - address + 4 - } } diff --git a/src/main/scala/riscv/plugins/StaticMemoryBackbone.scala b/src/main/scala/riscv/plugins/StaticMemoryBackbone.scala deleted file mode 100644 index d4c32d3..0000000 --- a/src/main/scala/riscv/plugins/StaticMemoryBackbone.scala +++ /dev/null @@ -1,101 +0,0 @@ -package riscv.plugins - -import riscv._ -import spinal.core._ -import spinal.lib._ - -import scala.collection.mutable - -class StaticMemoryBackbone(implicit config: Config) extends Plugin with MemoryService { - private var externalIBus: MemBus = null - private var internalIBus: MemBus = null - private var externalDBus: MemBus = null - private var internalDBus: MemBus = null - private var internalDBusStage: Stage = null - private var dbusFilter: Option[MemBusFilter] = None - private val dbusObservers = mutable.ArrayBuffer[MemBusObserver]() - - override def finish(): Unit = { - def dummyConnect(bus: MemBus) = { - bus.cmd.valid := False - bus.cmd.payload.assignDontCare() - bus.rsp.ready := False - } - - // IBUS - pipeline plug new Area { - externalIBus = master(new MemBus(config.ibusConfig, 0 bits)).setName("ibus") - - if (internalIBus != null) { - externalIBus <> internalIBus - } else { - internalIBus = externalIBus - dummyConnect(internalIBus) - } - } - - // DBUS - if (dbusFilter.isEmpty) { - dbusFilter = Some((_, idbus, edbus) => { - idbus <> edbus - }) - } - - pipeline plug new Area { - externalDBus = master(new MemBus(config.dbusConfig, 0 bits)).setName("dbus") - dbusFilter.foreach(_(internalDBusStage, internalDBus, externalDBus)) - dbusObservers.foreach(_(internalDBusStage, internalDBus)) - } - } - - override def getExternalIBus: MemBus = { - assert(externalIBus != null) - externalIBus - } - - override def getExternalDBus: MemBus = { - assert(externalDBus != null) - externalDBus - } - - override def createInternalIBus(stage: Stage): MemBus = { - assert(internalIBus == null) - - stage plug new Area { - internalIBus = master(new MemBus(config.ibusConfig, 0 bits)) - internalIBus.cmd.id.assignDontCare() - } - - internalIBus - } - - override def createInternalDBus( - readStages: Seq[Stage], - writeStage: Stage - ): (Seq[MemBus], MemBus) = { - assert(readStages.size == 1) - assert(readStages.head == writeStage) - - internalDBusStage = readStages.head - - internalDBusStage plug new Area { - val dbus = master(new MemBus(config.dbusConfig, 0 bits)) - internalDBus = dbus - } - - (Seq(internalDBus), internalDBus) - } - - override def getDBusStages: Seq[Stage] = { - Seq(internalDBusStage).filter(_ != null).distinct // TODO: not sure what this does - } - - override def filterDBus(filter: MemBusFilter): Unit = { - assert(dbusFilter.isEmpty) - dbusFilter = Some(filter) - } - - override def observeDBus(observer: MemBusObserver): Unit = { - dbusObservers += observer - } -} diff --git a/src/main/scala/riscv/plugins/cheri/Core.scala b/src/main/scala/riscv/plugins/cheri/Core.scala index e420503..501e2ab 100644 --- a/src/main/scala/riscv/plugins/cheri/Core.scala +++ b/src/main/scala/riscv/plugins/cheri/Core.scala @@ -1,7 +1,6 @@ package riscv.plugins.cheri import riscv._ -import riscv.plugins.StaticMemoryBackbone import riscv.sim._ import riscv.soc._ import spinal.core._ @@ -33,13 +32,13 @@ object createCheriPipeline { new rvp.scheduling.static.DataHazardResolver(firstRsReadStage = pipeline.execute), new rvp.TrapHandler(pipeline.writeback), new rvp.TrapStageInvalidator, // TODO: ? - new StaticMemoryBackbone, - new rvp.Fetcher(pipeline.fetch), + new rvp.memory.StaticMemoryBackbone, + new rvp.memory.Fetcher(pipeline.fetch), new rvp.Decoder(pipeline.decode), new rvp.RegisterFileAccessor(pipeline.decode, pipeline.writeback), new rvp.IntAlu(Set(pipeline.execute)), new rvp.Shifter(Set(pipeline.execute)), - new rvp.Lsu(Set(pipeline.memory), Seq(pipeline.memory), pipeline.memory), + new rvp.memory.Lsu(Set(pipeline.memory), Seq(pipeline.memory), pipeline.memory), new rvp.BranchUnit(Set(pipeline.execute)), new rvp.scheduling.static.PcManager(0x80000000L), new rvp.CsrFile(pipeline.writeback, pipeline.writeback), // TODO: ugly diff --git a/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala new file mode 100644 index 0000000..6f244e0 --- /dev/null +++ b/src/main/scala/riscv/plugins/memory/DynamicMemoryBackbone.scala @@ -0,0 +1,193 @@ +package riscv.plugins.memory + +import riscv._ +import spinal.core._ +import spinal.lib._ + +import scala.collection.mutable + +class DynamicMemoryBackbone(stageCount: Int)(implicit config: Config) + extends MemoryBackbone + with Resettable { + + private var activeFlush: Bool = null + + override def build(): Unit = { + pipeline plug new Area { + val activeFlushCopy = Bool() + activeFlush = activeFlushCopy + activeFlush := False + } + } + + override def finish(): Unit = { + super.finish() + + pipeline plug new Area { + externalDBus = master(new MemBus(config.dbusConfig)).setName("dbus") + + private val unifiedInternalDBus = Stream(MemBus(config.dbusConfig)) + + unifiedInternalDBus.cmd.valid := False + unifiedInternalDBus.cmd.address.assignDontCare() + unifiedInternalDBus.cmd.id.assignDontCare() + unifiedInternalDBus.cmd.write := False + unifiedInternalDBus.cmd.wdata.assignDontCare() + unifiedInternalDBus.cmd.wmask.assignDontCare() + + unifiedInternalDBus.rsp.ready := False + + private case class IdMeta() extends Bundle { + val stageIndex = UInt(config.dbusConfig.idWidth bits) + val cmdSent = Bool() + val rspReceived = Bool() + val invalidated = Bool() + } + + private val currentlyInserting = Flow(IdMeta()) + currentlyInserting.valid := False + currentlyInserting.payload.assignDontCare() + + private val sameCycleReturn = Bool() + sameCycleReturn := False + + private val nextId = Counter(config.dbusConfig.idWidth bits) + private val busId2StageIndex = Vec.fill( + UInt(config.dbusConfig.idWidth bits).maxValue.intValue() + 1 + )(RegInit(IdMeta().getZero)) + + for (i <- 0 until stageCount) { + when(activeFlush) { + busId2StageIndex(i).invalidated := True + } + } + // when putting a cmd on external, add stage index with counter.next + // when pipeline is invalidated, invalidate all entries in here + // when getting a response, if it's invalid, throw it away + // when counter is full (all entries are waiting), don't accept any cmd + + private val fullDBusCmds = internalReadDBuses.zipWithIndex.map { + case (internalReadDBus, index) => + val fullReadDBusCmd = Stream(MemBusCmd(config.dbusConfig)) + fullReadDBusCmd.valid := internalReadDBus.cmd.valid + fullReadDBusCmd.id := internalReadDBus.cmd.id + internalReadDBus.cmd.ready := False + fullReadDBusCmd.write := False + fullReadDBusCmd.wmask.assignDontCare() + fullReadDBusCmd.wdata.assignDontCare() + fullReadDBusCmd.address := internalReadDBus.cmd.address + + val busValid = Bool() + busValid := False + + when( + currentlyInserting.payload.stageIndex === index && currentlyInserting.valid && !activeFlush + ) { + internalReadDBus.cmd.ready := True + } + + // only set valid bit for the corresponding load bus + when(unifiedInternalDBus.rsp.valid) { + when( + currentlyInserting.valid && currentlyInserting.stageIndex === index && nextId === unifiedInternalDBus.rsp.id + ) { + sameCycleReturn := True + busId2StageIndex(unifiedInternalDBus.rsp.id).rspReceived := True + busId2StageIndex(unifiedInternalDBus.rsp.id).cmdSent := False // free up slot + busValid := True + } + when( + busId2StageIndex(unifiedInternalDBus.rsp.id).cmdSent && busId2StageIndex( + unifiedInternalDBus.rsp.id + ).stageIndex === index + ) { + busId2StageIndex(unifiedInternalDBus.rsp.id).rspReceived := True + busId2StageIndex(unifiedInternalDBus.rsp.id).cmdSent := False // free up slot + when(!busId2StageIndex(unifiedInternalDBus.rsp.id).invalidated) { + busValid := True + } + } + } + + internalReadDBus.rsp.valid := busValid + internalReadDBus.rsp.payload := unifiedInternalDBus.rsp.payload + + fullReadDBusCmd + } + + // check whether the correct load bus is ready to receive + when(unifiedInternalDBus.rsp.valid) { + unifiedInternalDBus.rsp.ready := internalReadDBuses( + busId2StageIndex(unifiedInternalDBus.rsp.id).stageIndex + ).rsp.ready + } + + private var context = when(False) {} + + // TODO: is it possible to do the following with an arbiter instead of this manual mess? + + private val cmds = fullDBusCmds :+ internalWriteDBus.cmd + + internalWriteDBus.cmd.ready := unifiedInternalDBus.cmd.ready && unifiedInternalDBus.cmd.write + + cmds.zipWithIndex.foreach { case (cmd, index) => + context = + context.elsewhen(cmd.valid && ((!busId2StageIndex(nextId).cmdSent) || cmd.write)) { + unifiedInternalDBus.cmd.valid := True + unifiedInternalDBus.cmd.address := cmd.address + unifiedInternalDBus.cmd.id := nextId + unifiedInternalDBus.cmd.write := cmd.write + unifiedInternalDBus.cmd.wdata := cmd.wdata + unifiedInternalDBus.cmd.wmask := cmd.wmask + when(unifiedInternalDBus.cmd.ready) { + when(!cmd.write) { + nextId.increment() + busId2StageIndex(nextId).stageIndex := U(index).resized + when(!sameCycleReturn) { + busId2StageIndex(nextId).cmdSent := True + } + busId2StageIndex(nextId).rspReceived := False + busId2StageIndex(nextId).invalidated := activeFlush + currentlyInserting.valid := True + currentlyInserting.payload.stageIndex := index + } + } + } + } + + dbusFilter.foreach(_(internalWriteDBusStage, unifiedInternalDBus, externalDBus)) + dbusObservers.foreach(_(internalWriteDBusStage, unifiedInternalDBus)) + } + } + + override def createInternalDBus( + readStages: Seq[Stage], + writeStage: Stage + ): (Seq[MemBus], MemBus) = { + assert(!readStages.contains(writeStage)) + + internalReadDBuses = readStages.map(readStage => { + val readArea = readStage plug new Area { + val dbus = master(new MemBus(config.readDbusConfig)) + } + readArea.dbus + }) + + writeStage plug new Area { + internalWriteDBus = master(new MemBus(config.dbusConfig)) + } + + pipeline plug { + internalWriteDBus.rsp.valid := False + } + + internalReadDBusStages = readStages + internalWriteDBusStage = writeStage + + (internalReadDBuses, internalWriteDBus) + } + + override def pipelineReset(): Unit = { + activeFlush := True + } +} diff --git a/src/main/scala/riscv/plugins/Fetcher.scala b/src/main/scala/riscv/plugins/memory/Fetcher.scala similarity index 84% rename from src/main/scala/riscv/plugins/Fetcher.scala rename to src/main/scala/riscv/plugins/memory/Fetcher.scala index c86bbfa..28f9098 100644 --- a/src/main/scala/riscv/plugins/Fetcher.scala +++ b/src/main/scala/riscv/plugins/memory/Fetcher.scala @@ -1,11 +1,9 @@ -package riscv.plugins +package riscv.plugins.memory import riscv._ - import spinal.core._ -import spinal.lib._ -class Fetcher(fetchStage: Stage, ibusLatency: Int = 2) extends Plugin[Pipeline] with FetchService { +class Fetcher(fetchStage: Stage) extends Plugin[Pipeline] with FetchService { private var addressTranslator = new FetchAddressTranslator { override def translate(stage: Stage, address: UInt): UInt = { address @@ -19,7 +17,7 @@ class Fetcher(fetchStage: Stage, ibusLatency: Int = 2) extends Plugin[Pipeline] import fetchStage._ val ibus = pipeline.service[MemoryService].createInternalIBus(fetchStage) - val ibusCtrl = new IBusControl(ibus, ibusLatency) + val ibusCtrl = new MemBusControl(ibus) arbitration.isReady := False diff --git a/src/main/scala/riscv/plugins/Lsu.scala b/src/main/scala/riscv/plugins/memory/Lsu.scala similarity index 97% rename from src/main/scala/riscv/plugins/Lsu.scala rename to src/main/scala/riscv/plugins/memory/Lsu.scala index c6a5496..743890d 100644 --- a/src/main/scala/riscv/plugins/Lsu.scala +++ b/src/main/scala/riscv/plugins/memory/Lsu.scala @@ -1,8 +1,7 @@ -package riscv.plugins +package riscv.plugins.memory import riscv._ import spinal.core._ -import spinal.lib._ class Lsu(addressStages: Set[Stage], loadStages: Seq[Stage], storeStage: Stage) extends Plugin[Pipeline] @@ -228,20 +227,20 @@ class Lsu(addressStages: Set[Stage], loadStages: Seq[Stage], storeStage: Stage) def checkAccessWidth(accessWidth: SpinalEnumCraft[LsuAccessWidth.type], address: UInt) = { val misaligned = Bool() - val baseMask = Bits(config.xlen / 8 bits) + val baseMask = Bits(config.memBusWidth / 8 bits) switch(accessWidth) { is(LsuAccessWidth.B) { misaligned := False - baseMask := B"0001" + baseMask := B"0001".resized } is(LsuAccessWidth.H) { misaligned := (address & 1) =/= 0 - baseMask := B"0011" + baseMask := B"0011".resized } is(LsuAccessWidth.W) { misaligned := (address & 3) =/= 0 - baseMask := B"1111" + baseMask := B"1111".resized } } (misaligned, baseMask) @@ -381,7 +380,9 @@ class Lsu(addressStages: Set[Stage], loadStages: Seq[Stage], storeStage: Stage) val (misaligned, baseMask) = checkAccessWidth(accessWidth, address) - val mask = baseMask |<< address(1 downto 0) + val addressOffset = log2Up(config.memBusWidth / 8) - 1 + + val mask = baseMask |<< address(addressOffset downto 0) when(isActive && misaligned) { if (pipeline.hasService[TrapService]) { @@ -428,7 +429,7 @@ class Lsu(addressStages: Set[Stage], loadStages: Seq[Stage], storeStage: Stage) } } - val accepted = dbusCtrl.write(busAddress, data, mask) + val accepted = dbusCtrl.write(busAddress, data.resized, mask) arbitration.isReady := accepted formal.lsuOnStore(storeStage, busAddress, mask, data) diff --git a/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala new file mode 100644 index 0000000..e2d56f3 --- /dev/null +++ b/src/main/scala/riscv/plugins/memory/MemoryBackbone.scala @@ -0,0 +1,95 @@ +package riscv.plugins.memory + +import riscv._ +import spinal.core._ +import spinal.lib._ + +import scala.collection.mutable + +abstract class MemoryBackbone(implicit config: Config) extends Plugin with MemoryService { + + var externalIBus: MemBus = null + var internalIBus: MemBus = null + var externalDBus: MemBus = null + var internalReadDBuses: Seq[MemBus] = null + var internalWriteDBus: MemBus = null + var internalReadDBusStages: Seq[Stage] = null + var internalWriteDBusStage: Stage = null + var dbusFilter: Option[MemBusFilter] = None + var ibusFilter: Option[MemBusFilter] = None + val dbusObservers = mutable.ArrayBuffer[MemBusObserver]() + + def dummyConnect(bus: MemBus) = { + bus.cmd.valid := False + bus.cmd.payload.assignDontCare() + bus.rsp.ready := False + } + + def setupIBus(): Unit = { + pipeline plug new Area { + externalIBus = master(new MemBus(config.ibusConfig)).setName("ibus") + + if (internalIBus != null) { + if (ibusFilter.isEmpty) { + ibusFilter = Some((_, iibus, eibus) => { + iibus <> eibus + }) + } + ibusFilter.foreach(_(null, internalIBus, externalIBus)) + } else { + internalIBus = externalIBus + dummyConnect(internalIBus) + } + } + } + + override def finish(): Unit = { + setupIBus() + + // DBUS + if (dbusFilter.isEmpty) { + dbusFilter = Some((_, idbus, edbus) => { + idbus <> edbus + }) + } + } + + override def getExternalIBus: MemBus = { + assert(externalIBus != null) + externalIBus + } + + override def getExternalDBus: MemBus = { + assert(externalDBus != null) + externalDBus + } + + override def createInternalIBus(stage: Stage): MemBus = { + assert(internalIBus == null) + + stage plug new Area { + internalIBus = master(new MemBus(config.ibusConfig)) + internalIBus.cmd.id.assignDontCare() + } + + internalIBus + } + + override def getDBusStages: Seq[Stage] = { + internalReadDBusStages.filter(_ != null).distinct + } + + override def filterDBus(filter: MemBusFilter): Unit = { + assert(dbusFilter.isEmpty) + dbusFilter = Some(filter) + } + + override def filterIBus(filter: MemBusFilter): Unit = { + assert(ibusFilter.isEmpty) + ibusFilter = Some(filter) + } + + override def observeDBus(observer: MemBusObserver): Unit = { + dbusObservers += observer + } +} diff --git a/src/main/scala/riscv/plugins/memory/SequentialInstructionPrefetcher.scala b/src/main/scala/riscv/plugins/memory/SequentialInstructionPrefetcher.scala new file mode 100644 index 0000000..b514b71 --- /dev/null +++ b/src/main/scala/riscv/plugins/memory/SequentialInstructionPrefetcher.scala @@ -0,0 +1,23 @@ +package riscv.plugins.memory + +import riscv._ +import spinal.core._ + +class SequentialInstructionPrefetcher(implicit config: Config) extends Plugin with PrefetchService { + + private var nextTarget: UInt = null + + override def setup(): Unit = { + pipeline plug new Area { + nextTarget = RegInit(UInt(config.xlen bits).getZero) + } + } + + override def updatePrefetcherState(address: UInt, insignificantBits: Int): Unit = { + nextTarget := ((address >> insignificantBits) + 1) << insignificantBits + } + + override def getPrefetchTarget: UInt = { + nextTarget + } +} diff --git a/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala b/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala new file mode 100644 index 0000000..d2f210b --- /dev/null +++ b/src/main/scala/riscv/plugins/memory/StaticMemoryBackbone.scala @@ -0,0 +1,38 @@ +package riscv.plugins.memory + +import riscv._ +import spinal.core._ +import spinal.lib._ + +import scala.collection.mutable + +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") + dbusFilter.foreach(_(internalWriteDBusStage, internalWriteDBus, externalDBus)) + dbusObservers.foreach(_(internalWriteDBusStage, internalWriteDBus)) + } + } + + override def createInternalDBus( + readStages: Seq[Stage], + writeStage: Stage + ): (Seq[MemBus], MemBus) = { + assert(readStages.size == 1) + assert(readStages.head == writeStage) + + internalWriteDBusStage = readStages.head + + internalWriteDBusStage plug new Area { + val dbus = master(new MemBus(config.dbusConfig)) + internalWriteDBus = dbus + } + + (Seq(internalWriteDBus), internalWriteDBus) + } + +} diff --git a/src/main/scala/riscv/plugins/scheduling/dynamic/PcManager.scala b/src/main/scala/riscv/plugins/scheduling/dynamic/PcManager.scala index 47e6049..b332ba0 100644 --- a/src/main/scala/riscv/plugins/scheduling/dynamic/PcManager.scala +++ b/src/main/scala/riscv/plugins/scheduling/dynamic/PcManager.scala @@ -77,9 +77,7 @@ class PcManager() extends Plugin[DynamicPipeline] with JumpService { val staticPcManager = pipeline.issuePipeline.service[JumpService] staticPcManager.jump(jumpStage.output(pipeline.data.NEXT_PC)) - pipeline.rob.reset() - - for (component <- pipeline.components) { + for (component <- pipeline.resettables) { component.pipelineReset() } } diff --git a/src/main/scala/riscv/plugins/scheduling/dynamic/ReorderBuffer.scala b/src/main/scala/riscv/plugins/scheduling/dynamic/ReorderBuffer.scala index 28f385c..6e39b4a 100644 --- a/src/main/scala/riscv/plugins/scheduling/dynamic/ReorderBuffer.scala +++ b/src/main/scala/riscv/plugins/scheduling/dynamic/ReorderBuffer.scala @@ -47,7 +47,8 @@ class ReorderBuffer( metaRegisters: DynBundle[PipelineData[Data]] )(implicit config: Config) extends Area - with CdbListener { + with CdbListener + with Resettable { def capacity: Int = robCapacity def indexBits: BitCount = log2Up(capacity) bits @@ -70,6 +71,10 @@ class ReorderBuffer( isFull := False } + private def byte2WordAddress(address: UInt) = { + address(config.xlen - 1 downto log2Up(config.xlen / 8)) + } + private def isValidAbsoluteIndex(index: UInt): Bool = { val ret = Bool() @@ -196,7 +201,7 @@ class ReorderBuffer( val found = Bool() found := False - val wordAddress = config.dbusConfig.byte2WordAddress(address) + val wordAddress = byte2WordAddress(address) for (nth <- 0 until capacity) { val entry = robEntries(nth) @@ -207,7 +212,7 @@ class ReorderBuffer( val entryIsStore = lsuService.operationOfBundle(entry.registerMap) === LsuOperationType.STORE val entryAddressValid = lsuService.addressValidOfBundle(entry.registerMap) val entryAddress = lsuService.addressOfBundle(entry.registerMap) - val entryWordAddress = config.dbusConfig.byte2WordAddress(entryAddress) + val entryWordAddress = byte2WordAddress(entryAddress) val addressesMatch = entryWordAddress === wordAddress val isOlder = relativeIndexForAbsolute(index) < relativeIndexForAbsolute(robIndex) @@ -281,4 +286,8 @@ class ReorderBuffer( } } } + + override def pipelineReset(): Unit = { + reset() + } } diff --git a/src/main/scala/riscv/plugins/scheduling/dynamic/Scheduler.scala b/src/main/scala/riscv/plugins/scheduling/dynamic/Scheduler.scala index ed5dc95..28c939e 100644 --- a/src/main/scala/riscv/plugins/scheduling/dynamic/Scheduler.scala +++ b/src/main/scala/riscv/plugins/scheduling/dynamic/Scheduler.scala @@ -70,7 +70,8 @@ class Scheduler() extends Plugin[DynamicPipeline] with IssueService { rs.dispatchStream >> dispatchBus.inputs(index) } - pipeline.components = reservationStations ++ loadManagers :+ dispatcher + pipeline.resettables = + reservationStations ++ loadManagers :+ dispatcher :+ pipeline.backbone :+ pipeline.rob // Dispatch private val issueStage = pipeline.issuePipeline.stages.last diff --git a/src/main/scala/riscv/soc/SoC.scala b/src/main/scala/riscv/soc/SoC.scala index 10a84d7..3965b1b 100644 --- a/src/main/scala/riscv/soc/SoC.scala +++ b/src/main/scala/riscv/soc/SoC.scala @@ -34,7 +34,7 @@ class SoC( val axi = ramType match { case RamType.ExternalAxi4(size) => val axiConfig = Axi4SharedOnChipRam.getAxiConfig( - dataWidth = config.xlen, + dataWidth = config.memBusWidth, byteCount = size, idWidth = 4 ) @@ -71,7 +71,7 @@ class SoC( case RamType.OnChipRam(size, initHexFile) => val ram = Axi4SharedOnChipRam( byteCount = size, - dataWidth = config.xlen, + dataWidth = config.memBusWidth, idWidth = 4 ) @@ -81,7 +81,7 @@ class SoC( val apbBridge = Axi4SharedToApb3Bridge( addressWidth = config.dbusConfig.addressWidth, - dataWidth = config.dbusConfig.dataWidth, + dataWidth = config.memBusWidth, idWidth = 4 ) diff --git a/src/main/scala/riscv/soc/devices/Apb3ByteDev.scala b/src/main/scala/riscv/soc/devices/Apb3ByteDev.scala index 64c43e5..9b98bbf 100644 --- a/src/main/scala/riscv/soc/devices/Apb3ByteDev.scala +++ b/src/main/scala/riscv/soc/devices/Apb3ByteDev.scala @@ -20,7 +20,7 @@ class Apb3ByteDev(implicit config: Config) extends Component { val io = new Bundle { val bytes = master(new ByteDevIo) val irq = master(new IrqIo) - val apb = slave(Apb3(Apb3Config(addressWidth = 4, dataWidth = config.xlen))) + val apb = slave(Apb3(Apb3Config(addressWidth = 4, dataWidth = config.memBusWidth))) } io.irq.init() diff --git a/src/main/scala/riscv/soc/devices/Apb3CharDev.scala b/src/main/scala/riscv/soc/devices/Apb3CharDev.scala index 4bf4ae9..8437e07 100644 --- a/src/main/scala/riscv/soc/devices/Apb3CharDev.scala +++ b/src/main/scala/riscv/soc/devices/Apb3CharDev.scala @@ -9,7 +9,9 @@ import spinal.lib.bus.amba3.apb._ class Apb3CharDev(implicit config: Config) extends Component { val io = new Bundle { val char = master(Flow(UInt(8 bits))) - val apb = slave(Apb3(Apb3Config(addressWidth = 4, dataWidth = config.xlen))) + val apb = slave( + Apb3(Apb3Config(addressWidth = 4, dataWidth = config.memBusWidth)) + ) // TODO: we are abusing this, the apb spec doesn't allow wide buses } val busCtrl = Apb3SlaveFactory(io.apb) diff --git a/src/main/scala/riscv/soc/devices/Apb3MachineTimers.scala b/src/main/scala/riscv/soc/devices/Apb3MachineTimers.scala index 459a9c2..e1b5abe 100644 --- a/src/main/scala/riscv/soc/devices/Apb3MachineTimers.scala +++ b/src/main/scala/riscv/soc/devices/Apb3MachineTimers.scala @@ -9,7 +9,7 @@ import spinal.lib.bus.amba3.apb._ class Apb3MachineTimers(pipeline: Pipeline)(implicit config: Config) extends Component { val io = new Bundle { val mtimer = master(new IrqIo) - val apb = slave(Apb3(Apb3Config(addressWidth = 4, dataWidth = config.xlen))) + val apb = slave(Apb3(Apb3Config(addressWidth = 4, dataWidth = config.memBusWidth))) } io.mtimer.init() diff --git a/src/main/scala/riscv/soc/devices/Apb3TestDev.scala b/src/main/scala/riscv/soc/devices/Apb3TestDev.scala index 0047071..741c3c9 100644 --- a/src/main/scala/riscv/soc/devices/Apb3TestDev.scala +++ b/src/main/scala/riscv/soc/devices/Apb3TestDev.scala @@ -10,7 +10,7 @@ import spinal.lib.bus.amba3.apb._ class Apb3TestDev(implicit config: Config) extends Component { val io = new Bundle { val test = master(Flow(UInt(config.xlen bits))) - val apb = slave(Apb3(Apb3Config(addressWidth = 4, dataWidth = config.xlen))) + val apb = slave(Apb3(Apb3Config(addressWidth = 4, dataWidth = config.memBusWidth))) } val busCtrl = Apb3SlaveFactory(io.apb)