
spinal.lib.bus.tilelink.sim.MemoryAgent.scala Maven / Gradle / Ivy
package spinal.lib.bus.tilelink.sim
import spinal.core.{ClockDomain, log2Up}
import spinal.lib.bus.tilelink.{Bus, M2sAgent, Opcode, Param}
import spinal.lib.sim._
import spinal.core.sim._
import spinal.sim.SimThread
import scala.collection.mutable
class MemoryAgent(bus: Bus,
cd: ClockDomain,
seed : Long = simRandom.nextInt(),
var randomProberFactor : Float = 0.0f,
var randomProberDelayMax : Int = 1000,
memArg : Option[SparseMemory] = None
)(implicit idCallback : IdCallback) extends MonitorSubscriber{
implicit val _ = sm
val mem = memArg.getOrElse(SparseMemory(seed))
var blockSize = 64
try{
blockSize = bus.p.node.s.emits.probe.getSingleSize().get
}catch{
case e : Throwable =>
}
val monitor = new Monitor(bus, cd).add(this)
val driver = new SlaveDriver(bus, cd)
val sinkAllocator = new BlockingIdAllocator(bus.p.sinkWidth)
val locks = mutable.LinkedHashMap[Long, SimMutex]()
def reserve(address : Long) = {
locks.get(address) match {
case Some(x) => x.lock(); cd.waitSampling(simRandom.nextInt(10))
case None => locks(address) = SimMutex(randomized=true).lock
}
}
def release(address : Long) = {
val l = locks(address)
l.unlock()
if(!l.locked){
locks.remove(address)
}
}
val capMap = mutable.LinkedHashMap[M2sAgent, mutable.LinkedHashMap[Long, Int]]()
val sourceToM2s = new Array[M2sAgent](1 << bus.p.sourceWidth)
for(m <- bus.p.node.m.masters){
capMap(m) = mutable.LinkedHashMap[Long, Int]()
for(source <- m.mapping){
source.id.foreach{sourceId =>
sourceToM2s(sourceId.toInt) = m
}
}
}
def getCap(source: Int, address : Long) = capMap(sourceToM2s(source)).getOrElse(address, Param.Cap.toN)
def getCap(m2sAgent: M2sAgent, address : Long) = capMap(m2sAgent).getOrElse(address, Param.Cap.toN)
def changeCap(source : Int, address : Long, cap : Int) = {
val m2s = sourceToM2s(source)
if(cap == Param.Cap.toN)
capMap(m2s).remove(address)
else
capMap(m2s)(address) = cap
}
def delayOnA(a : TransactionA): Unit = {
val r = simRandom.nextFloat()
cd.waitSampling((r * r * 20).toInt) //Will enable out of order handeling
}
def checkAddress(address : Long) = true
override def onA(a: TransactionA) = {
if(bus.p.withBCE && simRandom.nextFloat() < randomProberFactor) fork {
cd.waitSampling(simRandom.nextInt(randomProberDelayMax))
val address = a.address.toLong
reserve(address)
handleCoherency(
address = address,
isAquire = false,
sourceAgent = null,
cap = List(Param.Cap.toN, Param.Cap.toB).randomPick(),
allowProbePerm = false
)
release(address)
}
fork{
delayOnA(a)
val blockAddress = a.address.toLong & ~(blockSize-1)
reserve(blockAddress)
val ok = checkAddress(a.address.toLong)
a.opcode match {
case Opcode.A.GET => {
handleCoherency(a, Param.Cap.toN)
if(idCallback != null) idCallback.call(a.debugId)(new OrderingArgs(0, a.bytes))
val d = TransactionD(a)
d.opcode = Opcode.D.ACCESS_ACK_DATA
d.denied = !ok
if(ok) d.data = mem.readBytes(a.address.toLong, a.bytes)
if(!ok) d.data = Array.fill(a.bytes)(simRandom.nextInt().toByte)
driver.scheduleD(d)
}
case Opcode.A.PUT_PARTIAL_DATA | Opcode.A.PUT_FULL_DATA => { //TODO probePerm not tested
handleCoherency(a, Param.Cap.toN, a.bytes == blockSize && a.opcode == Opcode.A.PUT_FULL_DATA)
if(idCallback != null) idCallback.call(a.debugId)(new OrderingArgs(0, a.bytes))
if(ok) mem.write(a.address.toLong, a.data, a.mask)
val d = TransactionD(a)
d.opcode = Opcode.D.ACCESS_ACK
d.denied = !ok
driver.scheduleD(d)
}
case Opcode.A.ACQUIRE_BLOCK => {
assert(ok)
val probe = handleCoherency(a, Param.Grow.getCap(a.param))
if(idCallback != null) idCallback.call(a.debugId)(new OrderingArgs(0, a.bytes))
val sink = sinkAllocator.allocate()
val d = TransactionD(a)
d.sink = sink
d.param = if(probe.unique) Param.Cap.toT else Param.Cap.toB
probe.needData match {
case false =>{
d.opcode = Opcode.D.GRANT
}
case true => {
d.opcode = Opcode.D.GRANT_DATA
d.data = mem.readBytes(a.address.toLong, a.bytes)
}
}
driver.scheduleD(d)
changeCap(a.source, a.address.toLong, d.param)
waitE(sink)
sinkAllocator.remove(sink)
}
case Opcode.A.ACQUIRE_PERM => {
assert(ok)
val probe = handleCoherency(a, Param.Grow.getCap(a.param))
if(idCallback != null) idCallback.call(a.debugId)(new OrderingArgs(0, a.bytes))
assert(probe.unique)
val sink = sinkAllocator.allocate()
val d = TransactionD(a)
d.sink = sink
d.param = Param.Cap.toT
d.opcode = Opcode.D.GRANT
driver.scheduleD(d)
changeCap(a.source, a.address.toLong, d.param)
waitE(sink)
sinkAllocator.remove(sink)
}
}
release(blockAddress)
}
}
case class CoherencyReport(val needData : Boolean,
val unique : Boolean)
def handleCoherency(a: TransactionA, cap : Int, allowProbePerm : Boolean = false): CoherencyReport = {
handleCoherency(
address = a.address.toLong,
isAquire = a.opcode == Opcode.A.ACQUIRE_BLOCK || a.opcode == Opcode.A.ACQUIRE_PERM,
sourceAgent = sourceToM2s(a.source),
cap = cap,
allowProbePerm = allowProbePerm
)
}
def handleCoherency(address : Long,
isAquire : Boolean,
sourceAgent : M2sAgent,
cap : Int,
allowProbePerm : Boolean): CoherencyReport ={
val blockAddress = address & ~(blockSize-1)
var needData = false
var unique = true
val inflights = mutable.ArrayBuffer[SimThread]()
for(m <- bus.p.node.m.masters if m.emits.withBCE) {
val isSelf = m == sourceAgent && isAquire
//We check if we realy need to probe the agent, based on its known cap
val needProbe = isSelf match {
case false => getCap(m, address) == Param.Cap.toT || cap != Param.Cap.toB
case true => false
}
val doProbe = needProbe || simRandom.nextBoolean() //Randomly generate unecessary probe, to simulate a lack of knowledge
if(!doProbe) {
if(isSelf && getCap(m, address) == Param.Cap.toN) needData = true
if(!isSelf && getCap(m, address) != Param.Cap.toN) unique = false
} else inflights += fork{
val mapping = m.mapping.randomPick()
val b = TransactionB()
b.opcode = Opcode.B.PROBE_BLOCK
if(allowProbePerm && simRandom.nextBoolean()) {
b.opcode = Opcode.B.PROBE_PERM
}
b.address = blockAddress
b.param = cap match {
case Param.Cap.toT => if(isSelf) Param.Cap.toT else Param.Cap.toN
case Param.Cap.toB => Param.Cap.toB
case Param.Cap.toN => Param.Cap.toN
}
b.source = mapping.id.randomPick().toInt
b.size = log2Up(blockSize)
driver.scheduleB(b)
val c = waitC(b.source, blockAddress)
c.opcode match{
case Opcode.C.PROBE_ACK =>
case Opcode.C.PROBE_ACK_DATA => {
mem.write(c.address.toLong, c.data)
}
}
if(isSelf && c.param == Param.Report.NtoN){
needData = true
}
if(!isSelf && Param.reportPruneToCap(c.param) != Param.Cap.toN) unique = false
}
}
inflights.foreach(_.join())
CoherencyReport(needData, unique)
}
override def onB(b: TransactionB) = {}
val callbackOnC = mutable.LinkedHashMap[(Int, Long), TransactionC => Unit]()
override def onC(c: TransactionC) = {
import Opcode.C._
changeCap(c.source, c.address.toLong, Param.reportPruneToCap(c.param) max getCap(c.source, c.address.toLong))
c.opcode match {
case PROBE_ACK | PROBE_ACK_DATA => callbackOnC(c.source -> c.address.toLong).apply(c)
case RELEASE | RELEASE_DATA => {
if(c.opcode == RELEASE_DATA){
mem.write(c.address.toLong, c.data)
}
val d = TransactionD(c)
d.opcode = Opcode.D.RELEASE_ACK
driver.scheduleD(d)
}
}
}
override def onD(d: TransactionD) = {}
val callbackOnE = mutable.LinkedHashMap[BigInt, TransactionE => Unit]()
override def onE(e: TransactionE) = {
callbackOnE(e.sink).apply(e)
}
def waitC(source : Int, address : Long) : TransactionC = {
var value : TransactionC = null
val lock = SimMutex().lock()
callbackOnC(source -> address) = { c =>
value = c
lock.unlock()
}
lock.await()
callbackOnC(source -> address) = null
value
}
def waitE(sink : BigInt) : TransactionE = {
var value : TransactionE = null
val lock = SimMutex().lock()
callbackOnE(sink) = { e =>
value = e
lock.unlock()
}
lock.await()
callbackOnE(sink) = null
value
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy