package vexriscv.demo.smp

import spinal.core
import spinal.core._
import spinal.core.sim.{onSimEnd, simSuccess}
import spinal.lib._
import spinal.lib.bus.bmb.sim.BmbMemoryAgent
import spinal.lib.bus.bmb._
import spinal.lib.bus.misc.{DefaultMapping, SizeMapping}
import spinal.lib.bus.wishbone.{Wishbone, WishboneConfig, WishboneToBmb, WishboneToBmbGenerator}
import spinal.lib.com.jtag.{Jtag, JtagInstructionDebuggerGenerator, JtagTapInstructionCtrl}
import spinal.lib.com.jtag.sim.JtagTcp
import spinal.lib.com.jtag.xilinx.Bscane2BmbMasterGenerator
import spinal.lib.generator._
import spinal.core.fiber._
import spinal.idslplugin.PostInitCallback
import spinal.lib.cpu.riscv.debug.{DebugModule, DebugModuleCpuConfig, DebugModuleParameter, DebugTransportModuleParameter, DebugTransportModuleTunneled}
import spinal.lib.misc.plic.PlicMapping
import spinal.lib.system.debugger.SystemDebuggerConfig
import vexriscv.ip.{DataCacheAck, DataCacheConfig, DataCacheMemBus, InstructionCache, InstructionCacheConfig}
import vexriscv.plugin._
import vexriscv.{Riscv, VexRiscv, VexRiscvBmbGenerator, VexRiscvConfig, plugin}

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import spinal.lib.generator._
import vexriscv.ip.fpu.FpuParameter

case class VexRiscvSmpClusterParameter(cpuConfigs : Seq[VexRiscvConfig],
                                       jtagHeaderIgnoreWidth : Int,
                                       withExclusiveAndInvalidation : Boolean,
                                       forcePeripheralWidth : Boolean = true,
                                       outOfOrderDecoder : Boolean = true,
                                       fpu : Boolean = false,
                                       privilegedDebug : Boolean = false,
                                       hardwareBreakpoints : Int = 0)

class VexRiscvSmpClusterBase(p : VexRiscvSmpClusterParameter) extends Area with PostInitCallback{
  val cpuCount = p.cpuConfigs.size

  val debugCd = ClockDomainResetGenerator()
  debugCd.holdDuration.load(4095)
  debugCd.makeExternal()

  val systemCd = ClockDomainResetGenerator()
  systemCd.holdDuration.load(63)
  systemCd.setInput(debugCd)


  val ctx = systemCd.outputClockDomain.push()
  override def postInitCallback(): VexRiscvSmpClusterBase.this.type = {
    ctx.restore()
    this
  }

  implicit val interconnect: BmbInterconnectGenerator = BmbInterconnectGenerator()

  val customDebug = !p.privilegedDebug generate new Area {
    val debugBridge = debugCd.outputClockDomain on JtagInstructionDebuggerGenerator(p.jtagHeaderIgnoreWidth)
    debugBridge.jtagClockDomain.load(ClockDomain.external("jtag", withReset = false))

    val debugPort = Handle(debugBridge.logic.jtagBridge.io.ctrl.toIo).setName("debugPort")
  }

  val dBusCoherent = BmbBridgeGenerator()
  val dBusNonCoherent = BmbBridgeGenerator()

  val smp = p.withExclusiveAndInvalidation generate new Area{
    val exclusiveMonitor = BmbExclusiveMonitorGenerator()
    interconnect.addConnection(dBusCoherent.bmb, exclusiveMonitor.input)

    val invalidationMonitor = BmbInvalidateMonitorGenerator()
    interconnect.addConnection(exclusiveMonitor.output, invalidationMonitor.input)
    interconnect.addConnection(invalidationMonitor.output, dBusNonCoherent.bmb)
    if(p.outOfOrderDecoder) interconnect.masters(invalidationMonitor.output).withOutOfOrderDecoder()
  }

  val noSmp = !p.withExclusiveAndInvalidation generate new Area{
    interconnect.addConnection(dBusCoherent.bmb, dBusNonCoherent.bmb)
  }

