package quantumrisc

import java.io.{File, FileInputStream, FileOutputStream, IOException, OutputStream}

import scopt.OptionParser

import spinal.sim._
import spinal.core._
import spinal.lib._
import spinal.core.sim._

import spinal.lib.bus.simple._
import spinal.lib.bus.misc.SizeMapping
import spinal.lib.io.{TriStateArray, InOutWrapper}
import spinal.lib.com.jtag.Jtag
import spinal.lib.com.uart.Uart
import spinal.lib.com.jtag.sim.JtagTcp

import vexriscv.VexRiscv
import vexriscv.plugin.Plugin

case class PipelinedMemoryBusRamUlx3s(size : BigInt, initialContent : File = null) extends Component{
  require(size % 4 == 0, "Size must be multiple of 4 bytes")
  require(size > 0, "Size must be greater than zero")
  val busConfig = PipelinedMemoryBusConfig(log2Up(size), 32)
  val io = new Bundle{
    val bus = slave(PipelinedMemoryBus(busConfig))
  }

  val ram = Mem(Bits(32 bits), size / 4)
  io.bus.rsp.valid := RegNext(io.bus.cmd.fire && !io.bus.cmd.write) init(False)
  io.bus.rsp.data := ram.readWriteSync(
    address = io.bus.cmd.address >> 2,
    data  = io.bus.cmd.data,
    enable  = io.bus.cmd.valid,
    write  = io.bus.cmd.write,
    mask  = io.bus.cmd.mask
  )
  io.bus.cmd.ready := True

  if (initialContent != null) {
    val input = new FileInputStream(initialContent)
    val initContent = Array.fill[BigInt](ram.wordCount)(0)
    val fileContent = Array.ofDim[Byte](Seq(input.available, initContent.length * 4).min)
    input.read(fileContent)
    for ((byte, addr) <- fileContent.zipWithIndex) {
      val l = java.lang.Byte.toUnsignedLong(byte) << ((addr & 3) * 8)
      initContent(addr >> 2) |= BigInt(l)
    }
    ram.initBigInt(initContent)
  }
}




class PQVexRiscvUlx3s(
  val ramBlockSizes : Seq[BigInt] = Seq[BigInt](256 KiB, 128 KiB),
  val initialContent : File = null,
  val coreFrequency : HertzNumber = 25 MHz,
  cpuPlugins : () => Seq[Plugin[VexRiscv]] = PQVexRiscv.withDSPMultiplier()
) extends PQVexRiscv(
  cpuPlugins = cpuPlugins,
  ibusRange = SizeMapping(0x80000000l, ramBlockSizes.reduce(_ + _))
) {
  val io = new Bundle {
    val asyncReset = in Bool
    val mainClock = in Bool
    val uart = master(Uart())
    val jtag = slave(Jtag())
  }

  asyncReset := io.asyncReset
  mainClock := io.mainClock

  uart <> io.uart
  jtag <> io.jtag

  val memory = new ClockingArea(systemClockDomain) {
    val ramBlocks = ramBlockSizes.zipWithIndex.map(t => PipelinedMemoryBusRamUlx3s(t._1, if (t._2 == 0) initialContent else null))
    var curAddr : BigInt = 0x80000000l
    for (block <- ramBlocks) {
      busSlaves += block.io.bus -> SizeMapping(curAddr, block.size)
      curAddr += block.size
    }
  }
}


object PQVexRiscvUlx3s {
  def main(args: Array[String]) : Unit = {
      case class PQVexRiscvUlx3sConfig(
        initFile: File = null
      )
      val optParser = new OptionParser[PQVexRiscvUlx3sConfig]("PQVexRiscvUlx3s") {
        head("PQVexRiscvUlx3s")
        help("help") text("print usage text")
        opt[File]("init") action((f, c) => c.copy(initFile = f)) text("Initialization file for first RAM block") valueName("<bin>")
      }

      val config = optParser.parse(args, PQVexRiscvUlx3sConfig()) match {
        case Some(config) => config
        case None => ???
      }

    SpinalConfig(
      mode = Verilog
      // targetDirectory = "rtl"
  ).generate(new PQVexRiscvUlx3s(initialContent = config.initFile)).printPruned()
  }
}
