Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perf counters #388

Merged
merged 20 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
- [YamlPlugin](#yamlplugin)
- [FpuPlugin](#fpuplugin)
- [AesPlugin](#aesplugin)
- [CounterPlugin](#counterplugin)



Expand Down Expand Up @@ -1363,3 +1364,21 @@ It was also ported on libressl via the following patch :
<https://github.com/SpinalHDL/buildroot-spinal-saxon/blob/main/patches/libressl/0000-vexriscv-aes.patch>

Speed up of 4 was observed in libressl running in linux. <https://github.com/SpinalHDL/SaxonSoc/pull/53#issuecomment-730133020>

#### CounterPlugin

Provides performance-counter and time CSRs.

Here is how to provide a custom event condition (which can then be configured by code):
In setup phase
```scala
val ctrSrv = pipeline.service(classOf[CounterService])
ctrSrv.createEvent(eventId)
```
In build phase
```scala
val ctrSrv = pipeline.service(classOf[CounterService])
ctrSrv.getCondition(eventId) := boolCond
```
eventId is BigInt, but only events between 0 and 2 ** XLEN (excluding boundaries) can be selected by cpu.
The configured counter counts clockcycles with boolCond asserted.
6 changes: 6 additions & 0 deletions src/main/scala/vexriscv/Riscv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ object Riscv{
def MCYCLEH = 0xB80 // MRW Upper 32 bits of mcycle, RV32I only.
def MINSTRETH = 0xB82 // MRW Upper 32 bits of minstret, RV32I only.
val MCOUNTEREN = 0x306
val MCOUNTER = 0xB03 // MRW Base address for mhpmcounterX.
val MCOUNTERH = 0xB83 // MRW Base address for mhpmcounterXh, RV32I only.
val MCOUNTINHIBIT = 0x320
val MEVENT = 0x323 // MRW Base address for mhpmeventX.

val SSTATUS = 0x100
val SIE = 0x104
Expand All @@ -234,6 +238,8 @@ object Riscv{
def UTIMEH = 0xC81
def UINSTRET = 0xC02 // UR Machine instructions-retired counter.
def UINSTRETH = 0xC82 // UR Upper 32 bits of minstret, RV32I only.
val UCOUNTER = 0xC03 // UR Base address for hpmcounter.
val UCOUNTERH = 0xC83 // UR Base address for hpmcounterXh, RV32I only.

val FFLAGS = 0x1
val FRM = 0x2
Expand Down
6 changes: 5 additions & 1 deletion src/main/scala/vexriscv/Services.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ trait PrivilegeService{
def isUser() : Bool
def isSupervisor() : Bool
def isMachine() : Bool
def hasUser() : Boolean
def hasSupervisor() : Boolean
def forceMachine() : Unit

def encodeBits() : Bits = {
Expand All @@ -70,6 +72,8 @@ case class PrivilegeServiceDefault() extends PrivilegeService{
override def isUser(): Bool = False
override def isSupervisor(): Bool = False
override def isMachine(): Bool = True
override def hasUser(): Boolean = false
override def hasSupervisor(): Boolean = false
override def forceMachine(): Unit = {}
}

Expand Down Expand Up @@ -143,4 +147,4 @@ class CacheReport {

class DebugReport {
@BeanProperty var hardwareBreakpointCount = 0
}
}
1 change: 1 addition & 0 deletions src/main/scala/vexriscv/demo/GenFull.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ object GenFull extends App{
earlyBranch = false,
catchAddressMisaligned = true
),
new CounterPlugin(CounterPluginConfig()),
new YamlPlugin("cpu0.yaml")
)
)
Expand Down
19 changes: 17 additions & 2 deletions src/main/scala/vexriscv/demo/VexRiscvAxi4LinuxPlicClint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,20 @@ object VexRiscvAxi4LinuxPlicClint{
earlyBranch = false,
catchAddressMisaligned = true
),
new CsrPlugin(CsrPluginConfig.openSbi(mhartid = 0, misa = Riscv.misaToInt(s"ima")).copy(utimeAccess = CsrAccess.READ_ONLY)),
new CsrPlugin(CsrPluginConfig.openSbi(mhartid = 0, misa = Riscv.misaToInt(s"ima"))),
new CounterPlugin(CounterPluginConfig(
NumOfCounters = 0,
mcycleAccess = CsrAccess.NONE,
ucycleAccess = CsrAccess.NONE,
minstretAccess = CsrAccess.NONE,
uinstretAccess = CsrAccess.NONE,
mcounterenAccess = CsrAccess.NONE,
scounterenAccess = CsrAccess.NONE,
mcounterAccess = CsrAccess.NONE,
ucounterAccess = CsrAccess.NONE,
meventAccess = CsrAccess.NONE,
mcountinhibitAccess = CsrAccess.NONE
)),
new YamlPlugin("cpu0.yaml")
)
)
Expand Down Expand Up @@ -152,7 +165,9 @@ object VexRiscvAxi4LinuxPlicClint{
plugin.softwareInterrupt setAsDirectionLess() := cpu.clintCtrl.io.softwareInterrupt(0)
plugin.externalInterrupt setAsDirectionLess() := cpu.plicCtrl.io.targets(0)
plugin.externalInterruptS setAsDirectionLess() := cpu.plicCtrl.io.targets(1)
plugin.utime setAsDirectionLess() := cpu.clintCtrl.io.time
}
case plugin: CounterPlugin => {
plugin.time setAsDirectionLess() := cpu.clintCtrl.io.time
}
case _ =>
}
Expand Down
17 changes: 15 additions & 2 deletions src/main/scala/vexriscv/demo/smp/VexRiscvSmpCluster.scala
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ class VexRiscvSmpClusterWithPeripherals(p : VexRiscvSmpClusterParameter) extends
plic.addTarget(core.cpu.externalSupervisorInterrupt)
List(clint.logic, core.cpu.logic).produce {
for (plugin <- core.cpu.config.plugins) plugin match {
case plugin: CsrPlugin if plugin.utime != null => plugin.utime := clint.logic.io.time
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, would need to preserve that feature in the CsrPlugin. (even if that is a duplication) to not break people code

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature is still in CSRPlugin, even if disabled when CounterService is present (which in this demo it is), so it should break no existing code which uses the Counters in CsrPlugin.

case plugin: CounterPlugin if plugin.time != null => plugin.time := clint.logic.io.time
case _ =>
}
}
Expand Down Expand Up @@ -276,7 +276,7 @@ object VexRiscvSmpClusterGen {

val misa = Riscv.misaToInt(s"ima${if(withFloat) "f" else ""}${if(withDouble) "d" else ""}${if(rvc) "c" else ""}${if(withSupervisor) "su" else ""}")
val csrConfig = if(withSupervisor){
var c = CsrPluginConfig.openSbi(mhartid = hartId, misa = misa).copy(utimeAccess = CsrAccess.READ_ONLY, withPrivilegedDebug = privilegedDebug)
var c = CsrPluginConfig.openSbi(mhartid = hartId, misa = misa).copy(withPrivilegedDebug = privilegedDebug)
if(csrFull){
c = c.copy(
mcauseAccess = CsrAccess.READ_WRITE,
Expand Down Expand Up @@ -430,6 +430,19 @@ object VexRiscvSmpClusterGen {
catchAddressMisaligned = true,
fenceiGenAsAJump = false
),
new CounterPlugin(if(csrFull) CounterPluginConfig() else CounterPluginConfig(
NumOfCounters = 0,
mcycleAccess = CsrAccess.NONE,
ucycleAccess = CsrAccess.NONE,
minstretAccess = CsrAccess.NONE,
uinstretAccess = CsrAccess.NONE,
mcounterenAccess = CsrAccess.NONE,
scounterenAccess = CsrAccess.NONE,
mcounterAccess = CsrAccess.NONE,
ucounterAccess = CsrAccess.NONE,
meventAccess = CsrAccess.NONE,
mcountinhibitAccess = CsrAccess.NONE
)),
new YamlPlugin(s"cpu$hartId.yaml")
)
)
Expand Down
230 changes: 230 additions & 0 deletions src/main/scala/vexriscv/plugin/CounterPlugin.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
package vexriscv.plugin

import spinal.core._

import vexriscv._
import vexriscv.Riscv.CSR._

import scala.collection.mutable._

trait CounterService{
def createEvent(eventId : BigInt) : Unit
def getCondition(eventId : BigInt) : Bool
}

case class CounterPluginConfig(
NumOfCounters : Byte = 29,

mcycleAccess : CsrAccess = CsrAccess.READ_WRITE,
ucycleAccess : CsrAccess = CsrAccess.READ_ONLY,

minstretAccess : CsrAccess = CsrAccess.READ_WRITE,
uinstretAccess : CsrAccess = CsrAccess.READ_ONLY,

utimeAccess : CsrAccess = CsrAccess.READ_ONLY,

mcounterenAccess : CsrAccess = CsrAccess.READ_WRITE,
scounterenAccess : CsrAccess = CsrAccess.READ_WRITE,

mcounterAccess : CsrAccess = CsrAccess.READ_WRITE,
ucounterAccess : CsrAccess = CsrAccess.READ_ONLY,

// management
meventAccess : CsrAccess = CsrAccess.READ_WRITE,
mcountinhibitAccess : CsrAccess = CsrAccess.READ_WRITE
) {
assert(!ucycleAccess.canWrite)
}

object Priv{
val M = 3
val S = 1
val U = 0
}

class CounterPlugin(config : CounterPluginConfig) extends Plugin[VexRiscv] with CounterService {
import config._

def xlen = 32

assert(NumOfCounters <= 29, "Cannot create more than 29 custom counters")
assert(NumOfCounters >= 0, "Cannot create less than 0 custom counters")

// counters : Array[Reg] = null
// event : Array[Reg] = null

// mcouen : Reg = null
// scouen : Reg = null

// inhibit : Reg = null

val eventType : Map[BigInt, Bool] = new HashMap()

var time : UInt = null

implicit class PrivilegeHelper(p : PrivilegeService){
def canMachine() : Bool = p.isMachine()
def canSupervisor() : Bool = p.isMachine() || p.isSupervisor()
}

implicit class CsrAccessHelper(csrAccess : CsrAccess){
import CsrAccess._
import Priv._
def apply(csrService : CsrInterface, csrAddress : Int, that : Data) : Unit = {
if(csrAccess == `WRITE_ONLY` || csrAccess == `READ_WRITE`) csrService.w(csrAddress, 0, that)
if(csrAccess == `READ_ONLY` || csrAccess == `READ_WRITE`) csrService.r(csrAddress, 0, that)
}
def apply(
csrSrv : CsrInterface,
prvSrv : PrivilegeService,
csrAddress : Int,
that : Data,
privAllows : (Int, Bool)*
) : Unit = {
apply(csrSrv, csrAddress, that)
if(csrAccess != CsrAccess.NONE) csrSrv.during(csrAddress){
for (ii <- privAllows) {
if (ii._1 == M)
when (~prvSrv.canMachine() || ~ii._2) { csrSrv.forceFailCsr() }
if (ii._1 == S && prvSrv.hasSupervisor())
when (~prvSrv.canSupervisor() || ~ii._2) { csrSrv.forceFailCsr() }
if (prvSrv.hasUser())
when (~ii._2) { csrSrv.forceFailCsr() }
}
}
}
}

override def createEvent(eventId : BigInt) : Unit = {
if (!eventType.contains(eventId)) {
eventType(eventId) = Bool
}
}

override def getCondition(eventId : BigInt) : Bool = {
eventType(eventId)
}

override def setup(pipeline : VexRiscv) : Unit = {
import pipeline._
import pipeline.config._

if (utimeAccess != CsrAccess.NONE) time = in UInt(64 bits) setName("utime")
}

override def build(pipeline : VexRiscv) : Unit = {
import pipeline._
import pipeline.config._

pipeline plug new Area{
val csrSrv = pipeline.service(classOf[CsrInterface])
val dbgSrv = pipeline.service(classOf[DebugService])
val prvSrv = pipeline.service(classOf[PrivilegeService])

val dbgCtrEn = ~(dbgSrv.inDebugMode() && dbgSrv.debugState().dcsr.stopcount)

val menable = RegInit(Bits(3 + NumOfCounters bits).getAllTrue) allowUnsetRegToAvoidLatch
val senable = RegInit(Bits(3 + NumOfCounters bits).getAllTrue) allowUnsetRegToAvoidLatch
val inhibit = Reg(Bits(NumOfCounters bits)) init(0)
val inhibitCY = RegInit(False)
val inhibitIR = RegInit(False)

val cycle = Reg(UInt(64 bits)) init(0)
cycle := cycle + U(dbgCtrEn && ~inhibitCY)
val instret = Reg(UInt(64 bits)) init(0)
when(pipeline.stages.last.arbitration.isFiring) {
instret := instret + U(dbgCtrEn && ~inhibitIR)
}

val counter = Array.fill(NumOfCounters){Reg(UInt(64 bits)) init(0)}
val events = Array.fill(NumOfCounters){Reg(UInt(xlen bits)) init(0)}

var customCounters = new Area {
val increment = Array.fill(NumOfCounters){Bool}

for (ii <- 0 until NumOfCounters) {
counter(ii) := counter(ii) + U(dbgCtrEn && ~inhibit(ii) && increment(ii))

increment(ii) := False

for (event <- eventType) {
when (event._1 =/= U(0, xlen bits) && event._1 === events(ii)) {
increment(ii) := event._2
}
}
}
}

val expose = new Area {
import Priv._
// inhibit
csrSrv.during(MCOUNTINHIBIT){ when (~prvSrv.isMachine()) {csrSrv.forceFailCsr()} }
csrSrv.rw(MCOUNTINHIBIT, 0 -> inhibitCY, 2 -> inhibitIR, 3 -> inhibit)

// enable
mcounterenAccess(csrSrv, prvSrv, MCOUNTEREN, menable, S -> False, U -> False)
scounterenAccess(csrSrv, prvSrv, SCOUNTEREN, senable, U -> False)

// custom counters
for (ii <- 0 until NumOfCounters) {
ucounterAccess(csrSrv, prvSrv, UCOUNTER + ii, counter(ii)(31 downto 0),
S -> menable(3 + ii),
U -> (if (prvSrv.hasSupervisor()) senable(3 + ii) else True),
U -> menable(3 + ii)
)
ucounterAccess(csrSrv, prvSrv, UCOUNTERH + ii, counter(ii)(63 downto 32),
S -> menable(3 + ii),
U -> (if (prvSrv.hasSupervisor()) senable(3 + ii) else True),
U -> menable(3 + ii)
)

mcounterAccess(csrSrv, prvSrv, MCOUNTER + ii, counter(ii)(31 downto 0), S -> False, U -> False)
mcounterAccess(csrSrv, prvSrv, MCOUNTERH + ii, counter(ii)(63 downto 32), S -> False, U -> False)
meventAccess(csrSrv, prvSrv, MEVENT + ii, events(ii), S -> False, U -> False)
}

// fixed counters
ucycleAccess(csrSrv, prvSrv, UCYCLE, cycle(31 downto 0),
S -> menable(0),
U -> (if (prvSrv.hasSupervisor()) senable(0) else True),
U -> menable(0)
)
ucycleAccess(csrSrv, prvSrv, UCYCLEH, cycle(63 downto 32),
S -> menable(0),
U -> (if (prvSrv.hasSupervisor()) senable(0) else True),
U -> menable(0)
)

mcycleAccess(csrSrv, prvSrv, MCYCLE, cycle(31 downto 0), S -> False, U -> False)
mcycleAccess(csrSrv, prvSrv, MCYCLEH, cycle(63 downto 32), S -> False, U -> False)

if(utimeAccess != CsrAccess.NONE) {
utimeAccess(csrSrv, prvSrv, UTIME, time(31 downto 0),
S -> menable(1),
U -> (if (prvSrv.hasSupervisor()) senable(1) else True),
U -> menable(1)
)
utimeAccess(csrSrv, prvSrv, UTIMEH, time(63 downto 32),
S -> menable(1),
U -> (if (prvSrv.hasSupervisor()) senable(1) else True),
U -> menable(1)
)
}

uinstretAccess(csrSrv, prvSrv, UINSTRET, instret(31 downto 0),
S -> menable(2),
U -> (if (prvSrv.hasSupervisor()) senable(2) else True),
U -> menable(2)
)
uinstretAccess(csrSrv, prvSrv, UINSTRETH, instret(63 downto 32),
S -> menable(2),
U -> (if (prvSrv.hasSupervisor()) senable(2) else True),
U -> menable(2)
)

minstretAccess(csrSrv, prvSrv, MINSTRET, instret(31 downto 0), S -> False, U -> False)
minstretAccess(csrSrv, prvSrv, MINSTRETH, instret(63 downto 32), S -> False, U -> False)
}
}
}
}