  val cores = for(cpuId <- 0 until cpuCount) yield new Area{
    val cpu = VexRiscvBmbGenerator()
    cpu.config.load(p.cpuConfigs(cpuId))
    interconnect.addConnection(
      cpu.dBus -> List(dBusCoherent.bmb)
    )

    cpu.hardwareBreakpointCount.load(p.hardwareBreakpoints)
    if(!p.privilegedDebug) {
      cpu.enableDebugBmb(
        debugCd = debugCd.outputClockDomain,
        resetCd = systemCd,
        mapping = SizeMapping(cpuId * 0x1000, 0x1000)
      )
      interconnect.addConnection(customDebug.debugBridge.bmb, cpu.debugBmb)
    } else {
      cpu.enableRiscvDebug(debugCd.outputClockDomain, systemCd)
    }
  }

  val privilegedDebug = p.privilegedDebug generate new Area{
    val jtagCd = ClockDomain.external("jtag", withReset = false)

    val systemReset = Handle(Bool())
    systemCd.relaxedReset(systemReset, ResetSensitivity.HIGH)

    val p = DebugTransportModuleParameter(
      addressWidth = 7,
      version = 1,
      idle = 7
    )

    val logic = hardFork(debugCd.outputClockDomain on new Area {
      val XLEN = 32

      val dm = DebugModule(
        DebugModuleParameter(
          version = p.version + 1,
          harts = cpuCount,
          progBufSize = 2,
          datacount = XLEN / 32 + cores.exists(_.cpu.config.get.FLEN == 64).toInt,
          hartsConfig = cores.map(c => DebugModuleCpuConfig(
            xlen = XLEN,
            flen = c.cpu.config.get.FLEN,
            withFpuRegAccess = c.cpu.config.get.FLEN == 64
          ))
        )
      )
      systemReset := dm.io.ndmreset
      for ((cpu, i) <- cores.zipWithIndex) {
        val privBus = cpu.cpu.debugRiscv
        privBus <> dm.io.harts(i)
        privBus.dmToHart.removeAssignments() <-< dm.io.harts(i).dmToHart
      }

      val clintStop =  (cores.map(e => e.cpu.logic.cpu.service(classOf[CsrPlugin]).stoptime).andR)

      val tunnel = DebugTransportModuleTunneled(
        p = p,
        jtagCd = jtagCd,
        debugCd = ClockDomain.current
      )
      dm.io.ctrl <> tunnel.io.bus

      val debugPort = Handle(tunnel.io.instruction.toIo).setName("debugPort")
    })
  }
}


class VexRiscvSmpClusterWithPeripherals(p : VexRiscvSmpClusterParameter) extends VexRiscvSmpClusterBase(p) {
  val peripheralBridge = BmbToWishboneGenerator(DefaultMapping)
  val peripheral = Handle(peripheralBridge.logic.io.output.toIo)
  if(p.forcePeripheralWidth) interconnect.slaves(peripheralBridge.bmb).forceAccessSourceDataWidth(32)

  val plic = BmbPlicGenerator()(interconnect = null)
  plic.priorityWidth.load(2)
  plic.mapping.load(PlicMapping.sifive)

  val plicWishboneBridge = new Generator{
    dependencies += plic.ctrl

    plic.accessRequirements.load(BmbAccessParameter(
      addressWidth = 22,
      dataWidth = 32
    ).addSources(1, BmbSourceParameter(
      contextWidth = 0,
      lengthWidth = 2,
      alignment =  BmbParameter.BurstAlignement.LENGTH
    )))

    val logic = add task new Area{
      val bridge = WishboneToBmb(WishboneConfig(20, 32))
      bridge.io.output >> plic.ctrl
    }
  }
  val plicWishbone = plicWishboneBridge.produceIo(plicWishboneBridge.logic.bridge.io.input)

  val clint = BmbClintGenerator(0)(interconnect = null)
  val clintWishboneBridge = new Generator{
    dependencies += clint.ctrl

    clint.accessRequirements.load(BmbAccessParameter(
      addressWidth = 16,
      dataWidth = 32
    ).addSources(1, BmbSourceParameter(
      contextWidth = 0,
      lengthWidth = 2,
      alignment =  BmbParameter.BurstAlignement.LENGTH
    )))

    val logic = add task new Area{
      val bridge = WishboneToBmb(WishboneConfig(14, 32))
      bridge.io.output >> clint.ctrl
    }
  }
  val clintWishbone = clintWishboneBridge.produceIo(clintWishboneBridge.logic.bridge.io.input)

  val interrupts = in Bits(32 bits)
  for(i <- 1 to 31) yield plic.addInterrupt(interrupts(i), i)

