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

need support for using Bundle in simulation #392

Open
ArcheyChen opened this issue Mar 24, 2021 · 12 comments · May be fixed by #879
Open

need support for using Bundle in simulation #392

ArcheyChen opened this issue Mar 24, 2021 · 12 comments · May be fixed by #879
Labels
question ❔ Question about SpinalHDL environment

Comments

@ArcheyChen
Copy link

@Dolu1990
Hello,I'm using spinal to write a multi-core cache.
But when I try to simulate my code,I found that I can't use things like Bundle in my testbench,and that is really a trouble for me.

like I'm trying to test a out-of-order queue, the payload is a Bundle with dozens of wires.
I have to create a pure scala class ---- same as the Bundle,but use int instead of UInt(), to maintain the data I push to the queue. And it's not a good solution,since I also have to compare every element with [ x.toInt ]

Is there any good plan to deal with this kind of problem? If not,I think using verilog to write a testbench is a better idea

@Dolu1990
Copy link
Member

Hi,

Right, you can't use the bundle stuff as a whole. but you can access its elements.

To workaround that issue, i have some "dirty" SimData classe which use Bundle introspection to copy all its values :

def copy(hard : Data) = new SimData().load(hard)

It isn't perfect, but can be usefull.

So, i would say, either use that SimData class to capture things, else you may have usage into iterate on the elements of your bundle via the Bundle.elements function.

Can you show me the Bundle in question ?

@ArcheyChen
Copy link
Author

Let's start with a simple case

class Rgb extends Bundle{
  val r,g,b = UInt(8 bits)
}
class MyTopLevel extends Component {
  val io = new Bundle {
    val rgbin = in (new Rgb)
    val rgbout = out(new Rgb)
  }
  io.rgbout.setAsReg()
  io.rgbout := io.rgbin
}

If I want to test this unit, what should I do?
I think if I solve this question, I can handle my origin problem by myself

The origin code I'm testing will be post below

@ArcheyChen
Copy link
Author

…………………………

case class Request(configs : LLCConfigs) extends Bundle{
  val reqType = RequestType()//请求的类型Acquire或者是Release
  val param = UInt(configs.paramBits bits)//Acquire:请求的权限转换,Release:是否带数据
  /* Acquire:{……,toExc,toShare}
    Release:{……,hasData,toShare}
   */
//  val prio = Bits(2 bits)//{Release,Acquire},Release的请求权限更高
  val clientID = UInt(configs.clientIDBits bits)//这个请求来自哪个CPU
  val tag = UInt(configs.tagBits bits)//请求的地址
  val set = UInt(configs.setBits bits)
  val dataIndex = UInt(configs.dataQueIndexBits bits)//如果是Release且带了Data,那么需要知道这个Data被放哪了

  def isRelease():Bool = {
    reqType === RequestType.Release
  }
  def isAccquire() = {
    reqType === RequestType.Acquire
  }
  def isToShare():Bool = {
    param(0)
  }
  def isToExc():Bool = {
    param(1)
  }
  def hasData():Bool = {
    param(1)
  }
  def accquireParamShare={
    U"b01".resize(configs.paramBits bits)
  }

  def accquireParamExc={
    U"b10".resize(configs.paramBits bits)
  }
  def releaseParamHasDataToExc={
    U"b10".resize(configs.paramBits bits)
  }
  def releaseParamHasDataToShare={
    U"b11".resize(configs.paramBits bits)
  }
  def releaseParamNoData={
    U"b00".resize(configs.paramBits bits)
  }
}

class IssueUpdateData(configs : LLCConfigs) extends Bundle{
  val issueSucess = Bool()
  val MSHRID = UInt(configs.mshrIDBits bits)
  val MSHRHit = Bool()//成功分配到一个MSHR
}
class RequestQue(configs : LLCConfigs) extends Component {
  val io = new Bundle{
    val newRequest = slave Stream(new Request(configs))//因为只能同时发射一个新请求,所以公用一个入口就够了,不够再说

    val mshrReady = in Bits(configs.mshrNumber bits)//哪些MSHR处于Busy状态

    val issueRequest = master Flow(new IssueRequest(configs))
    val issueUpdate = in (new IssueUpdateData(configs))
  }

  val queueSize = configs.reqQueSize

  val reqMem = Mem(new Request(configs),queueSize)//指令存储

  val reqValids = Reg(UInt(queueSize bits)) init 0//指令有效性存储
  val idMem = Vec(Reg(UInt(configs.mshrIDBits bits)),queueSize)
  val idValids = Reg(UInt(queueSize bits)) init 0//MSHRID是否已分配

  val reqTypes = Vec(Reg(RequestType()),queueSize)//单独拿一堆Reg来存储请求类型(因为需要同时读取)

  val hasFreeSpace = (~reqValids).orR
  io.newRequest.ready := hasFreeSpace
  
  val readyIssues = Cat((0 until queueSize).map(i =>
    reqValids(i) && (

      //已分配了MSHR ID的情况
      (idValids(i) && (UIntToOh(idMem(i),configs.mshrNumber) & io.mshrReady).orR)||
        //没有分配MSHR ID的情况
        (~idValids(i))//永远进行尝试为未分配MSHR的请求进行Issue
    )
  ))

