package uncore.converters

import Chisel._
import junctions._
import uncore.tilelink._
import uncore.constants._
import cde.Parameters
import scala.math.min

class IdMapper(val inIdBits: Int, val outIdBits: Int,
               val forceMapping: Boolean = false)
              (implicit val p: Parameters) extends Module {

  val io = new Bundle {
    val req = new Bundle {
      val valid  = Bool(INPUT)
      val ready  = Bool(OUTPUT)
      val in_id  = UInt(INPUT, inIdBits)
      val out_id = UInt(OUTPUT, outIdBits)
    }
    val resp = new Bundle {
      val valid   = Bool(INPUT)
      val matches = Bool(OUTPUT)
      val out_id  = UInt(INPUT, outIdBits)
      val in_id   = UInt(OUTPUT, inIdBits)
    }
  }
  val maxInXacts = 1 << inIdBits

  if (inIdBits <= outIdBits && !forceMapping) {
    io.req.ready := Bool(true)
    io.req.out_id := io.req.in_id
    io.resp.matches := Bool(true)
    io.resp.in_id := io.resp.out_id
  } else {
    val nInXacts = 1 << inIdBits
    // No point in allowing more out xacts than in xacts
    val nOutXacts = min(1 << outIdBits, nInXacts)

    val out_id_free = Reg(init = Vec.fill(nOutXacts){Bool(true)})
    val in_id_free = Reg(init = Vec.fill(nInXacts){Bool(true)})
    val next_out_id = PriorityEncoder(out_id_free)
    val id_mapping = Reg(Vec(nOutXacts, UInt(0, inIdBits)))

    val req_fire = io.req.valid && io.req.ready
    when (req_fire) {
      out_id_free(io.req.out_id) := Bool(false)
      in_id_free(io.req.in_id) := Bool(false)
      id_mapping(io.req.out_id) := io.req.in_id
    }
    when (io.resp.valid) {
      out_id_free(io.resp.out_id) := Bool(true)
      in_id_free(io.resp.in_id) := Bool(true)
    }

    io.req.ready := out_id_free.reduce(_ || _) && in_id_free(io.req.in_id)
    io.req.out_id := next_out_id

    io.resp.in_id := id_mapping(io.resp.out_id)
    io.resp.matches := !out_id_free(io.resp.out_id)
  }
}

class NastiIOTileLinkIOConverterInfo(implicit p: Parameters) extends TLBundle()(p) {
  val addr_beat = UInt(width = tlBeatAddrBits)
  val subblock = Bool()
}

