diff --git a/ext/NaxSoftware b/ext/NaxSoftware index 6ff56798..f9f2681a 160000 --- a/ext/NaxSoftware +++ b/ext/NaxSoftware @@ -1 +1 @@ -Subproject commit 6ff567988b3e7f1e2a59b5f28f81a8519f22e0e0 +Subproject commit f9f2681ae03ce28f973d41574996fca5552f9af1 diff --git a/ext/SpinalHDL b/ext/SpinalHDL index 6eac7c63..bec17f5d 160000 --- a/ext/SpinalHDL +++ b/ext/SpinalHDL @@ -1 +1 @@ -Subproject commit 6eac7c6372f72e0d84b5fb0381190cb1250b774c +Subproject commit bec17f5d280bf1db29e602a7168e4e9f2f515f07 diff --git a/ext/riscv-isa-sim b/ext/riscv-isa-sim index 5f4eca2c..c6fa02d5 160000 --- a/ext/riscv-isa-sim +++ b/ext/riscv-isa-sim @@ -1 +1 @@ -Subproject commit 5f4eca2c0a5ee7808ad235f5230f3bd2c14a6ffa +Subproject commit c6fa02d5b28bc0d98ea14e7fbfa749d5dd265034 diff --git a/ext/rvls b/ext/rvls index 12840c98..d4899fac 160000 --- a/ext/rvls +++ b/ext/rvls @@ -1 +1 @@ -Subproject commit 12840c98eb79bc43abdc40662f123d66f75b9315 +Subproject commit d4899fac2e7f943fb51fc2db0b9859df59cac4c6 diff --git a/src/main/scala/vexiiriscv/Param.scala b/src/main/scala/vexiiriscv/Param.scala index 5c841877..49462e46 100644 --- a/src/main/scala/vexiiriscv/Param.scala +++ b/src/main/scala/vexiiriscv/Param.scala @@ -13,7 +13,7 @@ import vexiiriscv.decode.DecoderPlugin import vexiiriscv.execute._ import vexiiriscv.execute.lsu._ import vexiiriscv.fetch.{FetchCachelessPlugin, FetchL1Plugin, PrefetcherNextLinePlugin} -import vexiiriscv.memory.{MmuPortParameter, MmuSpec, MmuStorageLevel, MmuStorageParameter} +import vexiiriscv.memory.{MmuPortParameter, MmuSpec, MmuStorageLevel, MmuStorageParameter, PmpParam, PmpPlugin, PmpPortParameter} import vexiiriscv.misc._ import vexiiriscv.prediction.{LearnCmd, LearnPlugin} import vexiiriscv.riscv.{FloatRegFile, IntRegFile} @@ -165,6 +165,49 @@ class ParamSimple(){ priority = 0 ) + var pmpParam = new PmpParam( + pmpSize = 0, + granularity = 4096, + withTor = true, + withNapot = true + ) + + var fetchNoL1PmpParam = new PmpPortParameter( + napotMatchAt = 0, + napotHitsAt = 1, + torCmpAt = 0, + torHitsAt = 1, + hitsAt = 1, + rspAt = 1 + ) + + var lsuNoL1PmpParam = new PmpPortParameter( + napotMatchAt = 0, + napotHitsAt = 0, + torCmpAt = 0, + torHitsAt = 0, + hitsAt = 0, + rspAt = 0 + ) + + var fetchL1PmpParam = new PmpPortParameter( + napotMatchAt = 1, + napotHitsAt = 1, + torCmpAt = 1, + torHitsAt = 2, + hitsAt = 2, + rspAt = 2 + ) + + var lsuL1PmpParam = new PmpPortParameter( + napotMatchAt = 1, + napotHitsAt = 1, + torCmpAt = 1, + torHitsAt = 2, + hitsAt = 2, + rspAt = 2 + ) + var lsuTsp = MmuStorageParameter( levels = List( MmuStorageLevel( @@ -472,6 +515,7 @@ class ParamSimple(){ opt[Unit]("with-dispatcher-buffer") action { (v, c) => withDispatcherBuffer = true } opt[Unit]("with-supervisor") action { (v, c) => privParam.withSupervisor = true; privParam.withUser = true; withMmu = true } opt[Unit]("with-user") action { (v, c) => privParam.withUser = true } + opt[Unit]("without-mmu") action { (v, c) => withMmu = false } opt[Unit]("without-mul") action { (v, c) => withMul = false } opt[Unit]("without-div") action { (v, c) => withDiv = false } opt[Unit]("with-mul") action { (v, c) => withMul = true } @@ -524,9 +568,10 @@ class ParamSimple(){ opt[Unit]("with-boot-mem-init") action { (v, c) => bootMemClear = true } opt[Int]("physical-width") action { (v, c) => physicalWidth = v } opt[Unit]("mul-keep-src") action { (v, c) => mulKeepSrc = true } - opt[Unit]("mmu-sync-read") action { (v, c) => - withMmuSyncRead() - } + opt[Unit]("mmu-sync-read") action { (v, c) => withMmuSyncRead() } + opt[Int]("pmp-size") action { (v, c) => pmpParam.pmpSize = v } + opt[Int]("pmp-granularity") action { (v, c) => pmpParam.granularity = v } + opt[Unit]("pmp-tor-disable") action { (v, c) => pmpParam.withTor = false } } def plugins(hartId : Int = 0) = pluginsArea(hartId).plugins @@ -545,6 +590,8 @@ class ParamSimple(){ ) } + plugins += new PmpPlugin(pmpParam) + plugins += new misc.PipelineBuilderPlugin() plugins += new schedule.ReschedulePlugin() @@ -588,6 +635,7 @@ class ParamSimple(){ forkAt = fetchForkAt, joinAt = fetchForkAt+1, //You can for instance allow the external memory to have more latency by changing this wordWidth = fetchMemDataWidth, + pmpPortParameter = fetchNoL1PmpParam, translationStorageParameter = fetchTsp, translationPortParameter = withMmu match { case false => null @@ -615,7 +663,8 @@ class ParamSimple(){ translationPortParameter = withMmu match { case false => null case true => fetchTpp - } + }, + pmpPortParameter = fetchL1PmpParam ) fetchL1Prefetch match { @@ -682,6 +731,7 @@ class ParamSimple(){ forkAt = lsuForkAt+0, joinAt = lsuForkAt+1, wbAt = 2, //TODO + pmpPortParameter = lsuNoL1PmpParam, translationStorageParameter = lsuTsp, translationPortParameter = withMmu match { case false => null @@ -701,6 +751,7 @@ class ParamSimple(){ storeBufferSlots = lsuStoreBufferSlots, storeBufferOps = lsuStoreBufferOps, softwarePrefetch = lsuSoftwarePrefetch, + pmpPortParameter = fetchL1PmpParam, translationStorageParameter = lsuTsp, translationPortParameter = withMmu match { case false => null diff --git a/src/main/scala/vexiiriscv/execute/lsu/LsuCachelessPlugin.scala b/src/main/scala/vexiiriscv/execute/lsu/LsuCachelessPlugin.scala index 8847ff3a..2984cf0e 100644 --- a/src/main/scala/vexiiriscv/execute/lsu/LsuCachelessPlugin.scala +++ b/src/main/scala/vexiiriscv/execute/lsu/LsuCachelessPlugin.scala @@ -10,7 +10,7 @@ import spinal.core.fiber.{Handle, Retainer} import spinal.core.sim.SimDataPimper import vexiiriscv.decode.Decode import vexiiriscv.fetch.FetchPipelinePlugin -import vexiiriscv.memory.{AddressTranslationPortUsage, AddressTranslationService, DBusAccessService, PmaLoad, PmaLogic, PmaPort, PmaStore} +import vexiiriscv.memory.{AddressTranslationPortUsage, AddressTranslationService, DBusAccessService, PmaLoad, PmaLogic, PmaPort, PmaStore, PmpService} import vexiiriscv.misc.{AddressToMask, LsuTriggerService, PerformanceCounterService, TrapArg, TrapReason, TrapService} import vexiiriscv.riscv.Riscv.{FLEN, LSLEN, XLEN} import spinal.lib.misc.pipeline._ @@ -27,6 +27,7 @@ class LsuCachelessPlugin(var layer : LaneLayer, var withSpeculativeLoadFlush : Boolean, //WARNING, the fork cmd may be flushed out of existance before firing if any plugin doesn't flush from the first cycle after !freeze var translationStorageParameter: Any, var translationPortParameter: Any, + var pmpPortParameter : Any, var addressAt: Int = 0, var triggerAt: Int = 0, var pmaAt : Int = 0, @@ -55,9 +56,10 @@ class LsuCachelessPlugin(var layer : LaneLayer, val fpwbp = host.findOption[WriteBackPlugin](p => p.lane == layer.lane && p.rf == FloatRegFile) val srcp = host.find[SrcPlugin](_.layer == layer) val ats = host[AddressTranslationService] + val ps = host[PmpService] val ts = host[TrapService] val ss = host[ScheduleService] - val buildBefore = retains(elp.pipelineLock, ats.portsLock) + val buildBefore = retains(elp.pipelineLock, ats.portsLock, ps.portsLock) val atsStorageLock = retains(ats.storageLock) val retainer = retains(List(elp.uopLock, srcp.elaborationLock, ifp.elaborationLock, ts.trapLock, ss.elaborationLock) ++ fpwbp.map(_.elaborationLock)) awaitBuild() @@ -138,6 +140,8 @@ class LsuCachelessPlugin(var layer : LaneLayer, } } + + val onAddress = new addressCtrl.Area{ val RAW_ADDRESS = insert(srcp.ADD_SUB.asUInt) @@ -152,6 +156,16 @@ class LsuCachelessPlugin(var layer : LaneLayer, val MISS_ALIGNED = insert((1 to log2Up(LSLEN / 8)).map(i => SIZE === i && RAW_ADDRESS(i - 1 downto 0) =/= 0).orR) //TODO remove from speculLoad and handle it with trap } val tpk = onAddress.translationPort.keys + val pmpPort = ps.createPmpPort( + nodes = List.tabulate(forkAt+1)(elp.execute(_).down), + physicalAddress = tpk.TRANSLATED, + forceCheck = _ => False, + read = _(LOAD), + write = _(STORE), + execute = _ => False, + portSpec = pmpPortParameter, + storageSpec = null + ) val onPma = new elp.Execute(pmaAt){ val port = new PmaPort(Global.PHYSICAL_WIDTH, (0 to log2Up(Riscv.LSLEN / 8)).map(1 << _), List(PmaLoad, PmaStore)) @@ -233,7 +247,7 @@ class LsuCachelessPlugin(var layer : LaneLayer, trapPort.code := TrapReason.REDO } - when(tpk.ACCESS_FAULT || onPma.RSP.fault) { + when(tpk.ACCESS_FAULT || onPma.RSP.fault || pmpPort.ACCESS_FAULT) { skip := True trapPort.exception := True trapPort.code := CSR.MCAUSE_ENUM.LOAD_ACCESS_FAULT diff --git a/src/main/scala/vexiiriscv/execute/lsu/LsuPlugin.scala b/src/main/scala/vexiiriscv/execute/lsu/LsuPlugin.scala index 6b85b900..ba5d0e4d 100644 --- a/src/main/scala/vexiiriscv/execute/lsu/LsuPlugin.scala +++ b/src/main/scala/vexiiriscv/execute/lsu/LsuPlugin.scala @@ -12,7 +12,7 @@ import spinal.lib.misc.plugin.FiberPlugin import spinal.lib.system.tag.PmaRegion import vexiiriscv.decode.Decode import vexiiriscv.decode.Decode.UOP -import vexiiriscv.memory.{AddressTranslationPortUsage, AddressTranslationService, DBusAccessService, PmaLoad, PmaLogic, PmaPort, PmaStore} +import vexiiriscv.memory.{AddressTranslationPortUsage, AddressTranslationService, DBusAccessService, PmaLoad, PmaLogic, PmaPort, PmaStore, PmpService} import vexiiriscv.misc.{AddressToMask, LsuTriggerService, PerformanceCounterService, TrapArg, TrapReason, TrapService} import vexiiriscv.riscv.Riscv.{FLEN, LSLEN, XLEN} import vexiiriscv.riscv._ @@ -48,6 +48,7 @@ class LsuPlugin(var layer : LaneLayer, var withRva : Boolean, var translationStorageParameter: Any, var translationPortParameter: Any, + var pmpPortParameter : Any, var softwarePrefetch: Boolean, var addressAt: Int = 0, var triggerAt : Int = 1, @@ -95,12 +96,13 @@ class LsuPlugin(var layer : LaneLayer, val ifp = host.find[IntFormatPlugin](_.lane == layer.lane) val srcp = host.find[SrcPlugin](_.layer == layer) val ats = host[AddressTranslationService] + val ps = host[PmpService] val ts = host[TrapService] val ss = host[ScheduleService] val pcs = host.get[PerformanceCounterService] val hp = host.get[PrefetcherPlugin] val fpwbp = host.findOption[WriteBackPlugin](p => p.lane == layer.lane && p.rf == FloatRegFile) - val buildBefore = retains(elp.pipelineLock, ats.portsLock) + val buildBefore = retains(elp.pipelineLock, ats.portsLock, ps.portsLock) val earlyLock = retains(List(ats.storageLock) ++ pcs.map(_.elaborationLock).toList) val retainer = retains(List(elp.uopLock, srcp.elaborationLock, ifp.elaborationLock, ts.trapLock, ss.elaborationLock) ++ fpwbp.map(_.elaborationLock)) awaitBuild() @@ -437,7 +439,16 @@ class LsuPlugin(var layer : LaneLayer, val tpk = onAddress0.translationPort.keys - + val pmpPort = ps.createPmpPort( + nodes = List.tabulate(ctrlAt+1)(elp.execute(_).down), + physicalAddress = tpk.TRANSLATED, + forceCheck = _(FROM_ACCESS), + read = _(LOAD), + write = _(STORE), + execute = _ => False, + portSpec = pmpPortParameter, + storageSpec = null + ) val onAddress1 = new elp.Execute(addressAt+1) { l1.PHYSICAL_ADDRESS := tpk.TRANSLATED @@ -626,7 +637,7 @@ class LsuPlugin(var layer : LaneLayer, trapPort.code.assignDontCare() trapPort.arg.allowOverride() := 0 - val accessFault = (onPma.CACHED_RSP.fault).mux[Bool](io.rsp.valid && io.rsp.error || l1.ATOMIC, l1.FAULT) + val accessFault = (onPma.CACHED_RSP.fault).mux[Bool](io.rsp.valid && io.rsp.error || l1.ATOMIC, l1.FAULT) || pmpPort.ACCESS_FAULT when(accessFault) { lsuTrap := True trapPort.exception := True @@ -745,12 +756,12 @@ class LsuPlugin(var layer : LaneLayer, abords += !l1.FLUSH && onPma.CACHED_RSP.fault abords += FROM_LSU && (!isValid || isCancel) abords += mmuNeeded && MMU_FAILURE - if(withStoreBuffer) abords += wb.loadHazard || !FROM_WB && fenceTrap.valid + if(withStoreBuffer) abords += wb.loadHazard || !FROM_WB && fenceTrap.valid || wb.selfHazard skipsWrite += l1.MISS || l1.MISS_UNIQUE - skipsWrite += l1.FAULT + skipsWrite += l1.FAULT || pmpPort.ACCESS_FAULT skipsWrite += preCtrl.MISS_ALIGNED - skipsWrite += FROM_LSU && onTrigger.HIT + skipsWrite += FROM_LSU && (onTrigger.HIT || pmpPort.ACCESS_FAULT) skipsWrite += FROM_PREFETCH if(Riscv.RVA) skipsWrite += l1.ATOMIC && !l1.LOAD && scMiss if (withStoreBuffer) skipsWrite += wb.selfHazard || !FROM_WB && wb.hit @@ -776,12 +787,13 @@ class LsuPlugin(var layer : LaneLayer, } } + //TODO what happen if used for IO access ? val access = dbusAccesses.nonEmpty generate new Area { assert(dbusAccesses.size == 1) val rsp = dbusAccesses.head.rsp rsp.valid := l1.SEL && FROM_ACCESS && !elp.isFreezed() - rsp.data := loadData.RESULT.resized //loadData.RESULT instead of l1.READ_DATA (because it rv32fd - rsp.error := l1.FAULT + rsp.data := loadData.RESULT.resized //loadData.RESULT instead of l1.READ_DATA (because it rv32fd) + rsp.error := l1.FAULT || pmpPort.ACCESS_FAULT rsp.redo := traps.l1Failed rsp.waitSlot := 0 rsp.waitAny := False //TODO diff --git a/src/main/scala/vexiiriscv/fetch/FetchCachelessPlugin.scala b/src/main/scala/vexiiriscv/fetch/FetchCachelessPlugin.scala index bd58d335..09fa1095 100644 --- a/src/main/scala/vexiiriscv/fetch/FetchCachelessPlugin.scala +++ b/src/main/scala/vexiiriscv/fetch/FetchCachelessPlugin.scala @@ -12,7 +12,7 @@ import spinal.lib.bus.tilelink.DebugId import spinal.lib.system.tag.{MappedTransfers, PmaRegion} import vexiiriscv._ import vexiiriscv.Global._ -import vexiiriscv.memory.{AddressTranslationPortUsage, AddressTranslationService, PmaLoad, PmaLogic, PmaPort} +import vexiiriscv.memory.{AddressTranslationPortUsage, AddressTranslationService, PmaLoad, PmaLogic, PmaPort, PmpService} import vexiiriscv.misc.{PerformanceCounterService, TrapArg, TrapReason, TrapService} import vexiiriscv.riscv.CSR @@ -27,6 +27,7 @@ object FetchCachelessPlugin{ class FetchCachelessPlugin(var wordWidth : Int, var translationStorageParameter: Any, var translationPortParameter: Any, + var pmpPortParameter : Any, var addressAt: Int = 0, var pmaAt: Int = 0, var forkAt : Int = 0, @@ -36,9 +37,10 @@ class FetchCachelessPlugin(var wordWidth : Int, val logic = during setup new Area{ val pp = host[FetchPipelinePlugin] + val ps = host[PmpService] val ts = host[TrapService] val ats = host[AddressTranslationService] - val buildBefore = retains(pp.elaborationLock, ats.portsLock) + val buildBefore = retains(pp.elaborationLock, ats.portsLock, ps.portsLock) val atsStorageLock = retains(ats.storageLock) val trapLock = ts.trapLock() awaitBuild() @@ -93,6 +95,17 @@ class FetchCachelessPlugin(var wordWidth : Int, } val tpk = onAddress.translationPort.keys + val pmpPort = ps.createPmpPort( + nodes = List.tabulate(joinAt+1)(pp.fetch(_).down), + physicalAddress = tpk.TRANSLATED, + forceCheck = _ => False, + read = _ => False, + write = _ => False, + execute = _ => True, + portSpec = pmpPortParameter, + storageSpec = null + ) + val onPma = new pp.Fetch(pmaAt) { val port = new PmaPort(Global.PHYSICAL_WIDTH, List(Fetch.WORD_WIDTH / 8), List(PmaLoad)) port.cmd.address := tpk.TRANSLATED @@ -152,7 +165,7 @@ class FetchCachelessPlugin(var wordWidth : Int, trapPort.code.assignDontCare() trapPort.arg.allowOverride() := 0 - when(rsp.error || fork.PMA_FAULT){ + when(rsp.error || fork.PMA_FAULT || pmpPort.ACCESS_FAULT){ TRAP := True trapPort.exception := True trapPort.code := CSR.MCAUSE_ENUM.INSTRUCTION_ACCESS_FAULT diff --git a/src/main/scala/vexiiriscv/fetch/FetchL1Plugin.scala b/src/main/scala/vexiiriscv/fetch/FetchL1Plugin.scala index a1a43663..58862ddd 100644 --- a/src/main/scala/vexiiriscv/fetch/FetchL1Plugin.scala +++ b/src/main/scala/vexiiriscv/fetch/FetchL1Plugin.scala @@ -10,7 +10,7 @@ import spinal.lib.bus.amba4.axilite.{AxiLite4Config, AxiLite4ReadOnly} import spinal.lib.bus.bmb.{Bmb, BmbAccessParameter, BmbParameter, BmbSourceParameter} import spinal.lib.bus.tilelink.{M2sSupport, SizeRange} import spinal.lib.misc.Plru -import vexiiriscv.memory.{AddressTranslationPortUsage, AddressTranslationService, PmaLoad, PmaLogic, PmaPort} +import vexiiriscv.memory.{AddressTranslationPortUsage, AddressTranslationService, PmaLoad, PmaLogic, PmaPort, PmpService} import vexiiriscv.misc._ import vexiiriscv._ import vexiiriscv.Global._ @@ -56,6 +56,7 @@ trait LsuL1Service{ class FetchL1Plugin(var translationStorageParameter: Any, var translationPortParameter: Any, + var pmpPortParameter : Any, var memDataWidth : Int, var fetchDataWidth : Int, var setCount: Int, @@ -92,9 +93,10 @@ class FetchL1Plugin(var translationStorageParameter: Any, val rp = host[ReschedulePlugin] val ts = host[TrapService] val ats = host[AddressTranslationService] + val ps = host[PmpService] val pcs = host.get[PerformanceCounterService] val prefetcher = host.get[PrefetcherPlugin].map(_.io) - val buildBefore = retains(pp.elaborationLock, ats.portsLock) + val buildBefore = retains(pp.elaborationLock, ats.portsLock, ps.portsLock) val atsStorageLock = retains(ats.storageLock) val setupLock = retains(List(ts.trapLock, pcp.elaborationLock, rp.elaborationLock) ++ pcs.map(_.elaborationLock).toList) awaitBuild() @@ -365,6 +367,17 @@ class FetchL1Plugin(var translationStorageParameter: Any, ) val tpk = translationPort.keys + val pmpPort = ps.createPmpPort( + nodes = List.tabulate(ctrlAt+1)(pp.fetch(_).down), + physicalAddress = tpk.TRANSLATED, + forceCheck = _ => False, + read = _ => False, + write = _ => False, + execute = _ => True, + portSpec = pmpPortParameter, + storageSpec = null + ) + val cmd = new pp.Fetch(readAt) { val ra1 = pp.fetch(readAt + 1).up val doIt = ra1.ready || (!ra1.valid && prefetcher.nonEmpty.mux(!ra1(PREFETCH), True)) // Better timings than up.isReady, ra1.cancel not necessary as cancel do not collapse bubbles @@ -486,7 +499,7 @@ class FetchL1Plugin(var translationStorageParameter: Any, trapPort.code := TrapReason.REDO } - when(dataAccessFault || pmaPort.rsp.fault) { + when(dataAccessFault || pmaPort.rsp.fault || pmpPort.ACCESS_FAULT) { allowRefill := False trapPort.valid := True trapPort.exception := True diff --git a/src/main/scala/vexiiriscv/memory/MmuPlugin.scala b/src/main/scala/vexiiriscv/memory/MmuPlugin.scala index d6e83213..6b8782ab 100644 --- a/src/main/scala/vexiiriscv/memory/MmuPlugin.scala +++ b/src/main/scala/vexiiriscv/memory/MmuPlugin.scala @@ -198,11 +198,9 @@ class MmuPlugin(var spec : MmuSpec, val status = new Area{ val mxr = RegInit(False) val sum = RegInit(False) - val mprv = RegInit(False) clearWhen(priv.hart(0).xretAwayFromMachine) } for(offset <- List(CSR.MSTATUS, CSR.SSTATUS)) csr.readWrite(offset, 19 -> status.mxr, 18 -> status.sum) - csr.readWrite(CSR.MSTATUS, 17 -> status.mprv) csr.readWrite(CSR.SATP, satp.modeOffset -> satp.mode/*, 22 -> satp.asid*/, 0 -> satp.ppn) val satpModeWrite = csr.bus.write.bits(satp.modeOffset, satp.modeWidth bits) @@ -256,14 +254,15 @@ class MmuPlugin(var spec : MmuSpec, val isMachine = priv.getPrivilege(0) === U"11" val isSupervisor = priv.getPrivilege(0) === U"01" val isUser = priv.getPrivilege(0) === U"00" + def mprv = priv.logic.harts(0).m.status.mprv api.fetchTranslationEnable := satp.mode === spec.satpMode api.fetchTranslationEnable clearWhen(isMachine) api.lsuTranslationEnable := satp.mode === spec.satpMode - api.lsuTranslationEnable clearWhen(!status.mprv && isMachine) + api.lsuTranslationEnable clearWhen(!mprv && isMachine) when(isMachine) { - api.lsuTranslationEnable clearWhen (!status.mprv || priv.logic.harts(0).m.status.mpp === 3) + api.lsuTranslationEnable clearWhen (!mprv || priv.logic.harts(0).m.status.mpp === 3) } diff --git a/src/main/scala/vexiiriscv/memory/PmpPlugin.scala b/src/main/scala/vexiiriscv/memory/PmpPlugin.scala new file mode 100644 index 00000000..670fe1d0 --- /dev/null +++ b/src/main/scala/vexiiriscv/memory/PmpPlugin.scala @@ -0,0 +1,216 @@ +package vexiiriscv.memory + +import spinal.core._ +import spinal.lib._ +import spinal.lib.misc.pipeline.{NodeBaseApi, Payload} +import spinal.lib.misc.plugin.FiberPlugin +import vexiiriscv.Global +import vexiiriscv.execute.{CsrAccessPlugin, CsrListFilter, CsrRamService} +import vexiiriscv.misc.{PipelineBuilderPlugin, PrivilegedPlugin, TrapReason} +import vexiiriscv.riscv.{CSR, Riscv} + +import scala.collection.mutable.ArrayBuffer + +// Here are some good Spike implementation references : +// - https://github.com/riscv-software-src/riscv-isa-sim/blob/2c67071743d4b55719cee22fdb319df2a0756db7/riscv/mmu.cc#L348 +// - https://github.com/riscv-software-src/riscv-isa-sim/blob/2c67071743d4b55719cee22fdb319df2a0756db7/riscv/csrs.cc#L183 +// - https://github.com/riscv-software-src/riscv-isa-sim/blob/2c67071743d4b55719cee22fdb319df2a0756db7/riscv/mmu.cc#L348 + +case class PmpStorageParameter(slots : Int) +case class PmpPortParameter(var napotMatchAt : Int, + var napotHitsAt : Int, + var torCmpAt : Int, + var torHitsAt : Int, + var hitsAt : Int, + var rspAt : Int) + + +case class PmpParam( + var pmpSize: Int, + var granularity: Int, + var withTor: Boolean = true, + var withNapot: Boolean = true +) + +class PmpPlugin(val p : PmpParam) extends FiberPlugin with PmpService{ + import p._ + + override def getPmpNum(): Int = p.pmpSize + + assert(isPow2(granularity)) + val granularityWidth = log2Up(granularity) + + case class PortSpec(stages: Seq[NodeBaseApi], + physicalAddress: Payload[UInt], + forceCheck: NodeBaseApi => Bool, + read: NodeBaseApi => Bool, + write: NodeBaseApi => Bool, + execute: NodeBaseApi => Bool, + pp: PmpPortParameter, + rsp : PmpRsp){ + val permStage = stages(0) + val napotMatchStage = stages(pp.napotMatchAt) + val napotHitsStage = stages(pp.napotHitsAt) + val torCmpStage = stages(pp.torCmpAt) + val torHitsStage = stages(pp.torHitsAt) + val hitsStage = stages(pp.hitsAt) + val rspStage = stages(pp.rspAt) + } + val portSpecs = ArrayBuffer[PortSpec]() + + override def createPmpPort(nodes: Seq[NodeBaseApi], + physicalAddress: Payload[UInt], + forceCheck: NodeBaseApi => Bool, + read: NodeBaseApi => Bool, + write: NodeBaseApi => Bool, + execute: NodeBaseApi => Bool, + portSpec: Any, + storageSpec: Any): PmpRsp = { + val pp = portSpec.asInstanceOf[PmpPortParameter] + portSpecs.addRet( + new PortSpec( + stages = nodes, + physicalAddress = physicalAddress, + forceCheck = forceCheck, + read = read, + write = write, + execute = execute, + pp = pp, + rsp = new PmpRsp() + ) + ).rsp + } + + val logic = during setup new Area{ + val priv = host[PrivilegedPlugin] + val csr = host[CsrAccessPlugin] + val ram = host[CsrRamService] + + val csrLock = retains(csr.csrLock, ram.csrLock) + val buildBefore = retains(List(host[PipelineBuilderPlugin].elaborationLock)) + + awaitBuild() + + if(Riscv.XLEN.get == 64) assert( + granularity >= 8, + """VexiiRiscv RV64 doesn't support granularity smaller than 8 bytes, as durring 8 bytes accesses, + | it would need to check that the upper 4 bytes of access are contained in the pmp regions aswell""".stripMargin + ) + + case class Cfg() extends Bundle{ + val kind = Bits(2 bits) + val read, write, execute = Bool() + val locked = Bool() + } + + val pRange = Global.PHYSICAL_WIDTH-1 downto granularityWidth + // This is a tricky thing, basicaly the granularity doesn't affect TOR and NAPOT csr in the same way. + // In particular the granularityWidth-2 bit + val extraBit = granularity > 4 + + // Generate all the PMP entries storage + CSR mapping + val entries = for(i <- 0 until pmpSize) yield new Area{ + val isLocked = Bool() + val address = Reg(UInt(Global.PHYSICAL_WIDTH.get - granularityWidth + extraBit.toInt bits)) + val cfg = Reg(Cfg()) + val cfgNext = CombInit(cfg) // This allows to handle WARL (Write Any, Read Legal) on the CSRs + when(!cfg.locked) { + cfg := cfgNext + cfg.write clearWhen (!cfgNext.read) + if(!p.withTor) when(cfgNext.kind === 1) {cfg.kind := 0} + if(granularity > 4) when(cfgNext.kind === 2) {cfg.kind := 0} + } + + if(i == 0){ + // Allows software which is unaware of the PMP but uses the supervisor/user modes + // to get access to all the memory by default after reset + assert(p.withNapot) + address.init(address.getAllTrue) + cfg.read init(True) + cfg.write init(True) + cfg.execute init(True) + cfg.kind init(3) + cfg.locked init(False) + } else { + address.init(0) + cfg.initZero() + } + + + val isNapot = RegNext(cfg.kind(1)) + val isTor = RegNext(cfg.kind === 1) + val napot = Napot(address.dropHigh(1)).dropLow(extraBit.toInt) + if(granularity == 4) when(!cfgNext.kind(0)){ napot.setAll() } + val mask = RegNext(napot) + + val (cfgId, cfgOffset) = Riscv.XLEN.get match{ + case 32 => (i/4, i%4*8) + case 64 => (i/8*2, i%8*8) + } + + csr.writeCancel(CSR.PMPADDR + i, isLocked) + csr.read(address(address.bitsRange.drop(extraBit.toInt)), CSR.PMPADDR + i, granularityWidth-2) + csr.write(address(address.bitsRange), CSR.PMPADDR + i, granularityWidth-2-extraBit.toInt) + if(granularity > 4 && p.withNapot) csr.read(U(granularityWidth-2 bits, default -> isNapot, granularityWidth-3 -> (address.lsb && isNapot)), CSR.PMPADDR + i) //WTF + csr.read(CSR.PMPCFG + cfgId, 0+cfgOffset -> cfg.read, 1+cfgOffset -> cfg.write, 2+cfgOffset -> cfg.execute, 3+cfgOffset -> cfg.kind, 7+cfgOffset -> cfg.locked) + csr.write(CSR.PMPCFG + cfgId, 0+cfgOffset -> cfgNext.read, 1+cfgOffset -> cfgNext.write, 2+cfgOffset -> cfgNext.execute, 3+cfgOffset -> cfgNext.kind, 7+cfgOffset -> cfgNext.locked) + } + + for(i <- 0 until pmpSize; self = entries(i)) { + self.isLocked := self.cfg.locked || (i+1 != pmpSize).mux(entries(i+1).cfg.locked && entries(i+1).isTor , False) + } + + val allFilter = CsrListFilter((0 to 63).map(_ + CSR.PMPADDR) ++ (0 to 15).filter(i => Riscv.XLEN.get == 32 || (i % 2) == 0).map(_ + CSR.PMPCFG)) + if(p.pmpSize > 0) csr.onDecode(allFilter) { + when(csr.bus.decode.write) { + csr.bus.decode.doTrap(TrapReason.NEXT) + } + } + + assert(Global.HART_COUNT.get == 1) + + csrLock.release() + portsLock.await() + + val isMachine = priv.isMachine(0) + val checkInstruction = !isMachine + val checkData = !isMachine || priv.logic.harts(0).m.status.mprv && priv.logic.harts(0).m.status.mpp =/= 3 + + val ports = for(ps <- portSpecs) yield new Composite(ps.rsp, "logic", false){ + import ps._ + val portCheckData = checkData || ps.forceCheck(permStage) + val torCmpAddress = torCmpStage(ps.physicalAddress) >> granularityWidth + val TOR_SMALLER = new Array[Payload[Bool]](p.pmpSize) + val onEntries = for((e, id) <- entries.zipWithIndex) yield new Area{ + val napot = p.withNapot generate new Area { + val MATCH = napotMatchStage.insert(((e.address.dropLow(extraBit.toInt).asUInt << granularityWidth)(pRange) ^ napotMatchStage(ps.physicalAddress)(pRange)).asBits & e.mask) + val HIT = napotHitsStage.insert(napotHitsStage(MATCH) === 0) + } + val tor = p.withTor generate new Area{ + val BIGGER = torCmpStage.insert((e.address >> extraBit.toInt) > torCmpAddress) + val HIT = { import torHitsStage._ ; insert(BIGGER && (id != 0).mux(!TOR_SMALLER(id-1), True))} + TOR_SMALLER(id) = BIGGER + } + val HIT_ANY = { import hitsStage._ ; insert(p.withNapot.mux(e.isNapot && napot.HIT, False) || p.withTor.mux(e.isTor && tor.HIT, False)) } + + val normalRwx = (!ps.read(permStage) || e.cfg.read || !checkInstruction) && + (((!ps.write(permStage) || e.cfg.write) && (!ps.execute(permStage) || e.cfg.execute)) || !portCheckData) + val PERM_OK = permStage.insert(normalRwx) + } + val NEED_HIT = permStage.insert(checkInstruction && ps.execute(permStage) || portCheckData && (ps.read(permStage) || ps.write(permStage))) + + val onCtrl = (p.pmpSize > 0) generate new Area{ + import ps.rspStage._ + val hits = Cat(onEntries.map(e => ps.rspStage(e.HIT_ANY))) + val oh = OHMasking.firstV2(hits) + val reader = onEntries.reader(oh) + + rsp.ACCESS_FAULT := NEED_HIT && !(reader(e => ps.rspStage(e.PERM_OK))) + } + if(p.pmpSize == 0) ps.rspStage(rsp.ACCESS_FAULT) := False + } + + + buildBefore.release() + } +} \ No newline at end of file diff --git a/src/main/scala/vexiiriscv/memory/Service.scala b/src/main/scala/vexiiriscv/memory/Service.scala index afb7fa56..cf7eeda0 100644 --- a/src/main/scala/vexiiriscv/memory/Service.scala +++ b/src/main/scala/vexiiriscv/memory/Service.scala @@ -79,6 +79,24 @@ class AddressTranslationRsp(s : AddressTranslationService, val wayCount : Int) e } } +trait PmpService extends Area { + val portsLock = Retainer() + def createPmpPort(nodes: Seq[NodeBaseApi], + physicalAddress: Payload[UInt], + forceCheck: NodeBaseApi => Bool, + read: NodeBaseApi => Bool, + write: NodeBaseApi => Bool, + execute: NodeBaseApi => Bool, + portSpec: Any, + storageSpec: Any): PmpRsp + def getPmpNum() : Int +} + +class PmpRsp extends Area{ + val ACCESS_FAULT = Payload(Bool()) +} + + trait DBusAccessService{ def accessRefillCount : Int diff --git a/src/main/scala/vexiiriscv/misc/PrivilegedPlugin.scala b/src/main/scala/vexiiriscv/misc/PrivilegedPlugin.scala index 5535d0ac..01f01a6d 100644 --- a/src/main/scala/vexiiriscv/misc/PrivilegedPlugin.scala +++ b/src/main/scala/vexiiriscv/misc/PrivilegedPlugin.scala @@ -523,6 +523,7 @@ class PrivilegedPlugin(val p : PrivilegedParam, val hartIds : Seq[Int]) extends val sd = False val tsr, tvm = p.withSupervisor generate RegInit(False) val tw = p.withUser.mux(RegInit(False), False) + val mprv = RegInit(False) clearWhen(xretAwayFromMachine) if (RVF) { fpuEnable(hartId) setWhen (fs =/= 0) @@ -545,6 +546,7 @@ class PrivilegedPlugin(val p : PrivilegedParam, val hartIds : Seq[Int]) extends } } read(XLEN - 1 -> sd) + readWrite(17 -> mprv) if (withFs) readWrite(13 -> fs) if (p.withUser && XLEN.get == 64) read(32 -> U"10") if (p.withSupervisor && XLEN.get == 64) read(34 -> U"10") diff --git a/src/main/scala/vexiiriscv/riscv/Const.scala b/src/main/scala/vexiiriscv/riscv/Const.scala index 5fe6fe31..0a78256f 100644 --- a/src/main/scala/vexiiriscv/riscv/Const.scala +++ b/src/main/scala/vexiiriscv/riscv/Const.scala @@ -111,6 +111,9 @@ object CSR { val MCOUNTINHIBIT = 0x320 def MHPMEVENT0H = 0x720 // MRW Machine instructions-retired counter. + val PMPCFG = 0x3a0 + val PMPADDR = 0x3b0 + val SSTATUS = 0x100 val SIE = 0x104 val STVEC = 0x105 diff --git a/src/main/scala/vexiiriscv/test/VexiiRiscvProbe.scala b/src/main/scala/vexiiriscv/test/VexiiRiscvProbe.scala index 6f773058..1de3abe8 100644 --- a/src/main/scala/vexiiriscv/test/VexiiRiscvProbe.scala +++ b/src/main/scala/vexiiriscv/test/VexiiRiscvProbe.scala @@ -10,6 +10,7 @@ import vexiiriscv._ import vexiiriscv.decode.Decode import vexiiriscv.execute.lsu._ import vexiiriscv.fetch.FetchPipelinePlugin +import vexiiriscv.memory.{PmpPlugin, PmpService} import vexiiriscv.misc.PrivilegedPlugin import vexiiriscv.riscv.FloatRegFile //import vexiiriscv.execute.LsuCachelessPlugin @@ -185,7 +186,7 @@ class VexiiRiscvProbe(cpu : VexiiRiscv, kb : Option[konata.Backend], var withRvl if (get(Riscv.RVZbc)) isa += "_zbc" if (get(Riscv.RVZbs)) isa += "_zbs" tracer.newCpuMemoryView(hartId, 16, 1 << Decode.STORE_ID_WIDTH) - tracer.newCpu(hartId, isa, csrp, 63, hartId) + tracer.newCpu(hartId, isa, csrp, get(Global.PHYSICAL_WIDTH), cpu.host.get[PmpService].map(_.getPmpNum).getOrElse(0), hartId) val pc = if(xlen == 32) 0x80000000l else 0x80000000l tracer.setPc(hartId, pc) } diff --git a/src/main/scala/vexiiriscv/tester/TestBench.scala b/src/main/scala/vexiiriscv/tester/TestBench.scala index c334b385..e485692d 100644 --- a/src/main/scala/vexiiriscv/tester/TestBench.scala +++ b/src/main/scala/vexiiriscv/tester/TestBench.scala @@ -219,6 +219,7 @@ class TestOptions{ probe.enabled = withProbe probe.trace = false +// delayed(1000){ simFailure() } // Things to enable when we want to collect traces val tracerFile = traceRvlsLog.option(new FileBackend(new File(currentTestPath(), "tracer.log"))) onTrace { diff --git a/src/test/scala/vexiiriscv/tester/Regression.scala b/src/test/scala/vexiiriscv/tester/Regression.scala index 9e8f2238..d09b37bb 100644 --- a/src/test/scala/vexiiriscv/tester/Regression.scala +++ b/src/test/scala/vexiiriscv/tester/Regression.scala @@ -5,7 +5,7 @@ import spinal.core._ import spinal.core.sim._ import spinal.lib.misc.plugin.Hostable import spinal.lib.misc.test.{AsyncJob, MultithreadedFunSuite} -import vexiiriscv.memory.MmuPlugin +import vexiiriscv.memory.{MmuPlugin, PmpPlugin} import vexiiriscv.misc.{EmbeddedRiscvJtag, PrivilegedPlugin} import vexiiriscv.riscv.Riscv import vexiiriscv.{Global, ParamSimple, VexiiRiscv} @@ -52,6 +52,7 @@ class RegressionSingle(compiled : SimCompiled[VexiiRiscv], val xlen = dut.database(Riscv.XLEN) val priv = dut.host.get[PrivilegedPlugin] val mmu = dut.host.get[MmuPlugin] + val pmp = dut.host.get[PmpPlugin] val rvm = dut.database(Riscv.RVM) val rvc = dut.database(Riscv.RVC) @@ -351,6 +352,7 @@ class RegressionSingle(compiled : SimCompiled[VexiiRiscv], priv.filter(_.p.withSupervisor).foreach(_ => regulars ++= List("supervisor")) if(mmu.nonEmpty) regulars ++= List(s"mmu_sv${if(xlen == 32) 32 else 39}") + if(pmp.get.p.pmpSize > 4 && priv.get.p.withSupervisor) regulars ++= List(s"pmp") if(config.regular) for(name <- regulars){ val args = newArgs() @@ -627,6 +629,7 @@ class Regression extends MultithreadedFunSuite(sys.env.getOrElse("VEXIIRISCV_REG return List("", "--with-rvf", "--with-rvf --with-rvd").randomPick(random) } } + addDim("pmp", List("", "--pmp-size=8")) // addTest(default)