package groundtest

import Chisel._
import uncore.tilelink._
import uncore.devices.NTiles
import uncore.constants._
import junctions._
import rocket._
import scala.util.Random
import cde.{Parameters, Field}

case class GeneratorParameters(
  maxRequests: Int,
  startAddress: BigInt)
case object GeneratorKey extends Field[GeneratorParameters]

trait HasGeneratorParameters extends HasGroundTestParameters {
  implicit val p: Parameters

  val genParams = p(GeneratorKey)
  val nGens = p(GroundTestKey).map(
    cs => cs.uncached + cs.cached).reduce(_ + _)
  val genTimeout = 8192
  val maxRequests = genParams.maxRequests
  val startAddress = genParams.startAddress

  val genWordBits = 32
  val genWordBytes = genWordBits / 8
  val wordOffset = log2Ceil(genWordBytes)
  val wordSize = UInt(log2Ceil(genWordBytes))

  require(startAddress % BigInt(genWordBytes) == 0)
}

class UncachedTileLinkGenerator(id: Int)
    (implicit p: Parameters) extends TLModule()(p) with HasGeneratorParameters {

  private val tlBlockOffset = tlBeatAddrBits + tlByteAddrBits

  val io = new Bundle {
    val mem = new ClientUncachedTileLinkIO
    val status = new GroundTestStatus
  }

  val (s_start :: s_put :: s_get :: s_finished :: Nil) = Enum(Bits(), 4)
  val state = Reg(init = s_start)

  val (req_cnt, req_wrap) = Counter(io.mem.grant.fire(), maxRequests)

  val sending = Reg(init = Bool(false))

  when (state === s_start) {
    sending := Bool(true)
    state := s_put
  }

  when (io.mem.acquire.fire()) { sending := Bool(false) }
  when (io.mem.grant.fire()) { sending := Bool(true) }
  when (req_wrap) { state := Mux(state === s_put, s_get, s_finished) }

  val timeout = Timer(genTimeout, io.mem.acquire.fire(), io.mem.grant.fire())
  assert(!timeout, s"Uncached generator ${id} timed out waiting for grant")

  io.status.finished := (state === s_finished)
  io.status.timeout.valid := timeout
  io.status.timeout.bits := UInt(id)

  val part_of_full_addr =
    if (log2Ceil(nGens) > 0) {
      Cat(UInt(id, log2Ceil(nGens)),
          UInt(0, wordOffset))
    } else {
      UInt(0, wordOffset)
    }
  val full_addr = UInt(startAddress) + Cat(req_cnt, part_of_full_addr)

  val addr_block = full_addr >> UInt(tlBlockOffset)
  val addr_beat = full_addr(tlBlockOffset - 1, tlByteAddrBits)
  val addr_byte = full_addr(tlByteAddrBits - 1, 0)

  val data_prefix = Cat(UInt(id, log2Up(nGens)), req_cnt)
  val word_data = Wire(UInt(width = genWordBits))
  word_data := Cat(data_prefix, part_of_full_addr)
  val beat_data = Fill(tlDataBits / genWordBits, word_data)
  val wshift = Cat(beatOffset(full_addr), UInt(0, wordOffset))
  val wmask = Fill(genWordBits / 8, Bits(1, 1)) << wshift

  val put_acquire = Put(
    client_xact_id = UInt(0),
    addr_block = addr_block,
    addr_beat = addr_beat,
    data = beat_data,
    wmask = Some(wmask),
    alloc = Bool(false))

  val get_acquire = Get(
    client_xact_id = UInt(0),
    addr_block = addr_block,
    addr_beat = addr_beat,
    addr_byte = addr_byte,
    operand_size = wordSize,
    alloc = Bool(false))

  io.mem.acquire.valid := sending && !io.status.finished
  io.mem.acquire.bits := Mux(state === s_put, put_acquire, get_acquire)
  io.mem.grant.ready := !sending && !io.status.finished

  def wordFromBeat(addr: UInt, dat: UInt) = {
    val shift = Cat(beatOffset(addr), UInt(0, wordOffset + 3))
    (dat >> shift)(genWordBits - 1, 0)
  }

  val data_mismatch = io.mem.grant.fire() && state === s_get &&
    wordFromBeat(full_addr, io.mem.grant.bits.data) =/= word_data

  io.status.error.valid := data_mismatch
  io.status.error.bits := UInt(id)

  assert(!data_mismatch,
    s"Get received incorrect data in uncached generator ${id}")

  def beatOffset(addr: UInt) = // TODO zero-width
    if (tlByteAddrBits > wordOffset) addr(tlByteAddrBits - 1, wordOffset)
    else UInt(0)
}