  val frees = ~reqValids
  val freeOH = OHMasking.last(frees)
  val freeIndex = OHToUInt(freeOH)

  //用位向量来新增、清除valid位
  val reqSetOH,reqClearOH,idSetOH,idClearOH = U(0,queueSize  bits)
  reqValids := (reqValids | reqSetOH) & (~reqClearOH)
  idValids := (idValids | idSetOH) & (~idClearOH)

  when(io.newRequest.fire){//新的请求进行分配,需要把请求有效位拉高,MSHR ID已分配位拉低
    reqMem(freeIndex) := io.newRequest
    reqSetOH := freeOH
    idClearOH := freeOH
    reqTypes(freeIndex) := io.newRequest.payload.reqType
  }

  val issueOH = if(configs.ReleaseHasHigherPriority){
    val accquireReqs = Cat(reqTypes.map{ rt => rt === RequestType.Acquire}) & readyIssues
    val releaseReqs = Cat(reqTypes.map{ rt => rt === RequestType.Release}) & readyIssues
    val priOH = OHMasking.last(accquireReqs ## releaseReqs)//优先取Release的请求
    priOH(0,queueSize bits) | priOH(queueSize,queueSize bits)
  } else{
    OHMasking.last(readyIssues)
  }

  val issueIndex = OHToUInt(issueOH)
  val issueRequest = new IssueRequest(configs)
  issueRequest.assignSomeByName(reqMem(issueIndex))
  issueRequest.mshrID := idMem(issueIndex)
  issueRequest.hasMSHR := idValids(issueIndex)

  when(io.issueUpdate.MSHRHit){
    idSetOH := issueOH.asUInt
    idMem(issueIndex) := io.issueUpdate.MSHRID
  }
  when(io.issueUpdate.issueSucess){//当成功Issue的时候,清空当前项
    reqClearOH := issueOH.asUInt
  }
  io.issueRequest.valid := readyIssues.orR//当存在可发射请求的时候,那么尝试发射
  io.issueRequest.payload := issueRequest
}

@ArcheyChen
Copy link
Author

@Dolu1990
And how should I change the values in SimData,if I the code to be tested is like this:

class MyTopLevel extends Component {
  val io = new Bundle {
    val rgbin = in (new Rgb)
    val rgbout = out(new Rgb)
  }
  io.rgbout.setAsReg()
  io.rgbout.r := io.rgbin.r
  io.rgbout.g := io.rgbin.g
  io.rgbout.b := io.rgbin.b + 1//Add one!
}

I tried to use the code below,but fail to compile

val testVal = SimData.copy(dut.io.rgbin)
        testVal.updateDynamic("b")(testVal.selectDynamic("b") + 1)
        assert(testVal.check(dut.io.rgbout))

@Dolu1990
Copy link
Member

object Play1 {
  class Rgb extends Bundle{
    val r,g,b = UInt(8 bits)
  }
  class MyTopLevel extends Component {
    val io = new Bundle {
      val rgbin = in (new Rgb)
      val rgbout = out(new Rgb)
    }
    io.rgbout.setAsReg()
    io.rgbout := io.rgbin
  }

  def main(args: Array[String]): Unit = {
    import spinal.core.sim._

    SimConfig.compile(new MyTopLevel).doSim{dut =>
      dut.clockDomain.forkStimulus(10)

      for(i <- 0 until 100){
        val ref = SimData.copy(dut.io.rgbin)
        dut.io.rgbin.randomize()
        dut.clockDomain.waitSampling()
        if(i != 0) assert(ref.check(dut.io.rgbout))
      }
    }
  }
}
object Play2 {
  class Rgb extends Bundle{
    val r,g,b = UInt(8 bits)
  }
  class MyTopLevel extends Component {
    val io = new Bundle {
      val rgbin = in (new Rgb)
      val rgbout = out(new Rgb)
    }
    io.rgbout.setAsReg()
    io.rgbout.r := io.rgbin.r
    io.rgbout.g := io.rgbin.g
    io.rgbout.b := io.rgbin.b + 1//Add one!
  }

  def main(args: Array[String]): Unit = {
    import spinal.core.sim._

    SimConfig.compile(new MyTopLevel).doSim{dut =>
      dut.clockDomain.forkStimulus(10)

      for(i <- 0 until 100){
        val ref = SimData()
        ref.r = dut.io.rgbin.r.toBigInt
        ref.g = dut.io.rgbin.g.toBigInt
        ref.b = dut.io.rgbin.b.toBigInt + 1

        dut.io.rgbin.randomize()
        dut.clockDomain.waitSampling()
        if(i != 0) assert(ref.check(dut.io.rgbout))
      }
    }
  }
}

@Dolu1990
Copy link
Member

Basicaly, the Dynamic trait of the SimData class allow to use it a bit like a dynamicaly typed class. (bit like in python)

@Dolu1990
Copy link
Member