  for ((core, cpuId) <- cores.zipWithIndex) {
    core.cpu.setTimerInterrupt(clint.timerInterrupt(cpuId))
    core.cpu.setSoftwareInterrupt(clint.softwareInterrupt(cpuId))
    plic.priorityWidth.load(2)
    plic.mapping.load(PlicMapping.sifive)
    plic.addTarget(core.cpu.externalInterrupt)
    if(core.cpu.config.withSupervisor) {
      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
        case _ =>
      }
    }
  }

  clint.cpuCount.load(cpuCount)
  if(p.privilegedDebug) hardFork(clint.logic.io.stop := privilegedDebug.logic.clintStop)
}

//python3 -m litex_boards.targets.digilent_nexys_video --cpu-type=vexriscv_smp --with-privileged-debug --sys-clk-freq 50000000 --cpu-count 1  --build --load
object VexRiscvSmpClusterGen {
  def vexRiscvConfig(hartId : Int,
                     ioRange : UInt => Bool = (x => x(31 downto 28) === 0xF),
                     resetVector : Long = 0x80000000l,
                     iBusWidth : Int = 128,
                     dBusWidth : Int = 64,
                     loadStoreWidth : Int = 32,
                     coherency : Boolean = true,
                     atomic : Boolean = true,
                     iCacheSize : Int = 8192,
                     dCacheSize : Int = 8192,
                     iCacheWays : Int = 2,
                     dCacheWays : Int = 2,
                     iBusRelax : Boolean = false,
                     injectorStage : Boolean = false,
                     earlyBranch : Boolean = false,
                     earlyShifterInjection : Boolean = true,
                     dBusCmdMasterPipe : Boolean = false,
                     withMmu : Boolean = true,
                     withSupervisor : Boolean = true,
                     withFloat : Boolean = false,
                     withDouble : Boolean = false,
                     externalFpu : Boolean = true,
                     simHalt : Boolean = false,
                     decoderIsolationBench : Boolean = false,
                     decoderStupid : Boolean = false,
                     regfileRead : RegFileReadKind = plugin.ASYNC,
                     rvc : Boolean = false,
                     iTlbSize : Int = 4,
                     dTlbSize : Int = 4,
                     prediction : BranchPrediction = vexriscv.plugin.NONE,
                     withDataCache : Boolean = true,
                     withInstructionCache : Boolean = true,
                     forceMisa : Boolean = false,
                     forceMscratch : Boolean = false,
                     privilegedDebug : Boolean = false,
                     csrFull : Boolean = false
                    ) = {
    assert(iCacheSize/iCacheWays <= 4096, "Instruction cache ways can't be bigger than 4096 bytes")
    assert(dCacheSize/dCacheWays <= 4096, "Data cache ways can't be bigger than 4096 bytes")
    assert(!(withDouble && !withFloat))

    val misa = Riscv.misaToInt(s"ima${if(withFloat) "f" else ""}${if(withDouble) "d" else ""}${if(rvc) "c" else ""}${if(withSupervisor) "s" else ""}")
    val csrConfig = if(withSupervisor){
      var c = CsrPluginConfig.openSbi(mhartid = hartId, misa = misa).copy(utimeAccess = CsrAccess.READ_ONLY, withPrivilegedDebug = privilegedDebug)
      if(csrFull){
       c = c.copy(
         mcauseAccess   = CsrAccess.READ_WRITE,
         mbadaddrAccess = CsrAccess.READ_WRITE,
         ucycleAccess   = CsrAccess.READ_ONLY,
         uinstretAccess = CsrAccess.READ_ONLY,
         mcycleAccess   = CsrAccess.READ_WRITE,
         minstretAccess = CsrAccess.READ_WRITE
       )
      }
      c
    } else {
      assert(!csrFull)
      CsrPluginConfig(
        catchIllegalAccess = true,
        mvendorid      = 0,
        marchid        = 0,
        mimpid         = 0,
        mhartid        = hartId,
        misaExtensionsInit = misa,
        misaAccess     = if(forceMisa) CsrAccess.READ_ONLY else CsrAccess.NONE,
        mtvecAccess    = CsrAccess.READ_WRITE,
        mtvecInit      = null,
        mepcAccess     = CsrAccess.READ_WRITE,
        mscratchGen    = forceMscratch,
        mcauseAccess   = CsrAccess.READ_ONLY,
        mbadaddrAccess = CsrAccess.READ_ONLY,
        mcycleAccess   = CsrAccess.NONE,
        minstretAccess = CsrAccess.NONE,
        ecallGen       = true,
        ebreakGen      = true,
        wfiGenAsWait   = false,
        wfiGenAsNop    = true,
        ucycleAccess   = CsrAccess.NONE,
        withPrivilegedDebug = privilegedDebug
      )
    }
    val config = VexRiscvConfig(
      plugins = List(
        if(withMmu)new MmuPlugin(
          ioRange = ioRange
        )else new StaticMemoryTranslatorPlugin(
          ioRange = ioRange
        ),
        //Uncomment the whole IBusCachedPlugin and comment IBusSimplePlugin if you want cached iBus config
        if(withInstructionCache) new IBusCachedPlugin(
          resetVector = resetVector,
          compressedGen = rvc,
          prediction = prediction,
          historyRamSizeLog2 = 9,
          relaxPredictorAddress = true,
          injectorStage = injectorStage,
          relaxedPcCalculation = iBusRelax,
          config = InstructionCacheConfig(
            cacheSize = iCacheSize,
            bytePerLine = 64,
            wayCount = iCacheWays,
            addressWidth = 32,
            cpuDataWidth = 32,
            memDataWidth = iBusWidth,
            catchIllegalAccess = true,
            catchAccessFault = true,
            asyncTagMemory = false,
            twoCycleRam = false,
            twoCycleCache = true,
            reducedBankWidth = true
          ),
          memoryTranslatorPortConfig = MmuPortConfig(
            portTlbSize = iTlbSize,
            latency = 1,
            earlyRequireMmuLockup = true,
            earlyCacheHits = true
          )
        ) else new IBusSimplePlugin(
          resetVector = resetVector,
          cmdForkOnSecondStage = false,
          cmdForkPersistence = false,
          prediction = NONE,
          catchAccessFault = false,
          compressedGen = rvc,
          busLatencyMin = 2,
          vecRspBuffer = true
        ),
        if(withDataCache) new DBusCachedPlugin(
          dBusCmdMasterPipe = dBusCmdMasterPipe || dBusWidth == 32,
          dBusCmdSlavePipe = true,
          dBusRspSlavePipe = true,
          relaxedMemoryTranslationRegister = true,
          config = new DataCacheConfig(
            cacheSize         = dCacheSize,
            bytePerLine       = 64,
            wayCount          = dCacheWays,
            addressWidth      = 32,
            cpuDataWidth      = loadStoreWidth,
            memDataWidth      = dBusWidth,
            catchAccessError  = true,
            catchIllegal      = true,
            catchUnaligned    = true,
            withLrSc = atomic,
            withAmo = atomic,
            withExclusive = coherency,
            withInvalidate = coherency,
            withWriteAggregation = dBusWidth > 32
          ),
          memoryTranslatorPortConfig = MmuPortConfig(
            portTlbSize = dTlbSize,
            latency = 1,
            earlyRequireMmuLockup = true,
            earlyCacheHits = true
          )
        ) else new DBusSimplePlugin(
          catchAddressMisaligned = false,
          catchAccessFault = false,
          earlyInjection = false
        ),
        new DecoderSimplePlugin(
          catchIllegalInstruction = true,
          decoderIsolationBench = decoderIsolationBench,
          stupidDecoder = decoderStupid
        ),
        new RegFilePlugin(
          regFileReadyKind = regfileRead,
          zeroBoot = false,
          x0Init = true
        ),
        new IntAluPlugin,
        new SrcPlugin(
          separatedAddSub = false
        ),
        new FullBarrelShifterPlugin(earlyInjection = earlyShifterInjection),
        //        new LightShifterPlugin,
        new HazardSimplePlugin(
          bypassExecute           = true,
          bypassMemory            = true,
          bypassWriteBack         = true,
          bypassWriteBackBuffer   = true,
          pessimisticUseSrc       = false,
          pessimisticWriteRegFile = false,
          pessimisticAddressMatch = false
        ),
        new MulPlugin,
        new MulDivIterativePlugin(
          genMul = false,
          genDiv = true,
          mulUnrollFactor = 32,
          divUnrollFactor = 1
        ),
        new CsrPlugin(csrConfig),
        new BranchPlugin(
          earlyBranch = earlyBranch,
          catchAddressMisaligned = true,
          fenceiGenAsAJump = false
        ),
        new YamlPlugin(s"cpu$hartId.yaml")
      )
    )

    if(withFloat) config.plugins += new FpuPlugin(
      externalFpu = externalFpu,
      simHalt = simHalt,
      p = FpuParameter(withDouble = withDouble)
    )
    config
  }


//  def vexRiscvCluster(cpuCount : Int, resetVector : Long = 0x80000000l) = VexRiscvSmpCluster(
//    debugClockDomain = ClockDomain.current.copy(reset = Bool().setName("debugResetIn")),
//    p = VexRiscvSmpClusterParameter(
//      cpuConfigs = List.tabulate(cpuCount) {
//        vexRiscvConfig(_, resetVector = resetVector)
//      }
//    )
//  )
//  def main(args: Array[String]): Unit = {
//    SpinalVerilog {
//      vexRiscvCluster(4)
//    }
//  }
}
//
//
//
//object VexRiscvSmpClusterTestInfrastructure{
//  val REPORT_OFFSET = 0xF8000000
//  val REPORT_THREAD_ID = 0x00
//  val REPORT_THREAD_COUNT = 0x04
//  val REPORT_END = 0x08
//  val REPORT_BARRIER_START = 0x0C
//  val REPORT_BARRIER_END   = 0x10
//  val REPORT_CONSISTENCY_VALUES = 0x14
//
//  val PUTC = 0x00
//  val GETC = 0x04
//  val CLINT_ADDR = 0x10000
//  val CLINT_IPI_ADDR = CLINT_ADDR+0x0000
//  val CLINT_CMP_ADDR = CLINT_ADDR+0x4000
//  val CLINT_TIME_ADDR = CLINT_ADDR+0xBFF8
//
//  def ram(dut : VexRiscvSmpCluster, withStall : Boolean) = {
//    import spinal.core.sim._
//    val cpuCount = dut.cpus.size
//    val ram = new BmbMemoryAgent(0x100000000l){
//      case class Report(hart : Int, code : Int, data : Int){
//        override def toString: String = {
//          f"CPU:$hart%2d ${code}%3x -> $data%3d"
//        }
//      }
//      val reports = ArrayBuffer.fill(cpuCount)(ArrayBuffer[Report]())
//
//
//      val writeTable = mutable.HashMap[Int, Int => Unit]()
//      val readTable = mutable.HashMap[Int, () => Int]()
//      def onWrite(address : Int)(body : Int => Unit) = writeTable(address) = body
//      def onRead(address : Int)(body : => Int) = readTable(address) = () => body
//
//      var writeData = 0
//      var readData = 0
//      var reportWatchdog = 0
//      val cpuEnd = Array.fill(cpuCount)(false)
//      val barriers = mutable.HashMap[Int, Int]()
//      var consistancyCounter = 0
//      var consistancyLast = 0
//      var consistancyA = 0
//      var consistancyB = 0
//      var consistancyAB = 0
//      var consistancyNone = 0
//
//      onSimEnd{
//        for((list, hart) <- reports.zipWithIndex){
//          println(f"\n\n**** CPU $hart%2d ****")
//          for((report, reportId) <- list.zipWithIndex){
//            println(f"  $reportId%3d : ${report.code}%3x -> ${report.data}%3d")
//          }
//        }
//
//        println(s"consistancy NONE:$consistancyNone A:$consistancyA B:$consistancyB AB:$consistancyAB")
//      }
//
//      override def setByte(address: Long, value: Byte): Unit = {
//        if((address & 0xF0000000l) != 0xF0000000l) return  super.setByte(address, value)
//        val byteId = address & 3
//        val mask = 0xFF << (byteId*8)
//        writeData = (writeData & ~mask) | ((value.toInt << (byteId*8)) & mask)
//        if(byteId != 3) return
//        val offset = (address & ~0xF0000000l)-3
//        //        println(s"W[0x${offset.toHexString}] = $writeData @${simTime()}")
//        offset match {
//          case _ if offset >= 0x8000000 && offset < 0x9000000 => {
//            val report = Report(
//              hart = ((offset & 0xFF0000) >> 16).toInt,
//              code = (offset & 0x00FFFF).toInt,
//              data = writeData
//            )
////            println(report)
//            reports(report.hart) += report
//            reportWatchdog += 1
//            import report._
//            code match {
//              case REPORT_THREAD_ID => assert(data == hart)
//              case REPORT_THREAD_COUNT => assert(data == cpuCount)
//              case REPORT_END => assert(data == 0); assert(cpuEnd(hart) == false); cpuEnd(hart) = true; if(!cpuEnd.exists(_ == false)) simSuccess()
//              case REPORT_BARRIER_START => {
//                val counter = barriers.getOrElse(data, 0)
//                assert(counter < cpuCount)
//                barriers(data) = counter + 1
//              }
//              case REPORT_BARRIER_END => {
//                val counter = barriers.getOrElse(data, 0)
//                assert(counter == cpuCount)
//              }
//              case REPORT_CONSISTENCY_VALUES => consistancyCounter match {
//                case 0 => {
//                  consistancyCounter = 1
//                  consistancyLast = data
//                }
//                case 1 => {
//                  consistancyCounter =  0
//                  (data, consistancyLast) match {
//                    case (666, 0) => consistancyA += 1
//                    case (0, 666) => consistancyB += 1
//                    case (666, 666)  => consistancyAB += 1
//                    case (0,0) => consistancyNone += 1; simFailure("Consistancy issue :(")
//                  }
//                }
//              }
//            }
//          }
//          case _ => writeTable.get(offset.toInt) match {
//            case Some(x) => x(writeData)
//            case _ => simFailure(f"\n\nWrite at ${address-3}%8x with $writeData%8x")
//          }
//        }
//      }
//
//      override def getByte(address: Long): Byte = {
//        if((address & 0xF0000000l) != 0xF0000000l) return super.getByte(address)
//        val byteId = address & 3
//        val offset = (address & ~0xF0000000l)
//        if(byteId == 0) readData = readTable.get(offset.toInt) match {
//          case Some(x) => x()
//          case _ => simFailure(f"\n\nRead at $address%8x")
//        }
//        (readData >> (byteId*8)).toByte
//      }
//
//      val clint = new {
//        val cmp = Array.fill(cpuCount)(0l)
//        var time = 0l
//        periodicaly(100){
//          time += 10
//          var timerInterrupts = 0l
//          for(i <- 0 until cpuCount){
//            if(cmp(i) < time) timerInterrupts |= 1l << i
//          }
//          dut.io.timerInterrupts #= timerInterrupts
//        }
//
////        delayed(200*1000000){
////          dut.io.softwareInterrupts #= 0xE
////          enableSimWave()
////          println("force IPI")
////        }
//      }
//
//      onWrite(PUTC)(data => print(data.toChar))
//      onRead(GETC)( if(System.in.available() != 0) System.in.read() else -1)
//
//      dut.io.softwareInterrupts #= 0
//      dut.io.timerInterrupts #= 0
//      dut.io.externalInterrupts #= 0
//      dut.io.externalSupervisorInterrupts #= 0
//      onRead(CLINT_TIME_ADDR)(clint.time.toInt)
//      onRead(CLINT_TIME_ADDR+4)((clint.time >> 32).toInt)
//      for(hartId <- 0 until cpuCount){
//        onWrite(CLINT_IPI_ADDR + hartId*4) {data =>
//          val mask = 1l << hartId
//          val value = (dut.io.softwareInterrupts.toLong & ~mask) | (if(data == 1) mask else 0)
//          dut.io.softwareInterrupts #= value
//        }
////        onRead(CLINT_CMP_ADDR + hartId*8)(clint.cmp(hartId).toInt)
////        onRead(CLINT_CMP_ADDR + hartId*8+4)((clint.cmp(hartId) >> 32).toInt)
//        onWrite(CLINT_CMP_ADDR + hartId*8){data => clint.cmp(hartId) = (clint.cmp(hartId) & 0xFFFFFFFF00000000l) | data}
//        onWrite(CLINT_CMP_ADDR + hartId*8+4){data => clint.cmp(hartId) = (clint.cmp(hartId) & 0x00000000FFFFFFFFl) | (data.toLong << 32)}
//      }
//
//
//
//    }
//    dut.io.iMems.foreach(ram.addPort(_,0,dut.clockDomain,true, withStall))
//    ram.addPort(dut.io.dMem,0,dut.clockDomain,true, withStall)
//    ram
//  }
//  def init(dut : VexRiscvSmpCluster): Unit ={
//    import spinal.core.sim._
//    dut.clockDomain.forkStimulus(10)
//    dut.debugClockDomain.forkStimulus(10)
//    dut.io.debugBus.cmd.valid #= false
//  }
//}
//
//object VexRiscvSmpClusterTest extends App{
//  import spinal.core.sim._
//
//  val simConfig = SimConfig
//  simConfig.withWave
//  simConfig.allOptimisation
//  simConfig.addSimulatorFlag("--threads 1")
//
//  val cpuCount = 4
//  val withStall = true
//
//  simConfig.compile(VexRiscvSmpClusterGen.vexRiscvCluster(cpuCount)).doSimUntilVoid(seed = 42){dut =>
//    disableSimWave()
//    SimTimeout(100000000l*10*cpuCount)
//    dut.clockDomain.forkSimSpeedPrinter(1.0)
//    VexRiscvSmpClusterTestInfrastructure.init(dut)
//    val ram = VexRiscvSmpClusterTestInfrastructure.ram(dut, withStall)
//    ram.memory.loadBin(0x80000000l, "src/test/cpp/raw/smp/build/smp.bin")
//    periodicaly(20000*10){
//      assert(ram.reportWatchdog != 0)
//      ram.reportWatchdog = 0
//    }
//  }
//}
//
//// echo "echo 10000 | dhrystone >> log" > test
//// time sh test &
//// top -b -n 1
//
//// TODO
//// MultiChannelFifo.toStream arbitration
//// BmbDecoderOutOfOrder arbitration
//// DataCache to bmb invalidation that are more than single line
//object VexRiscvSmpClusterOpenSbi extends App{
//  import spinal.core.sim._
//
//  val simConfig = SimConfig
//  simConfig.withWave
//  simConfig.allOptimisation
//  simConfig.addSimulatorFlag("--threads 1")
//
//  val cpuCount = 2
//  val withStall = false
//
//  def gen = {
//    val dut = VexRiscvSmpClusterGen.vexRiscvCluster(cpuCount, resetVector = 0x80000000l)
//    dut.cpus.foreach{cpu =>
//      cpu.core.children.foreach{
//        case cache : InstructionCache => cache.io.cpu.decode.simPublic()
//        case _ =>
//      }
//    }
//    dut
//  }
//
//  simConfig.workspaceName("rawr_4c").compile(gen).doSimUntilVoid(seed = 42){dut =>
////    dut.clockDomain.forkSimSpeedPrinter(1.0)
//    VexRiscvSmpClusterTestInfrastructure.init(dut)
//    val ram = VexRiscvSmpClusterTestInfrastructure.ram(dut, withStall)
////    ram.memory.loadBin(0x80000000l, "../opensbi/build/platform/spinal/vexriscv/sim/smp/firmware/fw_payload.bin")
//
////    ram.memory.loadBin(0x40F00000l, "/media/data/open/litex_smp/litex_vexriscv_smp/images/fw_jump.bin")
////    ram.memory.loadBin(0x40000000l, "/media/data/open/litex_smp/litex_vexriscv_smp/images/Image")
////    ram.memory.loadBin(0x40EF0000l, "/media/data/open/litex_smp/litex_vexriscv_smp/images/dtb")
////    ram.memory.loadBin(0x41000000l, "/media/data/open/litex_smp/litex_vexriscv_smp/images/rootfs.cpio")
//
//    ram.memory.loadBin(0x80000000l, "../opensbi/build/platform/spinal/vexriscv/sim/smp/firmware/fw_jump.bin")
//    ram.memory.loadBin(0xC0000000l, "../buildroot/output/images/Image")
//    ram.memory.loadBin(0xC1000000l, "../buildroot/output/images/dtb")
//    ram.memory.loadBin(0xC2000000l, "../buildroot/output/images/rootfs.cpio")
//
//    import spinal.core.sim._
//    var iMemReadBytes, dMemReadBytes, dMemWriteBytes, iMemSequencial,iMemRequests, iMemPrefetchHit = 0l
//    var reportTimer = 0
//    var reportCycle = 0
//    val iMemFetchDelta = mutable.HashMap[Long, Long]()
//    var iMemFetchDeltaSorted : Seq[(Long, Long)] = null
//    var dMemWrites, dMemWritesCached = 0l
//    val dMemWriteCacheCtx = List(4,8,16,32,64).map(bytes => new {
//      var counter = 0l
//      var address = 0l
//      val mask = ~((1 << log2Up(bytes))-1)
//    })
//
//    import java.io._
//    val csv = new PrintWriter(new File("bench.csv" ))
//    val iMemCtx = Array.tabulate(cpuCount)(i => new {
//      var sequencialPrediction = 0l
//      val cache = dut.cpus(i).core.children.find(_.isInstanceOf[InstructionCache]).head.asInstanceOf[InstructionCache].io.cpu.decode
//      var lastAddress = 0l
//    })
//    dut.clockDomain.onSamplings{
//      dut.io.time #= simTime()/10
//
//
//      for(i <- 0 until cpuCount; iMem = dut.io.iMems(i); ctx = iMemCtx(i)){
////        if(iMem.cmd.valid.toBoolean && iMem.cmd.ready.toBoolean){
////          val length = iMem.cmd.length.toInt + 1
////          val address = iMem.cmd.address.toLong
////          iMemReadBytes += length
////          iMemRequests += 1
////        }
//        if(ctx.cache.isValid.toBoolean && !ctx.cache.mmuRefilling.toBoolean && !ctx.cache.mmuException.toBoolean){
//          val address = ctx.cache.physicalAddress.toLong
//          val length = ctx.cache.p.bytePerLine.toLong
//          val mask = ~(length-1)
//          if(ctx.cache.cacheMiss.toBoolean) {
//            iMemReadBytes += length
//            if ((address & mask) == (ctx.sequencialPrediction & mask)) {
//              iMemSequencial += 1
//            }
//          }
//          if(!ctx.cache.isStuck.toBoolean) {
//            ctx.sequencialPrediction = address + length
//          }
//        }
//
//        if(iMem.cmd.valid.toBoolean && iMem.cmd.ready.toBoolean){
//          val address = iMem.cmd.address.toLong
//          iMemRequests += 1
//          if(iMemCtx(i).lastAddress + ctx.cache.p.bytePerLine == address){
//            iMemPrefetchHit += 1
//          }
//          val delta = address-iMemCtx(i).lastAddress
//          iMemFetchDelta(delta) = iMemFetchDelta.getOrElse(delta, 0l) + 1l
//          if(iMemRequests % 1000 == 999) iMemFetchDeltaSorted = iMemFetchDelta.toSeq.sortBy(_._1)
//          iMemCtx(i).lastAddress = address
//        }
//      }
//      if(dut.io.dMem.cmd.valid.toBoolean && dut.io.dMem.cmd.ready.toBoolean){
//        if(dut.io.dMem.cmd.opcode.toInt == Bmb.Cmd.Opcode.WRITE){
//          dMemWriteBytes += dut.io.dMem.cmd.length.toInt+1
//          val address = dut.io.dMem.cmd.address.toLong
//          dMemWrites += 1
//          for(ctx <- dMemWriteCacheCtx){
//            if((address & ctx.mask) == (ctx.address & ctx.mask)){
//              ctx.counter += 1
//            } else {
//              ctx.address = address
//            }
//          }
//        }else {
//          dMemReadBytes += dut.io.dMem.cmd.length.toInt+1
//          for(ctx <- dMemWriteCacheCtx) ctx.address = -1
//        }
//      }
//      reportTimer = reportTimer + 1
//      reportCycle = reportCycle + 1
//      if(reportTimer == 400000){
//        reportTimer = 0
////        println(f"\n** c=${reportCycle} ir=${iMemReadBytes*1e-6}%5.2f dr=${dMemReadBytes*1e-6}%5.2f dw=${dMemWriteBytes*1e-6}%5.2f **\n")
//
//
//        csv.write(s"$reportCycle,$iMemReadBytes,$dMemReadBytes,$dMemWriteBytes,$iMemRequests,$iMemSequencial,$dMemWrites,${dMemWriteCacheCtx.map(_.counter).mkString(",")},$iMemPrefetchHit\n")
//        csv.flush()
//        reportCycle = 0
//        iMemReadBytes = 0
//        dMemReadBytes = 0
//        dMemWriteBytes = 0
//        iMemRequests = 0
//        iMemSequencial = 0
//        dMemWrites = 0
//        iMemPrefetchHit = 0
//        for(ctx <- dMemWriteCacheCtx) ctx.counter = 0
//      }
//    }
//
//
////    fork{
////      disableSimWave()
////      val atMs = 3790
////      val durationMs = 5
////      sleep(atMs*1000000)
////      enableSimWave()
////      println("** enableSimWave **")
////      sleep(durationMs*1000000)
////      println("** disableSimWave **")
////      while(true) {
////        disableSimWave()
////        sleep(100000 * 10)
////        enableSimWave()
////        sleep(  100 * 10)
////      }
//////      simSuccess()
////    }
//
//    fork{
//      while(true) {
//        disableSimWave()
//        sleep(100000 * 10)
//        enableSimWave()
//        sleep(  100 * 10)
//      }
//    }
//  }
//}