class NastiIOTileLinkIOConverter(implicit p: Parameters) extends TLModule()(p)
    with HasNastiParameters {
  val io = new Bundle {
    val tl = new ClientUncachedTileLinkIO().flip
    val nasti = new NastiIO
  }

  val dataBits = tlDataBits*tlDataBeats 
  require(tlDataBits == nastiXDataBits, "Data sizes between LLC and MC don't agree") // TODO: remove this restriction
  require(tlDataBeats < (1 << nastiXLenBits), "Can't have that many beats")

  val has_data = io.tl.acquire.bits.hasData()

  val is_subblock = io.tl.acquire.bits.isSubBlockType()
  val is_multibeat = io.tl.acquire.bits.hasMultibeatData()
  val (tl_cnt_out, tl_wrap_out) = Counter(
    io.tl.acquire.fire() && is_multibeat, tlDataBeats)

  val get_valid = io.tl.acquire.valid && !has_data
  val put_valid = io.tl.acquire.valid && has_data

  // Reorder queue saves extra information needed to send correct
  // grant back to TL client
  val roqIdBits = min(tlClientXactIdBits, nastiXIdBits)
  val roq = Module(new ReorderQueue(
    new NastiIOTileLinkIOConverterInfo, roqIdBits))

  val get_id_mapper = Module(new IdMapper(tlClientXactIdBits, nastiXIdBits))
  val put_id_mapper = Module(new IdMapper(tlClientXactIdBits, nastiXIdBits))

  val get_id_ready = get_id_mapper.io.req.ready
  val put_id_mask = is_subblock || io.tl.acquire.bits.addr_beat === UInt(0)
  val put_id_ready = put_id_mapper.io.req.ready || !put_id_mask

  // For Get/GetBlock, make sure Reorder queue can accept new entry
  val get_helper = DecoupledHelper(
    get_valid,
    roq.io.enq.ready,
    io.nasti.ar.ready,
    get_id_ready)

  val w_inflight = Reg(init = Bool(false))
  val w_id_reg = Reg(init = UInt(0, nastiXIdBits))
  val w_id = Mux(w_inflight, w_id_reg, put_id_mapper.io.req.out_id)

  // For Put/PutBlock, make sure aw and w channel are both ready before
  // we send the first beat
  val aw_ready = w_inflight || io.nasti.aw.ready
  val put_helper = DecoupledHelper(
    put_valid,
    aw_ready,
    io.nasti.w.ready,
    put_id_ready)

  val (nasti_cnt_out, nasti_wrap_out) = Counter(
    io.nasti.r.fire() && !roq.io.deq.data.subblock, tlDataBeats)

  roq.io.enq.valid := get_helper.fire(roq.io.enq.ready)
  roq.io.enq.bits.tag := io.nasti.ar.bits.id
  roq.io.enq.bits.data.addr_beat := io.tl.acquire.bits.addr_beat
  roq.io.enq.bits.data.subblock := is_subblock
  roq.io.deq.valid := io.nasti.r.fire() && (nasti_wrap_out || roq.io.deq.data.subblock)
  roq.io.deq.tag := io.nasti.r.bits.id

  get_id_mapper.io.req.valid := get_helper.fire(get_id_ready)
  get_id_mapper.io.req.in_id := io.tl.acquire.bits.client_xact_id
  get_id_mapper.io.resp.valid := io.nasti.r.fire() && io.nasti.r.bits.last
  get_id_mapper.io.resp.out_id := io.nasti.r.bits.id

  put_id_mapper.io.req.valid := put_helper.fire(put_id_ready, put_id_mask)
  put_id_mapper.io.req.in_id := io.tl.acquire.bits.client_xact_id
  put_id_mapper.io.resp.valid := io.nasti.b.fire()
  put_id_mapper.io.resp.out_id := io.nasti.b.bits.id

  // Decompose outgoing TL Acquires into Nasti address and data channels
  io.nasti.ar.valid := get_helper.fire(io.nasti.ar.ready)
  io.nasti.ar.bits := NastiReadAddressChannel(
    id = get_id_mapper.io.req.out_id,
    addr = io.tl.acquire.bits.full_addr(),
    size = Mux(is_subblock,
      io.tl.acquire.bits.op_size(),
      UInt(log2Ceil(tlDataBytes))),
    len = Mux(is_subblock, UInt(0), UInt(tlDataBeats - 1)))

  def mask_helper(all_inside_0: Seq[Bool], defsize: Int): (Seq[Bool], UInt, UInt) = {
    val len = all_inside_0.size
    if (len == 1) {
      (Seq(Bool(true)), UInt(0), UInt(defsize))
    } else {
      val sub_inside_0 = Seq.tabulate (len/2) { i => all_inside_0(2*i) && all_inside_0(2*i+1) }
      val (sub_outside_0, sub_offset, sub_size) = mask_helper(sub_inside_0, defsize+1)
      val all_outside_0 = Seq.tabulate (len) { i => sub_outside_0(i/2) && all_inside_0(i^1) }
      val odd_outside_0 = Seq.tabulate (len/2) { i => all_outside_0(2*i+1) }
      val odd_outside = odd_outside_0.reduce (_ || _)
      val all_outside = all_outside_0.reduce (_ || _)
      val offset = Cat(sub_offset, odd_outside)
      val size = Mux(all_outside, UInt(defsize), sub_size)
      (all_outside_0, offset, size)
    }
  }

  val all_inside_0 = (~io.tl.acquire.bits.wmask()).toBools
  val (_, put_offset, put_size) = mask_helper(all_inside_0, 0)

  io.nasti.aw.valid := put_helper.fire(aw_ready, !w_inflight)
  io.nasti.aw.bits := NastiWriteAddressChannel(
    id = put_id_mapper.io.req.out_id,
    addr = io.tl.acquire.bits.full_addr() |
           Mux(is_multibeat, UInt(0), put_offset),
    size = Mux(is_multibeat, UInt(log2Ceil(tlDataBytes)), put_size),
    len = Mux(is_multibeat, UInt(tlDataBeats - 1), UInt(0)))

  io.nasti.w.valid := put_helper.fire(io.nasti.w.ready)
  io.nasti.w.bits := NastiWriteDataChannel(
    id = w_id,
    data = io.tl.acquire.bits.data,
    strb = Some(io.tl.acquire.bits.wmask()),
    last = Mux(w_inflight,
      tl_cnt_out === UInt(tlDataBeats - 1), !is_multibeat))

  io.tl.acquire.ready := Mux(has_data,
    put_helper.fire(put_valid),
    get_helper.fire(get_valid))

  when (!w_inflight && io.tl.acquire.fire() && is_multibeat) {
    w_inflight := Bool(true)
    w_id_reg := w_id
  }

  when (w_inflight) {
    when (tl_wrap_out) { w_inflight := Bool(false) }
  }

  // Aggregate incoming NASTI responses into TL Grants
  val (tl_cnt_in, tl_wrap_in) = Counter(
    io.tl.grant.fire() && io.tl.grant.bits.hasMultibeatData(), tlDataBeats)
  val gnt_arb = Module(new LockingArbiter(new GrantToDst, 2,
    tlDataBeats, Some((gnt: GrantToDst) => gnt.hasMultibeatData())))
  io.tl.grant <> gnt_arb.io.out

  gnt_arb.io.in(0).valid := io.nasti.r.valid
  io.nasti.r.ready := gnt_arb.io.in(0).ready
  gnt_arb.io.in(0).bits := Grant(
    is_builtin_type = Bool(true),
    g_type = Mux(roq.io.deq.data.subblock,
      Grant.getDataBeatType, Grant.getDataBlockType),
    client_xact_id = get_id_mapper.io.resp.in_id,
    manager_xact_id = UInt(0),
    addr_beat = Mux(roq.io.deq.data.subblock, roq.io.deq.data.addr_beat, tl_cnt_in),
    data = io.nasti.r.bits.data)

  assert(!roq.io.deq.valid || roq.io.deq.matches,
    "TL -> NASTI converter ReorderQueue: NASTI tag error")
  assert(!gnt_arb.io.in(0).valid || get_id_mapper.io.resp.matches,
    "TL -> NASTI ID Mapper: NASTI tag error")

  gnt_arb.io.in(1).valid := io.nasti.b.valid
  io.nasti.b.ready := gnt_arb.io.in(1).ready
  gnt_arb.io.in(1).bits := Grant(
    is_builtin_type = Bool(true),
    g_type = Grant.putAckType,
    client_xact_id = put_id_mapper.io.resp.in_id,
    manager_xact_id = UInt(0),
    addr_beat = UInt(0),
    data = Bits(0))
  assert(!gnt_arb.io.in(1).valid || put_id_mapper.io.resp.matches, "NASTI tag error")

  assert(!io.nasti.r.valid || io.nasti.r.bits.resp === UInt(0), "NASTI read error")
  assert(!io.nasti.b.valid || io.nasti.b.bits.resp === UInt(0), "NASTI write error")
}