        val ref = SimData.copy(dut.io.rgbin)
        ref.b = ref.b.asInstanceOf[BigInt] + 1     //Not the greated syntax in the world, that's not realy the intend of SimData

I don't know, in practice with the testbench i had to do, i didn't falled in this kind of usages. Basicaly, what is done is that i model in scala my stimulus, that then i apply to both hardware and reference model. Then i check the dut against the reference model. And basicaly, the nature of the stimulus generation and the reference model being in a fondamental nature different from the hardware one, there wasn't realy a gain trying to reuse Bundle structure in my testbenches.

@ArcheyChen
Copy link
Author

@Dolu1990
Thanks a lot. My project is a L2-Cache. So I'm dealing with a lot of requires, both internal and external.
and to gain performance, in most cases, these requires are processed out-of-orderd.
That means I can't build a reference model ---- or I have to basicly copy my whole code

I can only check that my model run funtionaly as my expect, rather than cycle by cycle
for example,there are 3 reqs:A,B,C. Both A->B->C, and A -> C -> B is OK.
In order to check if my model is funtioning, I have to maintain the status of each request ---- which need to store each requests,and change is status bits from now and then.

(or is there a better way to do this?

SimData is really helpful now , but I think native support for Bundle using in Simulation will be better.like when we using verilog,we use the same data type in sim as we use in design, so we won't need to deal with a lot of type conversion

@jijingg
Copy link
Collaborator

jijingg commented Mar 24, 2021

@ArcheyChen native supoort for Bundle Depending on how you define the drive method, spinal can't help you decide how to drive the bundle.

object palyRGB extends App{
  case class RGB() extends Bundle{
    val r = UInt( 8 bit)
    val g = UInt( 8 bit)
    val b = UInt( 8 bit)

    def #=(rgb: (Int, Int, Int)): Unit ={
      import spinal.core.sim._
      r  #= rgb._1
      g #= rgb._2
      b #= rgb._3
    }
    def monitor() = (r.toLong, g.toLong, b.toLong)
  }

  class Top extends Component {
    val io = new Bundle{
      val din = slave Flow(RGB())
      val dout = master Flow(RGB())
    }
    io.dout :=  RegNext(io.din)

    private val outcollect = new ListBuffer[(Long, Long, Long)]()
    def drive(rgbs: List[(Int, Int, Int)]): Unit = {
      outcollect.clear()
      fork{//drive
        rgbs.foreach{rgb =>
          io.din.valid #= true
          io.din #= rgb
          clockDomain.waitSampling()
        }
        io.din.valid #= false
        clockDomain.waitSampling(10)
        println(outcollect)
        simSuccess()
      }
    }

    def moniter = {
      fork{
        sleep(0)
        while(true){
          val res = io.dout.monitor()
          if(io.dout.valid.toBoolean){
            outcollect.append(res)
          }
          clockDomain.waitSampling()
        }
      }
    }
  }

  val rand = new  Random(0)
  val data = List.fill(100)(rand.nextInt(128), rand.nextInt(128), rand.nextInt(128))

  SpinalSimConfig().withFstWave.compile(new Top).doSimUntilVoid("test"){dut =>
    dut.clockDomain.forkStimulus(2)
    dut.drive(data)
    dut.moniter
  }
}

another Apb3 Bundle write and read example

  implicit class APB3Extends(apb3: Apb3){
    import spinal.core._
    import spinal.core.sim._

    def simWrite(addr: Long, data: BigInt)(cd: ClockDomain) = {
      cd.waitSampling()
      apb3.PSEL     #= 0
      apb3.PENABLE  #= true
      apb3.PADDR    #= addr
      apb3.PWRITE   #= true
      cd.waitSampling()
      apb3.PWDATA #= data
      while(!apb3.PREADY.toBoolean){cd.waitSampling()}
    }

    def simRead(addr: Long)(cd: ClockDomain): BigInt = {
      cd.waitSampling()
      apb3.PSEL     #= 0
      apb3.PENABLE  #= true
      apb3.PADDR    #= addr
      apb3.PWRITE   #= false
      cd.waitSampling()
      while(!apb3.PREADY.toBoolean){cd.waitSampling()}
      sleep(0)
      apb3.PRDATA.toLong
    }

@numero-744
Copy link
Collaborator

This should be addressed in the long term by #879 (like the last example above)

@numero-744 numero-744 added the question ❔ Question about SpinalHDL environment label Nov 14, 2022
@numero-744 numero-744 linked a pull request Nov 20, 2022 that will close this issue
@developfpga
Copy link

https://github.com/SpinalHDL/SpinalHDL/issues/392#issuecomment-805624235
Is it possible to support Bundle in sim natively?
For example, there is a packet generate by spinalHDL, and the first 4 bytes of the packet is assigned asBits of a Bundle object. When I get the packet at the sim, how can I decode the first 4 bytes of the packet by the Bundle definition?

@Dolu1990
Copy link
Member

I think things could be emulated in a soft way, like having a hashmap[Data, BigInt] and having it populated automaticaly by providing a reference bundle.
Currently there is nothing for it, but it isn't something which need to modify code, only adding layers on the top.
There is something kinda in the same range of things, named SimData, but doesn't cover your specific case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question ❔ Question about SpinalHDL environment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants