// See LICENSE.SiFive for license details.

package freechips.rocketchip.amba.ahb

import Chisel._
import chisel3.internal.sourceinfo.SourceInfo
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.tilelink._
import freechips.rocketchip.util.MaskGen

case class AHBToTLNode() extends MixedAdapterNode(AHBImp, TLImp)(
  dFn = { case AHBMasterPortParameters(masters) =>
    TLClientPortParameters(clients = masters.map { m =>
      TLClientParameters(name = m.name, nodePath = m.nodePath)
    })
  },
  uFn = { mp => AHBSlavePortParameters(
    slaves = mp.managers.map { m =>
      def adjust(x: TransferSizes) = {
        if (x.contains(mp.beatBytes)) {
          TransferSizes(x.min, m.minAlignment.min(mp.beatBytes * AHBParameters.maxTransfer).toInt)
        } else { // larger than beatBytes requires beatBytes if misaligned
          x.intersect(TransferSizes(1, mp.beatBytes))
        }
      }

      AHBSlaveParameters(
        address       = m.address,
        resources     = m.resources,
        regionType    = m.regionType,
        executable    = m.executable,
        nodePath      = m.nodePath,
        supportsWrite = adjust(m.supportsPutFull),
        supportsRead  = adjust(m.supportsGet))},
    beatBytes = mp.beatBytes)
  })

class AHBToTL()(implicit p: Parameters) extends LazyModule
{
  val node = AHBToTLNode()

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

    ((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
      val beatBytes = edgeOut.manager.beatBytes

      val d_send  = RegInit(Bool(false))
      val d_recv  = RegInit(Bool(false))
      val d_pause = RegInit(Bool(true))
      val d_error = RegInit(Bool(false))
      val d_write = RegInit(Bool(false))
      val d_addr  = Reg(in.haddr)
      val d_size  = Reg(in.hsize)

      when (out.d.valid) { d_recv  := Bool(false) }
      when (out.a.ready) { d_send  := Bool(false) }
      when (in.hresp)    { d_pause := Bool(false) }

      val a_count  = RegInit(UInt(0, width = 4))
      val a_first  = a_count === UInt(0)
      val d_last   = a_first

      val burst_sizes = Seq(1, 1, 4, 4, 8, 8, 16, 16)
      val a_burst_size = Vec(burst_sizes.map(beats => UInt(log2Ceil(beats * beatBytes))))(in.hburst)
      val a_burst_mask = Vec(burst_sizes.map(beats => UInt(beats * beatBytes - 1)))(in.hburst)

      val a_burst_ok =
        in.htrans === AHBParameters.TRANS_NONSEQ && // only start burst on first AHB beat
        in.hsize  === UInt(log2Ceil(beatBytes))  && // not a narrow burst
        (in.haddr & a_burst_mask) === UInt(0)    && // address aligned to burst size
        in.hburst =/= AHBParameters.BURST_INCR   && // we know the burst length a priori
        Mux(in.hwrite,                              // target device supports the burst
          edgeOut.manager.supportsPutFullSafe(in.haddr, a_burst_size),
          edgeOut.manager.supportsGetSafe    (in.haddr, a_burst_size))

      val beat = TransferSizes(1, beatBytes)
      val a_legal = // Is the single-beat access allowed?
        Mux(in.hwrite,
          edgeOut.manager.supportsPutFullSafe(in.haddr, in.hsize, Some(beat)),
          edgeOut.manager.supportsGetSafe    (in.haddr, in.hsize, Some(beat)))

      val a_access = in.htrans === AHBParameters.TRANS_NONSEQ || in.htrans === AHBParameters.TRANS_SEQ
      val a_accept = in.hready && in.hsel && a_access

      when (a_accept) {
        a_count := a_count - UInt(1)
        when ( in.hwrite) { d_send := Bool(true) }
        when (!in.hwrite) { d_recv := Bool(true) }
        when (a_first) {
          a_count := Mux(a_burst_ok, a_burst_mask >> log2Ceil(beatBytes), UInt(0))
          d_send  := a_legal
          d_recv  := a_legal
          d_pause := Bool(true)
          d_write := in.hwrite
          d_addr  := in.haddr
          d_size  := Mux(a_burst_ok, a_burst_size, in.hsize)
        }
      }

      out.a.valid        := d_send
      out.a.bits.opcode  := Mux(d_write, TLMessages.PutFullData, TLMessages.Get)
      out.a.bits.param   := UInt(0)
      out.a.bits.size    := d_size
      out.a.bits.source  := UInt(0)
      out.a.bits.address := d_addr
      out.a.bits.data    := in.hwdata
      out.a.bits.mask    := MaskGen(d_addr, d_size, beatBytes)

      out.d.ready  := d_recv // backpressure AccessAckData arriving faster than AHB beats
      in.hrdata    := out.d.bits.data

      // In a perfect world, we'd use these signals
      val hresp = d_error || (out.d.valid && out.d.bits.error)
      val hreadyout = Mux(d_write, (!d_send || out.a.ready) && (!d_last || !d_recv || out.d.valid), out.d.valid || !d_recv)

      // Make the error persistent (and defer it to the last beat--otherwise AHB can cancel the burst!)
      d_error :=
        (hresp && !(a_first && in.hready)) || // clear error when a new beat starts
        (a_accept && !a_legal)                // error if the address requested is illegal

      // When we report an error, we need to be hreadyout LOW for one cycle
      in.hresp     := hreadyout &&  (hresp && d_last)
      in.hreadyout := hreadyout && !(hresp && d_last && d_pause)

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

object AHBToTL
{
  def apply()(x: AHBOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = {
    val tl = LazyModule(new AHBToTL)
    tl.node :=? x
    tl.node
  }
}
