Merge pull request #378 from lschuermann/pmp-napot-rename

Rename `PmpPlugin -> PmpPluginNapot`, `PmpPluginOld -> PmpPlugin`
This commit is contained in:
Dolu1990 2023-11-14 12:40:29 +01:00 committed by GitHub
commit b6118e5cc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 497 additions and 476 deletions

View File

@ -44,6 +44,7 @@
- [StaticMemoryTranslatorPlugin](#staticmemorytranslatorplugin)
- [MmuPlugin](#mmuplugin)
- [PmpPlugin](#pmpplugin)
- [PmpPluginNapot](#pmppluginnapot)
- [DebugPlugin](#debugplugin)
- [EmbeddedRiscvJtag](#embeddedRiscvJtag)
- [YamlPlugin](#yamlplugin)
@ -1239,7 +1240,11 @@ fully associative TLB cache which is refilled automaticaly via a dbus access sha
#### PmpPlugin
This is a physical memory protection (PMP) plugin which conforms to the latest RISC-V privilege specification. PMP is configured by writing two special CSRs: `pmpcfg#` and `pmpaddr#`. The former contains the permissions and addressing modes for four protection regions, and the latter contains the encoded start address for a single region. Since the actual region bounds must be computed from the values written to these registers, writing them takes a few CPU cylces. This delay is necessary in order to centralize all of the decoding logic into a single component. Otherwise, it would have to be duplicated for each region, even though the decoding operation happens only when PMP is reprogrammed (e.g., on some context switches).
This is a physical memory protection (PMP) plugin which conforms to the v1.12 RISC-V privilege specification, without ePMP (`Smepmp`) extension support. PMP is configured by writing two special CSRs: `pmpcfg#` and `pmpaddr#`. The former contains the permissions and addressing modes for four protection regions, and the latter contains the encoded start address for a single region. Since the actual region bounds must be computed from the values written to these registers, writing them takes a few CPU cylces. This delay is necessary in order to centralize all of the decoding logic into a single component. Otherwise, it would have to be duplicated for each region, even though the decoding operation happens only when PMP is reprogrammed (e.g., on some context switches).
##### PmpPluginNapot
The `PmpPluginNapot` is a specialized PMP implementation, providing only the `NAPOT` (naturally-aligned poser-of-2 regions) addressing mode. It requires fewer resources and has a less significant timing impact compared to the full `PmpPlugin`.
#### DebugPlugin

View File

@ -41,7 +41,6 @@ object GenSecure extends App {
),
new PmpPlugin(
regions = 16,
granularity = 32,
ioRange = _(31 downto 28) === 0xf
),
new DecoderSimplePlugin(

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 Samuel Lindemer <samuel.lindemer@ri.se>
* Copyright (c) 2020 Samuel Lindemer <samuel.lindemer@ri.se>
*
* SPDX-License-Identifier: MIT
*/
@ -7,10 +7,9 @@
package vexriscv.plugin
import vexriscv.{VexRiscv, _}
import vexriscv.plugin.MemoryTranslatorPort.{_}
import spinal.core._
import spinal.lib._
import spinal.lib.fsm._
import scala.collection.mutable.ArrayBuffer
/* Each 32-bit pmpcfg# register contains four 8-bit configuration sections.
* These section numbers contain flags which apply to regions defined by the
@ -62,60 +61,130 @@ import spinal.lib.fsm._
*
* NA4: This is essentially an edge case of NAPOT where the entire pmpaddr#
* register defines a 4-byte wide region.
*
* N.B. THIS IMPLEMENTATION ONLY SUPPORTS NAPOT ADDRESSING. REGIONS ARE NOT
* ORDERED BY PRIORITY. A PERMISSION IS GRANTED TO AN ACCESS IF ANY MATCHING
* PMP REGION HAS THAT PERMISSION ENABLED.
*/
trait Pmp {
case class PmpRegister(previous : PmpRegister) extends Area {
def OFF = 0
def TOR = 1
def NA4 = 2
def NAPOT = 3
def xlen = 32
def rBit = 0
def wBit = 1
def xBit = 2
def aBits = 4 downto 3
def lBit = 7
}
class PmpSetter(cutoff : Int) extends Component with Pmp {
val io = new Bundle {
val addr = in UInt(xlen bits)
val base, mask = out UInt(xlen - cutoff bits)
val state = new Area {
val r, w, x = Reg(Bool)
val l = RegInit(False)
val a = Reg(UInt(2 bits)) init(0)
val addr = Reg(UInt(32 bits))
}
val ones = io.addr & ~(io.addr + 1)
io.base := io.addr(xlen - 3 downto cutoff - 2) ^ ones(xlen - 3 downto cutoff - 2)
io.mask := ~(ones(xlen - 4 downto cutoff - 2) @@ U"1")
// CSR writes connect to these signals rather than the internal state
// registers. This makes locking and WARL possible.
val csr = new Area {
val r, w, x = Bool
val l = Bool
val a = UInt(2 bits)
val addr = UInt(32 bits)
}
// Last valid assignment wins; nothing happens if a user-initiated write did
// not occur on this clock cycle.
csr.r := state.r
csr.w := state.w
csr.x := state.x
csr.l := state.l
csr.a := state.a
csr.addr := state.addr
// Computed PMP region bounds
val region = new Area {
val valid, locked = Bool
// The calculated start & end addresses can overflow xlen by 4 bit:
//
// - 2 bit, as the pmpaddrX registers are defined as to encode
// [XLEN + 2 downto 2] addresses.
//
// - 2 bit, as for NAPOT the most significant 0 bit encodes the region
// length, with this bit included in the range!
//
// This means that (for xlen == 32 bit)
//
// pmpcfg(X / 4)(X % 4) = NAPOT
// pmpaddrX = 0xFFFFFFFF
//
// will expand to
//
// start (inclusive): 0x000000000 << 2
// end (exclusive): 0x200000000 << 2
//
// hence requiring xlen + 2 + 2 bit to represent the exclusive end
// address. This could be optimized by using a saturating add, or making the
// end address exclusive.
val start, end = UInt(36 bits)
}
when(~state.l) {
state.r := csr.r
state.w := csr.w
state.x := csr.x
state.l := csr.l
state.a := csr.a
state.addr := csr.addr
if (csr.l == True & csr.a == TOR) {
previous.state.l := True
}
}
// Extend state.addr to 36 bits, to avoid these computations overflowing (as
// explained above):
val extended_addr = (B"00" ## state.addr.asBits).asUInt
val shifted = extended_addr << 2
val mask = extended_addr ^ (extended_addr + 1)
val masked = (extended_addr & ~mask) << 2
// PMP changes take effect two clock cycles after the initial CSR write (i.e.,
// settings propagate from csr -> state -> region).
region.locked := state.l
region.valid := True
switch(csr.a) {
is(TOR) {
if (previous == null) region.start := 0
else region.start := previous.region.end
region.end := shifted
}
is(NA4) {
region.start := shifted
region.end := shifted + 4
}
is(NAPOT) {
region.start := masked
region.end := masked + ((mask + 1) << 2)
}
default {
region.start := 0
region.end := shifted
region.valid := False
}
}
}
case class ProtectedMemoryTranslatorPort(bus : MemoryTranslatorBus)
class PmpPlugin(regions : Int, granularity : Int, ioRange : UInt => Bool) extends Plugin[VexRiscv] with MemoryTranslator with Pmp {
assert(regions % 4 == 0 & regions <= 16)
assert(granularity >= 8)
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]()
var setter : PmpSetter = null
var dPort, iPort : ProtectedMemoryTranslatorPort = null
val cutoff = log2Up(granularity) - 1
override def newTranslationPort(priority : Int, args : Any): MemoryTranslatorBus = {
val port = ProtectedMemoryTranslatorPort(MemoryTranslatorBus(new MemoryTranslatorBusParameter(0, 0)))
priority match {
case PRIORITY_INSTRUCTION => iPort = port
case PRIORITY_DATA => dPort = port
}
portsInfo += port
port.bus
}
override def setup(pipeline: VexRiscv): Unit = {
setter = new PmpSetter(cutoff)
}
override def build(pipeline: VexRiscv): Unit = {
import pipeline.config._
import pipeline._
@ -124,184 +193,77 @@ class PmpPlugin(regions : Int, granularity : Int, ioRange : UInt => Bool) extend
val csrService = pipeline.service(classOf[CsrInterface])
val privilegeService = pipeline.service(classOf[PrivilegeService])
val state = pipeline plug new Area {
val pmpaddr = Mem(UInt(xlen bits), regions)
val pmpcfg = Vector.fill(regions)(Reg(Bits(8 bits)) init (0))
val base, mask = Vector.fill(regions)(Reg(UInt(xlen - cutoff bits)))
}
val core = pipeline plug new Area {
def machineMode : Bool = privilegeService.isMachine()
execute plug new Area {
import execute._
val fsmPending = RegInit(False) clearWhen(!arbitration.isStuck)
val fsmComplete = False
val hazardFree = csrService.isHazardFree()
val csrAddress = input(INSTRUCTION)(csrRange)
val pmpNcfg = csrAddress(log2Up(regions) - 1 downto 0).asUInt
val pmpcfgN = pmpNcfg(log2Up(regions) - 3 downto 0)
val pmpcfgCsr = input(INSTRUCTION)(31 downto 24) === 0x3a
val pmpaddrCsr = input(INSTRUCTION)(31 downto 24) === 0x3b
val pmpNcfg_ = Reg(UInt(log2Up(regions) bits))
val pmpcfgN_ = Reg(UInt(log2Up(regions) - 2 bits))
val pmpcfgCsr_ = RegInit(False)
val pmpaddrCsr_ = RegInit(False)
val writeData_ = Reg(Bits(xlen bits))
csrService.duringAnyRead {
when (machineMode) {
when (pmpcfgCsr) {
csrService.allowCsr()
csrService.readData() :=
state.pmpcfg(pmpcfgN @@ U(3, 2 bits)) ##
state.pmpcfg(pmpcfgN @@ U(2, 2 bits)) ##
state.pmpcfg(pmpcfgN @@ U(1, 2 bits)) ##
state.pmpcfg(pmpcfgN @@ U(0, 2 bits))
}
when (pmpaddrCsr) {
csrService.allowCsr()
csrService.readData() := state.pmpaddr(pmpNcfg).asBits
}
// Instantiate pmpaddr0 ... pmpaddr# CSRs.
for (i <- 0 until regions) {
if (i == 0) {
pmps += PmpRegister(null)
} else {
pmps += PmpRegister(pmps.last)
}
csrService.r(0x3b0 + i, pmps(i).state.addr)
csrService.w(0x3b0 + i, pmps(i).csr.addr)
}
csrService.duringAnyWrite {
when ((pmpcfgCsr | pmpaddrCsr) & machineMode) {
csrService.allowCsr()
arbitration.haltItself := !fsmComplete
when (!fsmPending && hazardFree) {
fsmPending := True
writeData_ := csrService.writeData()
pmpNcfg_ := pmpNcfg
pmpcfgN_ := pmpcfgN
pmpcfgCsr_ := pmpcfgCsr
pmpaddrCsr_ := pmpaddrCsr
}
}
}
val fsm = new StateMachine {
val fsmEnable = RegInit(False)
val fsmCounter = Reg(UInt(log2Up(regions) bits)) init(0)
val stateIdle : State = new State with EntryPoint {
onEntry {
fsmPending := False
fsmEnable := False
fsmComplete := True
fsmCounter := 0
}
whenIsActive {
when (fsmPending) {
goto(stateWrite)
}
}
}
val stateWrite : State = new State {
whenIsActive {
when (pmpcfgCsr_) {
val overwrite = writeData_.subdivideIn(8 bits)
for (i <- 0 until 4) {
when (~state.pmpcfg(pmpcfgN_ @@ U(i, 2 bits))(lBit)) {
state.pmpcfg(pmpcfgN_ @@ U(i, 2 bits)).assignFromBits(overwrite(i))
}
}
goto(stateCfg)
}
when (pmpaddrCsr_) {
when (~state.pmpcfg(pmpNcfg_)(lBit)) {
state.pmpaddr(pmpNcfg_) := writeData_.asUInt
}
goto(stateAddr)
}
}
onExit (fsmEnable := True)
}
val stateCfg : State = new State {
onEntry (fsmCounter := pmpcfgN_ @@ U(0, 2 bits))
whenIsActive {
fsmCounter := fsmCounter + 1
when (fsmCounter(1 downto 0) === 3) {
goto(stateIdle)
}
}
}
val stateAddr : State = new State {
onEntry (fsmCounter := pmpNcfg_)
whenIsActive (goto(stateIdle))
}
when (pmpaddrCsr_) {
setter.io.addr := writeData_.asUInt
} otherwise {
setter.io.addr := state.pmpaddr(fsmCounter)
}
when (fsmEnable & ~state.pmpcfg(fsmCounter)(lBit)) {
state.base(fsmCounter) := setter.io.base
state.mask(fsmCounter) := setter.io.mask
}
}
}
pipeline plug new Area {
def getHits(address : UInt) = {
(0 until regions).map(i =>
((address & state.mask(U(i, log2Up(regions) bits))) === state.base(U(i, log2Up(regions) bits))) &
(state.pmpcfg(i)(lBit) | ~machineMode) & (state.pmpcfg(i)(aBits) === NAPOT)
// Instantiate pmpcfg0 ... pmpcfg# CSRs.
for (i <- 0 until (regions / 4)) {
csrService.r(0x3a0 + i,
31 -> pmps((i * 4) + 3).state.l, 23 -> pmps((i * 4) + 2).state.l,
15 -> pmps((i * 4) + 1).state.l, 7 -> pmps((i * 4) ).state.l,
27 -> pmps((i * 4) + 3).state.a, 26 -> pmps((i * 4) + 3).state.x,
25 -> pmps((i * 4) + 3).state.w, 24 -> pmps((i * 4) + 3).state.r,
19 -> pmps((i * 4) + 2).state.a, 18 -> pmps((i * 4) + 2).state.x,
17 -> pmps((i * 4) + 2).state.w, 16 -> pmps((i * 4) + 2).state.r,
11 -> pmps((i * 4) + 1).state.a, 10 -> pmps((i * 4) + 1).state.x,
9 -> pmps((i * 4) + 1).state.w, 8 -> pmps((i * 4) + 1).state.r,
3 -> pmps((i * 4) ).state.a, 2 -> pmps((i * 4) ).state.x,
1 -> pmps((i * 4) ).state.w, 0 -> pmps((i * 4) ).state.r
)
csrService.w(0x3a0 + i,
31 -> pmps((i * 4) + 3).csr.l, 23 -> pmps((i * 4) + 2).csr.l,
15 -> pmps((i * 4) + 1).csr.l, 7 -> pmps((i * 4) ).csr.l,
27 -> pmps((i * 4) + 3).csr.a, 26 -> pmps((i * 4) + 3).csr.x,
25 -> pmps((i * 4) + 3).csr.w, 24 -> pmps((i * 4) + 3).csr.r,
19 -> pmps((i * 4) + 2).csr.a, 18 -> pmps((i * 4) + 2).csr.x,
17 -> pmps((i * 4) + 2).csr.w, 16 -> pmps((i * 4) + 2).csr.r,
11 -> pmps((i * 4) + 1).csr.a, 10 -> pmps((i * 4) + 1).csr.x,
9 -> pmps((i * 4) + 1).csr.w, 8 -> pmps((i * 4) + 1).csr.r,
3 -> pmps((i * 4) ).csr.a, 2 -> pmps((i * 4) ).csr.x,
1 -> pmps((i * 4) ).csr.w, 0 -> pmps((i * 4) ).csr.r
)
}
def getPermission(hits : IndexedSeq[Bool], bit : Int) = {
MuxOH(OHMasking.first(hits), state.pmpcfg.map(_(bit)))
}
// Connect memory ports to PMP logic.
val ports = for ((port, portId) <- portsInfo.zipWithIndex) yield new Area {
val dGuard = new Area {
val address = dPort.bus.cmd(0).virtualAddress
dPort.bus.rsp.physicalAddress := address
dPort.bus.rsp.isIoAccess := ioRange(address)
dPort.bus.rsp.isPaging := False
dPort.bus.rsp.exception := False
dPort.bus.rsp.refilling := False
dPort.bus.rsp.allowExecute := False
dPort.bus.busy := False
val address = port.bus.cmd(0).virtualAddress
port.bus.rsp.physicalAddress := address
val hits = getHits(address(31 downto cutoff))
// Only the first matching PMP region applies.
val hits = pmps.map(pmp => pmp.region.valid &
pmp.region.start <= address &
pmp.region.end > address &
(pmp.region.locked | ~privilegeService.isMachine()))
when(~hits.orR) {
dPort.bus.rsp.allowRead := machineMode
dPort.bus.rsp.allowWrite := machineMode
// M-mode has full access by default, others have none.
when(CountOne(hits) === 0) {
port.bus.rsp.allowRead := privilegeService.isMachine()
port.bus.rsp.allowWrite := privilegeService.isMachine()
port.bus.rsp.allowExecute := privilegeService.isMachine()
} otherwise {
dPort.bus.rsp.allowRead := getPermission(hits, rBit)
dPort.bus.rsp.allowWrite := getPermission(hits, wBit)
port.bus.rsp.allowRead := MuxOH(OHMasking.first(hits), pmps.map(_.state.r))
port.bus.rsp.allowWrite := MuxOH(OHMasking.first(hits), pmps.map(_.state.w))
port.bus.rsp.allowExecute := MuxOH(OHMasking.first(hits), pmps.map(_.state.x))
}
}
val iGuard = new Area {
val address = iPort.bus.cmd(0).virtualAddress
iPort.bus.rsp.physicalAddress := address
iPort.bus.rsp.isIoAccess := ioRange(address)
iPort.bus.rsp.isPaging := False
iPort.bus.rsp.exception := False
iPort.bus.rsp.refilling := False
iPort.bus.rsp.allowRead := False
iPort.bus.rsp.allowWrite := False
iPort.bus.busy := False
port.bus.rsp.isIoAccess := ioRange(port.bus.rsp.physicalAddress)
port.bus.rsp.isPaging := False
port.bus.rsp.exception := False
port.bus.rsp.refilling := False
port.bus.busy := False
val hits = getHits(address(31 downto cutoff))
when(~hits.orR) {
iPort.bus.rsp.allowExecute := machineMode
} otherwise {
iPort.bus.rsp.allowExecute := getPermission(hits, xBit)
}
}
}
}
}
}

View File

@ -0,0 +1,307 @@
/*
* Copyright (c) 2021 Samuel Lindemer <samuel.lindemer@ri.se>
*
* SPDX-License-Identifier: MIT
*/
package vexriscv.plugin
import vexriscv.{VexRiscv, _}
import vexriscv.plugin.MemoryTranslatorPort.{_}
import spinal.core._
import spinal.lib._
import spinal.lib.fsm._
/* Each 32-bit pmpcfg# register contains four 8-bit configuration sections.
* These section numbers contain flags which apply to regions 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 region 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.
*
* N.B. THIS IMPLEMENTATION ONLY SUPPORTS NAPOT ADDRESSING. REGIONS ARE NOT
* ORDERED BY PRIORITY. A PERMISSION IS GRANTED TO AN ACCESS IF ANY MATCHING
* PMP REGION HAS THAT PERMISSION ENABLED.
*/
trait Pmp {
def OFF = 0
def TOR = 1
def NA4 = 2
def NAPOT = 3
def xlen = 32
def rBit = 0
def wBit = 1
def xBit = 2
def aBits = 4 downto 3
def lBit = 7
}
class PmpSetter(cutoff : Int) extends Component with Pmp {
val io = new Bundle {
val addr = in UInt(xlen bits)
val base, mask = out UInt(xlen - cutoff bits)
}
val ones = io.addr & ~(io.addr + 1)
io.base := io.addr(xlen - 3 downto cutoff - 2) ^ ones(xlen - 3 downto cutoff - 2)
io.mask := ~(ones(xlen - 4 downto cutoff - 2) @@ U"1")
}
case class ProtectedMemoryTranslatorPort(bus : MemoryTranslatorBus)
class PmpPluginNapot(regions : Int, granularity : Int, ioRange : UInt => Bool) extends Plugin[VexRiscv] with MemoryTranslator with Pmp {
assert(regions % 4 == 0 & regions <= 16)
assert(granularity >= 8)
var setter : PmpSetter = null
var dPort, iPort : ProtectedMemoryTranslatorPort = null
val cutoff = log2Up(granularity) - 1
override def newTranslationPort(priority : Int, args : Any): MemoryTranslatorBus = {
val port = ProtectedMemoryTranslatorPort(MemoryTranslatorBus(new MemoryTranslatorBusParameter(0, 0)))
priority match {
case PRIORITY_INSTRUCTION => iPort = port
case PRIORITY_DATA => dPort = port
}
port.bus
}
override def setup(pipeline: VexRiscv): Unit = {
setter = new PmpSetter(cutoff)
}
override def build(pipeline: VexRiscv): Unit = {
import pipeline.config._
import pipeline._
import Riscv._
val csrService = pipeline.service(classOf[CsrInterface])
val privilegeService = pipeline.service(classOf[PrivilegeService])
val state = pipeline plug new Area {
val pmpaddr = Mem(UInt(xlen bits), regions)
val pmpcfg = Vector.fill(regions)(Reg(Bits(8 bits)) init (0))
val base, mask = Vector.fill(regions)(Reg(UInt(xlen - cutoff bits)))
}
def machineMode : Bool = privilegeService.isMachine()
execute plug new Area {
import execute._
val fsmPending = RegInit(False) clearWhen(!arbitration.isStuck)
val fsmComplete = False
val hazardFree = csrService.isHazardFree()
val csrAddress = input(INSTRUCTION)(csrRange)
val pmpNcfg = csrAddress(log2Up(regions) - 1 downto 0).asUInt
val pmpcfgN = pmpNcfg(log2Up(regions) - 3 downto 0)
val pmpcfgCsr = input(INSTRUCTION)(31 downto 24) === 0x3a
val pmpaddrCsr = input(INSTRUCTION)(31 downto 24) === 0x3b
val pmpNcfg_ = Reg(UInt(log2Up(regions) bits))
val pmpcfgN_ = Reg(UInt(log2Up(regions) - 2 bits))
val pmpcfgCsr_ = RegInit(False)
val pmpaddrCsr_ = RegInit(False)
val writeData_ = Reg(Bits(xlen bits))
csrService.duringAnyRead {
when (machineMode) {
when (pmpcfgCsr) {
csrService.allowCsr()
csrService.readData() :=
state.pmpcfg(pmpcfgN @@ U(3, 2 bits)) ##
state.pmpcfg(pmpcfgN @@ U(2, 2 bits)) ##
state.pmpcfg(pmpcfgN @@ U(1, 2 bits)) ##
state.pmpcfg(pmpcfgN @@ U(0, 2 bits))
}
when (pmpaddrCsr) {
csrService.allowCsr()
csrService.readData() := state.pmpaddr(pmpNcfg).asBits
}
}
}
csrService.duringAnyWrite {
when ((pmpcfgCsr | pmpaddrCsr) & machineMode) {
csrService.allowCsr()
arbitration.haltItself := !fsmComplete
when (!fsmPending && hazardFree) {
fsmPending := True
writeData_ := csrService.writeData()
pmpNcfg_ := pmpNcfg
pmpcfgN_ := pmpcfgN
pmpcfgCsr_ := pmpcfgCsr
pmpaddrCsr_ := pmpaddrCsr
}
}
}
val fsm = new StateMachine {
val fsmEnable = RegInit(False)
val fsmCounter = Reg(UInt(log2Up(regions) bits)) init(0)
val stateIdle : State = new State with EntryPoint {
onEntry {
fsmPending := False
fsmEnable := False
fsmComplete := True
fsmCounter := 0
}
whenIsActive {
when (fsmPending) {
goto(stateWrite)
}
}
}
val stateWrite : State = new State {
whenIsActive {
when (pmpcfgCsr_) {
val overwrite = writeData_.subdivideIn(8 bits)
for (i <- 0 until 4) {
when (~state.pmpcfg(pmpcfgN_ @@ U(i, 2 bits))(lBit)) {
state.pmpcfg(pmpcfgN_ @@ U(i, 2 bits)).assignFromBits(overwrite(i))
}
}
goto(stateCfg)
}
when (pmpaddrCsr_) {
when (~state.pmpcfg(pmpNcfg_)(lBit)) {
state.pmpaddr(pmpNcfg_) := writeData_.asUInt
}
goto(stateAddr)
}
}
onExit (fsmEnable := True)
}
val stateCfg : State = new State {
onEntry (fsmCounter := pmpcfgN_ @@ U(0, 2 bits))
whenIsActive {
fsmCounter := fsmCounter + 1
when (fsmCounter(1 downto 0) === 3) {
goto(stateIdle)
}
}
}
val stateAddr : State = new State {
onEntry (fsmCounter := pmpNcfg_)
whenIsActive (goto(stateIdle))
}
when (pmpaddrCsr_) {
setter.io.addr := writeData_.asUInt
} otherwise {
setter.io.addr := state.pmpaddr(fsmCounter)
}
when (fsmEnable & ~state.pmpcfg(fsmCounter)(lBit)) {
state.base(fsmCounter) := setter.io.base
state.mask(fsmCounter) := setter.io.mask
}
}
}
pipeline plug new Area {
def getHits(address : UInt) = {
(0 until regions).map(i =>
((address & state.mask(U(i, log2Up(regions) bits))) === state.base(U(i, log2Up(regions) bits))) &
(state.pmpcfg(i)(lBit) | ~machineMode) & (state.pmpcfg(i)(aBits) === NAPOT)
)
}
def getPermission(hits : IndexedSeq[Bool], bit : Int) = {
MuxOH(OHMasking.first(hits), state.pmpcfg.map(_(bit)))
}
val dGuard = new Area {
val address = dPort.bus.cmd(0).virtualAddress
dPort.bus.rsp.physicalAddress := address
dPort.bus.rsp.isIoAccess := ioRange(address)
dPort.bus.rsp.isPaging := False
dPort.bus.rsp.exception := False
dPort.bus.rsp.refilling := False
dPort.bus.rsp.allowExecute := False
dPort.bus.busy := False
val hits = getHits(address(31 downto cutoff))
when(~hits.orR) {
dPort.bus.rsp.allowRead := machineMode
dPort.bus.rsp.allowWrite := machineMode
} otherwise {
dPort.bus.rsp.allowRead := getPermission(hits, rBit)
dPort.bus.rsp.allowWrite := getPermission(hits, wBit)
}
}
val iGuard = new Area {
val address = iPort.bus.cmd(0).virtualAddress
iPort.bus.rsp.physicalAddress := address
iPort.bus.rsp.isIoAccess := ioRange(address)
iPort.bus.rsp.isPaging := False
iPort.bus.rsp.exception := False
iPort.bus.rsp.refilling := False
iPort.bus.rsp.allowRead := False
iPort.bus.rsp.allowWrite := False
iPort.bus.busy := False
val hits = getHits(address(31 downto cutoff))
when(~hits.orR) {
iPort.bus.rsp.allowExecute := machineMode
} otherwise {
iPort.bus.rsp.allowExecute := getPermission(hits, xBit)
}
}
}
}
}

View File

@ -1,269 +0,0 @@
/*
* Copyright (c) 2020 Samuel Lindemer <samuel.lindemer@ri.se>
*
* 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 regions 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 region 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(previous : PmpRegister) extends Area {
def OFF = 0
def TOR = 1
def NA4 = 2
def NAPOT = 3
val state = new Area {
val r, w, x = Reg(Bool)
val l = RegInit(False)
val a = Reg(UInt(2 bits)) init(0)
val addr = Reg(UInt(32 bits))
}
// CSR writes connect to these signals rather than the internal state
// registers. This makes locking and WARL possible.
val csr = new Area {
val r, w, x = Bool
val l = Bool
val a = UInt(2 bits)
val addr = UInt(32 bits)
}
// Last valid assignment wins; nothing happens if a user-initiated write did
// not occur on this clock cycle.
csr.r := state.r
csr.w := state.w
csr.x := state.x
csr.l := state.l
csr.a := state.a
csr.addr := state.addr
// Computed PMP region bounds
val region = new Area {
val valid, locked = Bool
// The calculated start & end addresses can overflow xlen by 4 bit:
//
// - 2 bit, as the pmpaddrX registers are defined as to encode
// [XLEN + 2 downto 2] addresses.
//
// - 2 bit, as for NAPOT the most significant 0 bit encodes the region
// length, with this bit included in the range!
//
// This means that (for xlen == 32 bit)
//
// pmpcfg(X / 4)(X % 4) = NAPOT
// pmpaddrX = 0xFFFFFFFF
//
// will expand to
//
// start (inclusive): 0x000000000 << 2
// end (exclusive): 0x200000000 << 2
//
// hence requiring xlen + 2 + 2 bit to represent the exclusive end
// address. This could be optimized by using a saturating add, or making the
// end address exclusive.
val start, end = UInt(36 bits)
}
when(~state.l) {
state.r := csr.r
state.w := csr.w
state.x := csr.x
state.l := csr.l
state.a := csr.a
state.addr := csr.addr
if (csr.l == True & csr.a == TOR) {
previous.state.l := True
}
}
// Extend state.addr to 36 bits, to avoid these computations overflowing (as
// explained above):
val extended_addr = (B"00" ## state.addr.asBits).asUInt
val shifted = extended_addr << 2
val mask = extended_addr ^ (extended_addr + 1)
val masked = (extended_addr & ~mask) << 2
// PMP changes take effect two clock cycles after the initial CSR write (i.e.,
// settings propagate from csr -> state -> region).
region.locked := state.l
region.valid := True
switch(csr.a) {
is(TOR) {
if (previous == null) region.start := 0
else region.start := previous.region.end
region.end := shifted
}
is(NA4) {
region.start := shifted
region.end := shifted + 4
}
is(NAPOT) {
region.start := masked
region.end := masked + ((mask + 1) << 2)
}
default {
region.start := 0
region.end := shifted
region.valid := False
}
}
}
class PmpPluginOld(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(new MemoryTranslatorBusParameter(0, 0)))
portsInfo += port
port.bus
}
override def build(pipeline: VexRiscv): Unit = {
import pipeline.config._
import pipeline._
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 (i <- 0 until regions) {
if (i == 0) {
pmps += PmpRegister(null)
} else {
pmps += PmpRegister(pmps.last)
}
csrService.r(0x3b0 + i, pmps(i).state.addr)
csrService.w(0x3b0 + i, pmps(i).csr.addr)
}
// Instantiate pmpcfg0 ... pmpcfg# CSRs.
for (i <- 0 until (regions / 4)) {
csrService.r(0x3a0 + i,
31 -> pmps((i * 4) + 3).state.l, 23 -> pmps((i * 4) + 2).state.l,
15 -> pmps((i * 4) + 1).state.l, 7 -> pmps((i * 4) ).state.l,
27 -> pmps((i * 4) + 3).state.a, 26 -> pmps((i * 4) + 3).state.x,
25 -> pmps((i * 4) + 3).state.w, 24 -> pmps((i * 4) + 3).state.r,
19 -> pmps((i * 4) + 2).state.a, 18 -> pmps((i * 4) + 2).state.x,
17 -> pmps((i * 4) + 2).state.w, 16 -> pmps((i * 4) + 2).state.r,
11 -> pmps((i * 4) + 1).state.a, 10 -> pmps((i * 4) + 1).state.x,
9 -> pmps((i * 4) + 1).state.w, 8 -> pmps((i * 4) + 1).state.r,
3 -> pmps((i * 4) ).state.a, 2 -> pmps((i * 4) ).state.x,
1 -> pmps((i * 4) ).state.w, 0 -> pmps((i * 4) ).state.r
)
csrService.w(0x3a0 + i,
31 -> pmps((i * 4) + 3).csr.l, 23 -> pmps((i * 4) + 2).csr.l,
15 -> pmps((i * 4) + 1).csr.l, 7 -> pmps((i * 4) ).csr.l,
27 -> pmps((i * 4) + 3).csr.a, 26 -> pmps((i * 4) + 3).csr.x,
25 -> pmps((i * 4) + 3).csr.w, 24 -> pmps((i * 4) + 3).csr.r,
19 -> pmps((i * 4) + 2).csr.a, 18 -> pmps((i * 4) + 2).csr.x,
17 -> pmps((i * 4) + 2).csr.w, 16 -> pmps((i * 4) + 2).csr.r,
11 -> pmps((i * 4) + 1).csr.a, 10 -> pmps((i * 4) + 1).csr.x,
9 -> pmps((i * 4) + 1).csr.w, 8 -> pmps((i * 4) + 1).csr.r,
3 -> pmps((i * 4) ).csr.a, 2 -> pmps((i * 4) ).csr.x,
1 -> pmps((i * 4) ).csr.w, 0 -> pmps((i * 4) ).csr.r
)
}
// Connect memory ports to PMP logic.
val ports = for ((port, portId) <- portsInfo.zipWithIndex) yield new Area {
val address = port.bus.cmd(0).virtualAddress
port.bus.rsp.physicalAddress := address
// Only the first matching PMP region applies.
val hits = pmps.map(pmp => pmp.region.valid &
pmp.region.start <= address &
pmp.region.end > address &
(pmp.region.locked | ~privilegeService.isMachine()))
// M-mode has full access by default, others have none.
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.map(_.state.r))
port.bus.rsp.allowWrite := MuxOH(OHMasking.first(hits), pmps.map(_.state.w))
port.bus.rsp.allowExecute := MuxOH(OHMasking.first(hits), pmps.map(_.state.x))
}
port.bus.rsp.isIoAccess := ioRange(port.bus.rsp.physicalAddress)
port.bus.rsp.isPaging := False
port.bus.rsp.exception := False
port.bus.rsp.refilling := False
port.bus.busy := False
}
}
}
}

View File

@ -52,6 +52,7 @@ object VexRiscvUniverse{
val CATCH_ALL = new VexRiscvUniverse
val MMU = new VexRiscvUniverse
val PMP = new VexRiscvUniverse
val PMPNAPOT = new VexRiscvUniverse
val FORCE_MULDIV = new VexRiscvUniverse
val SUPERVISOR = new VexRiscvUniverse
val NO_WRITEBACK = new VexRiscvUniverse
@ -521,6 +522,17 @@ class MmuPmpDimension extends VexRiscvDimension("DBus") {
override def applyOn(config: VexRiscvConfig): Unit = {
config.plugins += new PmpPlugin(
regions = 16,
ioRange = _ (31 downto 28) === 0xF
)
}
}
} else if (universes.contains(VexRiscvUniverse.PMPNAPOT)) {
new VexRiscvPosition("WithPmpNapot") {
override def testParam = "MMU=no PMP=yes"
override def applyOn(config: VexRiscvConfig): Unit = {
config.plugins += new PmpPluginNapot(
regions = 16,
granularity = 32,
ioRange = _ (31 downto 28) === 0xF
@ -548,7 +560,7 @@ trait CatchAllPosition
class CsrDimension(freertos : String, zephyr : String, linux : String) extends VexRiscvDimension("Csr") {
override def randomPositionImpl(universes: Seq[ConfigUniverse], r: Random) = {
val pmp = universes.contains(VexRiscvUniverse.PMP)
val pmp = universes.contains(VexRiscvUniverse.PMP) || universes.contains(VexRiscvUniverse.PMPNAPOT)
val catchAll = universes.contains(VexRiscvUniverse.CATCH_ALL)
val supervisor = universes.contains(VexRiscvUniverse.SUPERVISOR)
if(supervisor){
@ -704,6 +716,7 @@ class TestIndividualFeatures extends MultithreadedFunSuite(sys.env.getOrElse("VE
val demwRate = sys.env.getOrElse("VEXRISCV_REGRESSION_CONFIG_DEMW_RATE", "0.6").toDouble
val demRate = sys.env.getOrElse("VEXRISCV_REGRESSION_CONFIG_DEM_RATE", "0.5").toDouble
val stopOnError = sys.env.getOrElse("VEXRISCV_REGRESSION_STOP_ON_ERROR", "no")
val pmpNapotRate = sys.env.getOrElse("VEXRISCV_REGRESSION_CONFIG_PMP_NAPOT_RANGE", "0.5").toDouble
val lock = new{}
@ -808,7 +821,11 @@ class TestIndividualFeatures extends MultithreadedFunSuite(sys.env.getOrElse("VE
} else if (secureRate > rand.nextDouble()) {
universe += VexRiscvUniverse.CACHE_ALL
universe += VexRiscvUniverse.CATCH_ALL
universe += VexRiscvUniverse.PMP
if (pmpNapotRate > rand.nextDouble()) {
universe += VexRiscvUniverse.PMP
} else {
universe += VexRiscvUniverse.PMPNAPOT
}
if(demwRate < rand.nextDouble()){
universe += VexRiscvUniverse.NO_WRITEBACK
}