// See LICENSE for license details.

package uncore.axi4

import Chisel._
import chisel3.internal.sourceinfo.SourceInfo
import diplomacy._
import uncore.tilelink2._

case class AXI4ToTLNode() extends MixedNode(AXI4Imp, TLImp)(
  dFn = { case (1, Seq(AXI4MasterPortParameters(masters))) =>
    Seq(TLClientPortParameters(clients = masters.map { m =>
      TLClientParameters(
        sourceId = IdRange(m.id.start << 1, m.id.end << 1), // R+W ids are distinct
        nodePath = m.nodePath)
    }))
  },
  uFn = { case (1, Seq(TLManagerPortParameters(managers, beatBytes, _))) =>
    Seq(AXI4SlavePortParameters(beatBytes = beatBytes, slaves = managers.map { m =>
      AXI4SlaveParameters(
        address       = m.address,
        regionType    = m.regionType,
        executable    = m.executable,
        nodePath      = m.nodePath,
        supportsWrite = m.supportsPutPartial,
        supportsRead  = m.supportsGet,
        interleavedId = Some(0)) // TL2 never interleaves D beats
    }))
  },
  numPO = 1 to 1,
  numPI = 1 to 1)

class AXI4ToTL extends LazyModule
{
  val node = AXI4ToTLNode()

  lazy val module = new LazyModuleImp(this) {
    val io = new Bundle {
      val in = node.bundleIn
      val out = node.bundleOut
    }

    val in = io.in(0)
    val out = io.out(0)
    val edgeIn = node.edgesIn(0)
    val edgeOut = node.edgesOut(0)
    val numIds = edgeIn.master.endId
    val beatBytes = edgeOut.manager.beatBytes
    val countBits = AXI4Parameters.lenBits + (1 << AXI4Parameters.sizeBits) - 1

    require (edgeIn.master.masters(0).aligned)

    val r_out = Wire(out.a)
    val r_inflight = RegInit(UInt(0, width = numIds))
    val r_block = r_inflight(in.ar.bits.id)
    val r_size1 = in.ar.bits.bytes1()
    val r_size = OH1ToUInt(r_size1)
    val r_addr = in.ar.bits.addr
    val r_ok = edgeOut.manager.supportsGetSafe(r_addr, r_size)
    val r_err_in = Wire(Decoupled(new AXI4BundleRError(in.ar.bits.params)))
    val r_err_out = Queue(r_err_in, 2)
    val r_count = RegInit(UInt(0, width = in.ar.bits.params.lenBits))
    val r_last = r_count === in.ar.bits.len

    assert (!in.ar.valid || r_size1 === UIntToOH1(r_size, countBits)) // because aligned
    in.ar.ready := Mux(r_ok, r_out.ready, r_err_in.ready && r_last) && !r_block
    r_out.valid := in.ar.valid && !r_block && r_ok
    r_out.bits := edgeOut.Get(in.ar.bits.id << 1 | UInt(1), r_addr, r_size)._2
    r_err_in.valid := in.ar.valid && !r_block && !r_ok
    r_err_in.bits.last := r_last
    r_err_in.bits.id := in.ar.bits.id

    when (r_err_in.fire()) { r_count := Mux(r_last, UInt(0), r_count + UInt(1)) }

    val w_out = Wire(out.a)
    val w_inflight = RegInit(UInt(0, width = numIds))
    val w_block = w_inflight(in.aw.bits.id)
    val w_size1 = in.aw.bits.bytes1()
    val w_size = OH1ToUInt(w_size1)
    val w_addr = in.aw.bits.addr
    val w_ok = edgeOut.manager.supportsPutPartialSafe(w_addr, w_size)
    val w_err_in = Wire(Decoupled(in.aw.bits.id))
    val w_err_out = Queue(w_err_in, 2)

    assert (!in.aw.valid || w_size1 === UIntToOH1(w_size, countBits)) // because aligned
    assert (!in.aw.valid || in.aw.bits.len === UInt(0) || in.aw.bits.size === UInt(log2Ceil(beatBytes))) // because aligned
    in.aw.ready := Mux(w_ok, w_out.ready, w_err_in.ready) && in.w.valid && in.w.bits.last && !w_block
    in.w.ready  := Mux(w_ok, w_out.ready, w_err_in.ready || !in.w.bits.last) && in.aw.valid && !w_block
    w_out.valid := in.aw.valid && in.w.valid && !w_block && w_ok
    w_out.bits := edgeOut.Put(in.aw.bits.id << 1, w_addr, w_size, in.w.bits.data, in.w.bits.strb)._2
    w_err_in.valid := in.aw.valid && in.w.valid && !w_block && !w_ok && in.w.bits.last
    w_err_in.bits := in.aw.bits.id

    TLArbiter(TLArbiter.lowestIndexFirst)(out.a, (UInt(0), r_out), (in.aw.bits.len, w_out))

    val ok_b  = Wire(in.b)
    val err_b = Wire(in.b)
    val mux_b = Wire(in.b)
    val ok_r  = Wire(in.r)
    val err_r = Wire(in.r)
    val mux_r = Wire(in.r)

    val d_resp = Mux(out.d.bits.error, AXI4Parameters.RESP_SLVERR, AXI4Parameters.RESP_OKAY)
    val d_hasData = edgeOut.hasData(out.d.bits)
    val (_, d_last, _) = edgeOut.firstlast(out.d.bits, out.d.fire())

    out.d.ready := Mux(d_hasData, ok_r.ready, ok_b.ready)
    ok_r.valid := out.d.valid && d_hasData
    ok_b.valid := out.d.valid && !d_hasData

    ok_r.bits.id   := out.d.bits.source >> 1
    ok_r.bits.data := out.d.bits.data
    ok_r.bits.resp := d_resp
    ok_r.bits.last := d_last

    r_err_out.ready := err_r.ready
    err_r.valid := r_err_out.valid
    err_r.bits.id   := r_err_out.bits.id
    err_r.bits.data := out.d.bits.data // don't care
    err_r.bits.resp := AXI4Parameters.RESP_DECERR
    err_r.bits.last := r_err_out.bits.last

    // AXI4 must hold R to one source until last
    val mux_lock_ok  = RegInit(Bool(false))
    val mux_lock_err = RegInit(Bool(false))
    when (ok_r .fire()) { mux_lock_ok  := !ok_r .bits.last }
    when (err_r.fire()) { mux_lock_err := !err_r.bits.last }
    assert (!mux_lock_ok || !mux_lock_err)

    // Prioritize err over ok (b/c err_r.valid comes from a register)
    mux_r.valid := (!mux_lock_err && ok_r.valid) || (!mux_lock_ok && err_r.valid)
    mux_r.bits  := Mux(!mux_lock_ok && err_r.valid, err_r.bits, ok_r.bits)
    ok_r.ready  := mux_r.ready && (mux_lock_ok || !err_r.valid)
    err_r.ready := mux_r.ready && !mux_lock_ok

    // AXI4 needs irrevocable behaviour
    in.r <> Queue.irrevocable(mux_r, 1, flow=true)

    ok_b.bits.id   := out.d.bits.source >> 1
    ok_b.bits.resp := d_resp

    w_err_out.ready := err_b.ready
    err_b.valid := w_err_out.valid
    err_b.bits.id   := w_err_out.bits
    err_b.bits.resp := AXI4Parameters.RESP_DECERR

    // Prioritize err over ok (b/c err_b.valid comes from a register)
    mux_b.valid := ok_b.valid || err_b.valid
    mux_b.bits  := Mux(err_b.valid, err_b.bits, ok_b.bits)
    ok_b.ready  := mux_b.ready && !err_b.valid
    err_b.ready := mux_b.ready

    // AXI4 needs irrevocable behaviour
    in.b <> Queue.irrevocable(mux_b, 1, flow=true)

    // Update flight trackers
    val r_set = in.ar.fire().asUInt << in.ar.bits.id
    val r_clr = (in.r.fire() && in.r.bits.last).asUInt << in.r.bits.id
    r_inflight := (r_inflight | r_set) & ~r_clr
    val w_set = in.aw.fire().asUInt << in.aw.bits.id
    val w_clr = in.b.fire().asUInt << in.b.bits.id
    w_inflight := (w_inflight | w_set) & ~w_clr

    // Unused channels
    out.b.ready := Bool(true)
    out.c.valid := Bool(false)
    out.e.valid := Bool(false)
  }
}

class AXI4BundleRError(params: AXI4BundleParameters) extends AXI4BundleBase(params)
{
  val id   = UInt(width = params.idBits)
  val last = Bool()
}

object AXI4ToTL
{
  def apply()(x: AXI4OutwardNode)(implicit sourceInfo: SourceInfo): TLOutwardNode = {
    val tl = LazyModule(new AXI4ToTL)
    tl.node := x
    tl.node
  }
}