class TileLinkIONastiIOConverter(implicit p: Parameters) extends TLModule()(p)
    with HasNastiParameters {
  val io = new Bundle {
    val nasti = (new NastiIO).flip
    val tl = new ClientUncachedTileLinkIO
  }

  val (s_idle :: s_put :: Nil) = Enum(Bits(), 2)
  val state = Reg(init = s_idle)

  private val blockOffset = tlByteAddrBits + tlBeatAddrBits

  val aw_req = Reg(new NastiWriteAddressChannel)
  val w_tl_id = Reg(io.tl.acquire.bits.client_xact_id)

  def is_singlebeat(chan: NastiAddressChannel): Bool =
    chan.len === UInt(0)

  def is_multibeat(chan: NastiAddressChannel): Bool =
    chan.len === UInt(tlDataBeats - 1) && chan.size === UInt(log2Up(tlDataBytes))

  def nasti_addr_block(chan: NastiAddressChannel): UInt =
    chan.addr(nastiXAddrBits - 1, blockOffset)

  def nasti_addr_beat(chan: NastiAddressChannel): UInt =
    chan.addr(blockOffset - 1, tlByteAddrBits)

  def nasti_addr_byte(chan: NastiAddressChannel): UInt =
    chan.addr(tlByteAddrBits - 1, 0)

  def size_mask(size: UInt): UInt =
    (UInt(1) << (UInt(1) << size)) - UInt(1)

  def nasti_wmask(aw: NastiWriteAddressChannel, w: NastiWriteDataChannel): UInt = {
    val base = w.strb & size_mask(aw.size)
    val addr_byte = nasti_addr_byte(aw)
    w.strb & (size_mask(aw.size) << addr_byte)
  }

  def tl_last(gnt: GrantMetadata): Bool =
    !gnt.hasMultibeatData() || gnt.addr_beat === UInt(tlDataBeats - 1)

  def tl_b_grant(gnt: GrantMetadata): Bool =
    gnt.g_type === Grant.putAckType

  assert(!io.nasti.ar.valid ||
    is_singlebeat(io.nasti.ar.bits) || is_multibeat(io.nasti.ar.bits),
    "NASTI read transaction cannot convert to TileLInk")

  assert(!io.nasti.aw.valid ||
    is_singlebeat(io.nasti.aw.bits) || is_multibeat(io.nasti.aw.bits),
    "NASTI write transaction cannot convert to TileLInk")

  val put_count = Reg(init = UInt(0, tlBeatAddrBits))
  val get_id_mapper = Module(new IdMapper(nastiXIdBits, tlClientXactIdBits, true))
  val put_id_mapper = Module(new IdMapper(nastiXIdBits, tlClientXactIdBits, true))

  when (io.nasti.aw.fire()) {
    aw_req := io.nasti.aw.bits
    w_tl_id := put_id_mapper.io.req.out_id
    state := s_put
  }

  when (io.nasti.w.fire()) {
    put_count := put_count + UInt(1)
    when (io.nasti.w.bits.last) {
      put_count := UInt(0)
      state := s_idle
    }
  }

  val get_acquire = Mux(is_multibeat(io.nasti.ar.bits),
    GetBlock(
      client_xact_id = get_id_mapper.io.req.out_id,
      addr_block = nasti_addr_block(io.nasti.ar.bits)),
    Get(
      client_xact_id = get_id_mapper.io.req.out_id,
      addr_block = nasti_addr_block(io.nasti.ar.bits),
      addr_beat = nasti_addr_beat(io.nasti.ar.bits),
      addr_byte = nasti_addr_byte(io.nasti.ar.bits),
      operand_size = io.nasti.ar.bits.size,
      alloc = Bool(false)))

  val put_acquire = Mux(is_multibeat(aw_req),
    PutBlock(
      client_xact_id = w_tl_id,
      addr_block = nasti_addr_block(aw_req),
      addr_beat = put_count,
      data = io.nasti.w.bits.data,
      wmask = Some(io.nasti.w.bits.strb)),
    Put(
      client_xact_id = w_tl_id,
      addr_block = nasti_addr_block(aw_req),
      addr_beat = nasti_addr_beat(aw_req),
      data = io.nasti.w.bits.data,
      wmask = Some(nasti_wmask(aw_req, io.nasti.w.bits))))

  val get_helper = DecoupledHelper(
    io.nasti.ar.valid,
    get_id_mapper.io.req.ready,
    io.tl.acquire.ready)

  get_id_mapper.io.req.valid := get_helper.fire(
    get_id_mapper.io.req.ready, state === s_idle)
  get_id_mapper.io.req.in_id := io.nasti.ar.bits.id
  get_id_mapper.io.resp.out_id := io.tl.grant.bits.client_xact_id
  get_id_mapper.io.resp.valid := io.nasti.r.fire() && io.nasti.r.bits.last

  val aw_ok = (state === s_idle && !io.nasti.ar.valid)

  put_id_mapper.io.req.valid := aw_ok && io.nasti.aw.valid
  put_id_mapper.io.req.in_id := io.nasti.aw.bits.id
  put_id_mapper.io.resp.out_id := io.tl.grant.bits.client_xact_id
  put_id_mapper.io.resp.valid := io.nasti.b.fire()

  io.tl.acquire.bits := Mux(state === s_put, put_acquire, get_acquire)
  io.tl.acquire.valid := get_helper.fire(io.tl.acquire.ready, state === s_idle) ||
                         (state === s_put && io.nasti.w.valid)

  io.nasti.ar.ready := get_helper.fire(io.nasti.ar.valid, state === s_idle)
  io.nasti.aw.ready := aw_ok && put_id_mapper.io.req.ready
  io.nasti.w.ready  := (state === s_put && io.tl.acquire.ready)

  val nXacts = tlMaxClientXacts * tlMaxClientsPerPort

  io.nasti.b.valid := io.tl.grant.valid && tl_b_grant(io.tl.grant.bits)
  io.nasti.b.bits := NastiWriteResponseChannel(
    id = put_id_mapper.io.resp.in_id)

  assert(!io.nasti.b.valid || put_id_mapper.io.resp.matches,
    "Put ID does not match")

  io.nasti.r.valid := io.tl.grant.valid && !tl_b_grant(io.tl.grant.bits)
  io.nasti.r.bits := NastiReadDataChannel(
    id = get_id_mapper.io.resp.in_id,
    data = io.tl.grant.bits.data,
    last = tl_last(io.tl.grant.bits))

  assert(!io.nasti.r.valid || get_id_mapper.io.resp.matches,
    "Get ID does not match")

  io.tl.grant.ready := Mux(tl_b_grant(io.tl.grant.bits),
    io.nasti.b.ready, io.nasti.r.ready)
}