class HellaCacheGenerator(id: Int)
    (implicit p: Parameters) extends L1HellaCacheModule()(p) with HasGeneratorParameters {
  val io = new Bundle {
    val mem = new HellaCacheIO
    val status = new GroundTestStatus
  }

  val timeout = Timer(genTimeout, io.mem.req.fire(), io.mem.resp.valid)
  assert(!timeout, s"Cached generator ${id} timed out waiting for response")
  io.status.timeout.valid := timeout
  io.status.timeout.bits := UInt(id)

  val (s_start :: s_write :: s_read :: s_finished :: Nil) = Enum(Bits(), 4)
  val state = Reg(init = s_start)
  val sending = Reg(init = Bool(false))

  val (req_cnt, req_wrap) = Counter(io.mem.resp.valid, maxRequests)

  val part_of_req_addr =
    if (log2Ceil(nGens) > 0) {
      Cat(UInt(id, log2Ceil(nGens)),
          UInt(0, wordOffset))
    } else {
      UInt(0, wordOffset)
    }
  val req_addr = UInt(startAddress) + Cat(req_cnt, part_of_req_addr)
  val req_data = Cat(UInt(id, log2Up(nGens)), req_cnt, part_of_req_addr)

  io.mem.req.valid := sending && !io.status.finished
  io.mem.req.bits.addr := req_addr
  io.mem.req.bits.data := req_data
  io.mem.req.bits.typ  := wordSize
  io.mem.req.bits.cmd  := Mux(state === s_write, M_XWR, M_XRD)
  io.mem.req.bits.tag  := UInt(0)

  when (state === s_start) { sending := Bool(true); state := s_write }

  when (io.mem.req.fire()) { sending := Bool(false) }
  when (io.mem.resp.valid) { sending := Bool(true) }

  when (req_wrap) { state := Mux(state === s_write, s_read, s_finished) }

  io.status.finished := (state === s_finished)

  def data_match(recv: Bits, expected: Bits): Bool = {
    val recv_resized = Wire(Bits(width = genWordBits))
    val exp_resized = Wire(Bits(width = genWordBits))

    recv_resized := recv
    exp_resized := expected
    recv_resized === exp_resized
  }

  val data_mismatch = io.mem.resp.valid && io.mem.resp.bits.has_data &&
    !data_match(io.mem.resp.bits.data, req_data)

  io.status.error.valid := data_mismatch
  io.status.error.bits := UInt(id)

  assert(!data_mismatch,
    s"Received incorrect data in cached generator ${id}")
}

class GeneratorTest(implicit p: Parameters)
    extends GroundTest()(p) with HasGeneratorParameters {

  val idStart = p(GroundTestKey).take(tileId)
    .map(settings => settings.cached + settings.uncached)
    .foldLeft(0)(_ + _)

  val cached = List.tabulate(nCached) { i =>
    val realId = idStart + i
    Module(new HellaCacheGenerator(realId))
  }

  val uncached = List.tabulate(nUncached) { i =>
    val realId = idStart + nCached + i
    Module(new UncachedTileLinkGenerator(realId))
  }

  io.cache <> cached.map(_.io.mem)
  io.mem <> uncached.map(_.io.mem)

  val gen_debug = cached.map(_.io.status) ++ uncached.map(_.io.status)
  io.status := DebugCombiner(gen_debug)
}
