// See LICENSE for license details.

package rocketchip

import Chisel._
import cde.{Parameters, Field}
import rocket.Util._
import junctions._

class TestHarness(implicit p: Parameters) extends Module {
  val io = new Bundle {
    val success = Bool(OUTPUT)
  }
  val dut = Module(new Top(p))

  // This test harness isn't especially flexible yet
  require(dut.io.mem_clk.isEmpty)
  require(dut.io.mem_rst.isEmpty)
  require(dut.io.mem_ahb.isEmpty)
  require(dut.io.mem_tl.isEmpty)
  require(dut.io.bus_clk.isEmpty)
  require(dut.io.bus_rst.isEmpty)
  require(dut.io.mmio_clk.isEmpty)
  require(dut.io.mmio_rst.isEmpty)
  require(dut.io.mmio_ahb.isEmpty)
  require(dut.io.mmio_tl.isEmpty)
  require(dut.io.extra.elements.isEmpty)

  for (int <- dut.io.interrupts)
    int := false

  if (dut.io.mem_axi.nonEmpty) {
    val memSize = p(GlobalAddrMap)("mem").size
    require(memSize % dut.io.mem_axi.size == 0)
    for (axi <- dut.io.mem_axi)
      Module(new SimAXIMem(memSize / dut.io.mem_axi.size)).io.axi <> axi
  }

  if (p(IncludeJtagDTM)) {
    val jtag_vpi = Module (new JtagVpi)
    dut.io.jtag.get <> jtag_vpi.io.jtag

    // To be proper,
    // TRST should really be synchronized
    // with TCK. But this is a fairly
    // accurate representation of how
    // HW may drive this signal.
    // Neither OpenOCD nor JtagVPI drive TRST.

    dut.io.jtag.get.TRST := reset 
    jtag_vpi.io.enable := ~reset
    jtag_vpi.io.init_done := ~reset

    // Success is determined by the gdbserver
    // which is controlling this simulation.
    io.success := Bool(false)
  }
  else {
    val dtm = Module(new SimDTM)
    dut.io.debug.get <> dtm.io.debug

    // Todo: enable the usage of different clocks
    // to test the synchronizer more aggressively.
    val dtm_clock = clock
    val dtm_reset = reset

    dtm.io.clk := dtm_clock
    dtm.io.reset := dtm_reset
    if (dut.io.debug_clk.isDefined)
      dut.io.debug_clk.get := dtm_clock
    if (dut.io.debug_rst.isDefined)
      dut.io.debug_rst.get := dtm_reset

    io.success := dut.io.success.getOrElse(dtm.io.exit === 1)
    when (dtm.io.exit >= 2) {
      printf("*** FAILED *** (exit code = %d)\n", dtm.io.exit >> 1)
      stop(1)
    }
  }

  for (bus_axi <- dut.io.bus_axi) {
    bus_axi.ar.valid := Bool(false)
    bus_axi.aw.valid := Bool(false)
    bus_axi.w.valid  := Bool(false)
    bus_axi.r.ready  := Bool(false)
    bus_axi.b.ready  := Bool(false)
  }

  for (mmio_axi <- dut.io.mmio_axi) {
    val slave = Module(new NastiErrorSlave)
    slave.io <> mmio_axi
  }

}

class SimAXIMem(size: BigInt)(implicit p: Parameters) extends Module {
  val io = new Bundle {
    val axi = new NastiIO().flip
  }

  val rValid = Reg(init = Bool(false))
  val ar = RegEnable(io.axi.ar.bits, io.axi.ar.fire())
  io.axi.ar.ready := !rValid
  when (io.axi.ar.fire()) { rValid := true }
  when (io.axi.r.fire()) {
    assert(ar.burst === NastiConstants.BURST_INCR)
    ar.addr := ar.addr + (UInt(1) << ar.size)
    ar.len := ar.len - 1
    when (ar.len === UInt(0)) { rValid := false }
  }

  val w = io.axi.w.bits
  require((size * 8) % w.data.getWidth == 0)
  val depth = (size * 8) / w.data.getWidth
  val mem = Mem(depth.toInt, w.data)

  val wValid = Reg(init = Bool(false))
  val bValid = Reg(init = Bool(false))
  val aw = RegEnable(io.axi.aw.bits, io.axi.aw.fire())
  io.axi.aw.ready := !wValid && !bValid
  io.axi.w.ready := wValid
  when (io.axi.b.fire()) { bValid := false }
  when (io.axi.aw.fire()) { wValid := true }
  when (io.axi.w.fire()) {
    assert(aw.burst === NastiConstants.BURST_INCR)
    aw.addr := aw.addr + (UInt(1) << aw.size)
    aw.len := aw.len - 1
    when (aw.len === UInt(0)) {
      wValid := false
      bValid := true
    }

    def row = mem((aw.addr >> log2Ceil(w.data.getWidth/8))(log2Ceil(depth)-1, 0))
    val mask = FillInterleaved(8, w.strb)
    val newData = mask & w.data | ~mask & row
    row := newData
  }

  io.axi.b.valid := bValid
  io.axi.b.bits.id := aw.id
  io.axi.b.bits.resp := UInt(0)

  io.axi.r.valid := rValid
  io.axi.r.bits.id := ar.id
  io.axi.r.bits.data := mem((ar.addr >> log2Ceil(w.data.getWidth/8))(log2Ceil(depth)-1, 0))
  io.axi.r.bits.resp := UInt(0)
  io.axi.r.bits.last := ar.len === UInt(0)
}

class SimDTM(implicit p: Parameters) extends BlackBox {
  val io = new Bundle {
    val clk = Clock(INPUT)
    val reset = Bool(INPUT)
    val debug = new uncore.devices.DebugBusIO
    val exit = UInt(OUTPUT, 32)
  }
}


class JtagVpi(implicit val p: Parameters) extends BlackBox {

  val io = new Bundle {
    val jtag = new JtagIO(false)
    val enable = Bool(INPUT)
    val init_done = Bool(INPUT)
  }
}
