diff --git a/src/main/scala/vexriscv/demo/GenZephyr.scala b/src/main/scala/vexriscv/demo/GenZephyr.scala new file mode 100644 index 0000000..69302e3 --- /dev/null +++ b/src/main/scala/vexriscv/demo/GenZephyr.scala @@ -0,0 +1,85 @@ +package vexriscv.demo + +import vexriscv.plugin._ +import vexriscv.ip.{DataCacheConfig, InstructionCacheConfig} +import vexriscv.{plugin, VexRiscv, VexRiscvConfig} +import spinal.core._ + +object GenZephyr extends App{ + def cpu() = new VexRiscv( + config = VexRiscvConfig( + plugins = List( + new IBusCachedPlugin( + prediction = STATIC, + config = InstructionCacheConfig( + cacheSize = 4096, + bytePerLine = 32, + wayCount = 1, + addressWidth = 32, + cpuDataWidth = 32, + memDataWidth = 32, + catchIllegalAccess = true, + catchAccessFault = true, + asyncTagMemory = false, + twoCycleRam = true, + twoCycleCache = true + ) + ), + new DBusCachedPlugin( + config = new DataCacheConfig( + cacheSize = 4096, + bytePerLine = 32, + wayCount = 1, + addressWidth = 32, + cpuDataWidth = 32, + memDataWidth = 32, + catchAccessError = true, + catchIllegal = true, + catchUnaligned = true + ) + ), + new PmpPlugin( + regions = 16, + ioRange = _(31 downto 28) === 0xF + ), + new DecoderSimplePlugin( + catchIllegalInstruction = true + ), + new RegFilePlugin( + regFileReadyKind = plugin.SYNC, + zeroBoot = false + ), + new IntAluPlugin, + new SrcPlugin( + separatedAddSub = false, + executeInsertion = true + ), + new FullBarrelShifterPlugin, + new HazardSimplePlugin( + bypassExecute = true, + bypassMemory = true, + bypassWriteBack = true, + bypassWriteBackBuffer = true, + pessimisticUseSrc = false, + pessimisticWriteRegFile = false, + pessimisticAddressMatch = false + ), + new MulDivIterativePlugin( + genMul = true, + genDiv = true, + mulUnrollFactor = 1, + divUnrollFactor = 1 + ), + new CsrPlugin(CsrPluginConfig.zephyr(0x00000020l)), + new DebugPlugin(ClockDomain.current.clone(reset = Bool().setName("debugReset"))), + new BranchPlugin( + earlyBranch = false, + catchAddressMisaligned = true + ), + new YamlPlugin("cpu0.yaml") + ) + ) + ) + + SpinalVerilog(cpu()) +} diff --git a/src/main/scala/vexriscv/plugin/CsrPlugin.scala b/src/main/scala/vexriscv/plugin/CsrPlugin.scala index 668f1b7..224ee09 100644 --- a/src/main/scala/vexriscv/plugin/CsrPlugin.scala +++ b/src/main/scala/vexriscv/plugin/CsrPlugin.scala @@ -264,6 +264,31 @@ object CsrPluginConfig{ uinstretAccess = CsrAccess.NONE ) + def zephyr(mtvecInit : BigInt) = CsrPluginConfig( + catchIllegalAccess = true, + mvendorid = 1, + marchid = 2, + mimpid = 3, + mhartid = 0, + misaExtensionsInit = 0, + misaAccess = CsrAccess.NONE, + mtvecAccess = CsrAccess.READ_WRITE, + mtvecInit = mtvecInit, + mepcAccess = CsrAccess.READ_WRITE, + mscratchGen = true, + mcauseAccess = CsrAccess.READ_ONLY, + mbadaddrAccess = CsrAccess.READ_ONLY, + mcycleAccess = CsrAccess.NONE, + minstretAccess = CsrAccess.NONE, + ucycleAccess = CsrAccess.NONE, + uinstretAccess = CsrAccess.NONE, + wfiGenAsWait = true, + ecallGen = true, + userGen = true, + medelegAccess = CsrAccess.READ_WRITE, + midelegAccess = CsrAccess.READ_WRITE + ) + } case class CsrWrite(that : Data, bitOffset : Int) case class CsrRead(that : Data , bitOffset : Int) diff --git a/src/main/scala/vexriscv/plugin/PmpPlugin.scala b/src/main/scala/vexriscv/plugin/PmpPlugin.scala new file mode 100644 index 0000000..839cb8b --- /dev/null +++ b/src/main/scala/vexriscv/plugin/PmpPlugin.scala @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2020 Samuel Lindemer + * + * SPDX-License-Identifier: MIT + */ + +package vexriscv.plugin + +import vexriscv.{VexRiscv, _} +import spinal.core._ +import spinal.lib._ +import scala.collection.mutable.ArrayBuffer + +/* Each 32-bit pmpcfg# register contains four 8-bit configuration sections. + * These section numbers contain flags which apply to region defined by the + * corresponding pmpaddr# register. + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | pmp3cfg | pmp2cfg | pmp1cfg | pmp0cfg | pmpcfg0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | pmp7cfg | pmp6cfg | pmp5cfg | pmp4cfg | pmpcfg2 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 7 6 5 4 3 2 1 0 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | L | 0 | A | X | W | R | pmp#cfg + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + * L: locks configuration until system reset (including M-mode) + * 0: hardwired to zero + * A: 0 = OFF (null region / disabled) + * 1 = TOR (top of range) + * 2 = NA4 (naturally aligned four-byte region) + * 3 = NAPOT (naturally aligned power-of-two region, > 7 bytes) + * X: execute + * W: write + * R: read + * + * TOR: Each 32-bit pmpaddr# register defines the upper bound of the pmp region + * right-shifted by two bits. The lower bound of the region is the previous + * pmpaddr# register. In the case of pmpaddr0, the lower bound is address 0x0. + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | address[33:2] | pmpaddr# + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * NAPOT: Each 32-bit pmpaddr# register defines the regionStart address and the size + * of the pmp region. The number of concurrent 1s begging at the LSB indicates + * the size of the region as a power of two (e.g. 0x...0 = 8-byte, 0x...1 = + * 16-byte, 0x...11 = 32-byte, etc.). + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | address[33:2] |0|1|1|1|1| pmpaddr# + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * NA4: This is essentially an edge case of NAPOT where the entire pmpaddr# + * register defines a 4-byte wide region. + */ + +case class PmpRegister() extends Bundle { + + // CSR segments + val x, w, r = Reg(Bool) + val l = Reg(Bool) init(False) + val a = Reg(UInt(2 bits)) init(0) + val address = Reg(UInt(32 bits)) + + // Computed region bounds + val regionStart = UInt(32 bits) + val regionEnd = UInt(32 bits) + val valid = Bool + + // Addressing options + def NA4 = 2 + def NAPOT = 3 + + switch(a) { + is(NA4) { + regionStart := address |<< 2 + regionEnd := regionStart + 4 + valid := True + } + is(NAPOT) { + val mask = address & ~(address + 1) + regionStart := (address & ~mask) |<< 2 + regionEnd := regionStart + ((mask + 1) |<< 3) + valid := True + } + default { + regionStart := 0 + regionEnd := 0 + valid := False + } + } + +} + +case class ProtectedMemoryTranslatorPort(bus : MemoryTranslatorBus) + +class PmpPlugin(regions : Int, ioRange : UInt => Bool) extends Plugin[VexRiscv] with MemoryTranslator { + + // Each pmpcfg# CSR configures four regions. + assert((regions % 4) == 0) + + val pmps = ArrayBuffer[PmpRegister]() + val portsInfo = ArrayBuffer[ProtectedMemoryTranslatorPort]() + + override def newTranslationPort(priority : Int, args : Any): MemoryTranslatorBus = { + val port = ProtectedMemoryTranslatorPort(MemoryTranslatorBus()) + portsInfo += port + port.bus + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + import Riscv._ + + val csrService = pipeline.service(classOf[CsrInterface]) + val privilegeService = pipeline.service(classOf[PrivilegeService]) + + val core = pipeline plug new Area { + + // Instantiate pmpaddr0 ... pmpaddr# CSRs. + for (n <- 0 until regions) { + pmps += PmpRegister() + csrService.rw(0x3B0 + n, pmps(n).address) + } + + // Instantiate pmpcfg0 ... pmpcfg# CSRs. + for (n <- 0 until (regions / 4)) { + csrService.rw(0x3A0 + n, + 31 -> pmps((n * 4) + 3).l, 27 -> pmps((n * 4) + 3).a, + 26 -> pmps((n * 4) + 3).x, 25 -> pmps((n * 4) + 3).w, 24 -> pmps((n * 4) + 3).r, + 23 -> pmps((n * 4) + 2).l, 19 -> pmps((n * 4) + 2).a, + 18 -> pmps((n * 4) + 2).x, 17 -> pmps((n * 4) + 2).w, 16 -> pmps((n * 4) + 2).r, + 15 -> pmps((n * 4) + 1).l, 11 -> pmps((n * 4) + 1).a, + 10 -> pmps((n * 4) + 1).x, 9 -> pmps((n * 4) + 1).w, 8 -> pmps((n * 4) + 1).r, + 7 -> pmps((n * 4) ).l, 3 -> pmps((n * 4) ).a, + 2 -> pmps((n * 4) ).x, 1 -> pmps((n * 4) ).w, 0 -> pmps((n * 4) ).r + ) + } + + // Connect memory ports to PMP logic. + val ports = for ((port, portId) <- portsInfo.zipWithIndex) yield new Area { + + val address = port.bus.cmd.virtualAddress + port.bus.rsp.physicalAddress := address + + // Only the first matching PMP region is applied. + val hits = pmps.map(pmp => pmp.valid && + pmp.regionStart <= address && + pmp.regionEnd > address && + (pmp.l || ~privilegeService.isMachine())) + + // M-mode has full access by default. All others have none by default. + when(CountOne(hits) === 0) { + port.bus.rsp.allowRead := privilegeService.isMachine() + port.bus.rsp.allowWrite := privilegeService.isMachine() + port.bus.rsp.allowExecute := privilegeService.isMachine() + } otherwise { + port.bus.rsp.allowRead := MuxOH(OHMasking.first(hits), pmps).r + port.bus.rsp.allowWrite := MuxOH(OHMasking.first(hits), pmps).w + port.bus.rsp.allowExecute := MuxOH(OHMasking.first(hits), pmps).x + } + + port.bus.rsp.isIoAccess := ioRange(port.bus.rsp.physicalAddress) + port.bus.rsp.exception := False + port.bus.rsp.refilling := False + port.bus.busy := False + + } + } + } +} +