// See LICENSE for license details.

package uncore.devices

import Chisel._
import Chisel.ImplicitConversions._

import junctions._
import uncore.tilelink._
import cde.Parameters

class GatewayPLICIO extends Bundle {
  val valid = Bool(OUTPUT)
  val ready = Bool(INPUT)
  val complete = Bool(INPUT)
}

class LevelGateway extends Module {
  val io = new Bundle {
    val interrupt = Bool(INPUT)
    val plic = new GatewayPLICIO
  }

  val inFlight = Reg(init=Bool(false))
  when (io.interrupt && io.plic.ready) { inFlight := true }
  when (io.plic.complete) { inFlight := false }
  io.plic.valid := io.interrupt && !inFlight
}

case class PLICConfig(nHartsIn: Int, supervisor: Boolean, nDevices: Int, nPriorities: Int) {
  def contextsPerHart = if (supervisor) 2 else 1
  def nHarts = contextsPerHart * nHartsIn
  def context(i: Int, mode: Char) = mode match {
    case 'M' => i * contextsPerHart
    case 'S' => require(supervisor); i * contextsPerHart + 1
  }
  def claimAddr(i: Int, mode: Char) = hartBase + hartOffset(context(i, mode)) + claimOffset
  def threshAddr(i: Int, mode: Char) = hartBase + hartOffset(context(i, mode))
  def enableAddr(i: Int, mode: Char) = enableBase + enableOffset(context(i, mode))
  def size = hartBase + hartOffset(maxHarts)

  def maxDevices = 1023
  def maxHarts = 15872
  def pendingBase = 0x1000
  def enableBase = 0x2000
  def hartBase = 0x200000
  require(hartBase >= enableBase + enableOffset(maxHarts))

  def enableOffset(i: Int) = i * ((maxDevices+7)/8)
  def hartOffset(i: Int) = i * 0x1000
  def claimOffset = 4
  def priorityBytes = 4

  require(nDevices > 0 && nDevices <= maxDevices)
  require(nHarts > 0 && nHarts <= maxHarts)
  require(nPriorities >= 0 && nPriorities <= nDevices)
}

/** Platform-Level Interrupt Controller */
class PLIC(val cfg: PLICConfig)(implicit val p: Parameters) extends Module
    with HasTileLinkParameters
    with HasAddrMapParameters {
  val io = new Bundle {
    val devices = Vec(cfg.nDevices, new GatewayPLICIO).flip
    val harts = Vec(cfg.nHarts, Bool()).asOutput
    val tl = new ClientUncachedTileLinkIO().flip
  }

  val priority =
    if (cfg.nPriorities > 0) Reg(Vec(cfg.nDevices+1, UInt(width=log2Up(cfg.nPriorities+1))))
    else Wire(init=Vec.fill(cfg.nDevices+1)(UInt(1)))
  val threshold =
    if (cfg.nPriorities > 0) Reg(Vec(cfg.nHarts, UInt(width = log2Up(cfg.nPriorities+1))))
    else Wire(init=Vec.fill(cfg.nHarts)(UInt(0)))
  val pending = Reg(init=Vec.fill(cfg.nDevices+1){Bool(false)})
  val enables = Reg(Vec(cfg.nHarts, Vec(cfg.nDevices+1, Bool())))

  for ((p, g) <- pending.tail zip io.devices) {
    g.ready := !p
    g.complete := false
    when (g.valid) { p := true }
  }

  def findMax(x: Seq[UInt]): (UInt, UInt) = {
    if (x.length > 1) {
      val half = 1 << (log2Ceil(x.length) - 1)
      val lMax = findMax(x take half)
      val rMax = findMax(x drop half)
      val useLeft = lMax._1 >= rMax._1
      (Mux(useLeft, lMax._1, rMax._1), Mux(useLeft, lMax._2, UInt(half) + rMax._2))
    } else (x.head, UInt(0))
  }

  val maxDevs = Wire(Vec(cfg.nHarts, UInt(width = log2Up(pending.size))))
  for (hart <- 0 until cfg.nHarts) {
    val effectivePriority =
      for (((p, en), pri) <- (pending zip enables(hart) zip priority).tail)
        yield Cat(p && en, pri)
    val (maxPri, maxDev) = findMax((UInt(1) << priority(0).getWidth) +: effectivePriority)

    maxDevs(hart) := Reg(next = maxDev)
    io.harts(hart) := Reg(next = maxPri) > Cat(UInt(1), threshold(hart))
  }

  val acq = Queue(io.tl.acquire, 1)
  val read = acq.fire() && acq.bits.isBuiltInType(Acquire.getType)
  val write = acq.fire() && acq.bits.isBuiltInType(Acquire.putType)
  assert(!acq.fire() || read || write, "unsupported PLIC operation")
  val addr = acq.bits.full_addr()(log2Up(cfg.size)-1,0)

  val claimant =
    if (cfg.nHarts == 1) UInt(0)
    else (addr - cfg.hartBase)(log2Up(cfg.hartOffset(cfg.nHarts))-1,log2Up(cfg.hartOffset(1)))
  val hart = Wire(init = claimant)
  val myMaxDev = maxDevs(claimant)
  val myEnables = enables(hart)
  val rdata = Wire(init = UInt(0, tlDataBits))
  val masked_wdata = (acq.bits.data & acq.bits.full_wmask()) | (rdata & ~acq.bits.full_wmask())

  when (addr >= cfg.hartBase) {
    val word =
      if (tlDataBytes > cfg.claimOffset) UInt(0)
      else addr(log2Up(cfg.claimOffset),log2Up(tlDataBytes))
    rdata := Cat(myMaxDev, UInt(0, 8*cfg.priorityBytes-threshold(0).getWidth), threshold(claimant)) >> (word * tlDataBits)

    when (read && addr(log2Ceil(cfg.claimOffset))) {
      pending(myMaxDev) := false
    }
    when (write) {
      when (if (tlDataBytes > cfg.claimOffset) acq.bits.wmask()(cfg.claimOffset) else addr(log2Ceil(cfg.claimOffset))) {
        val dev = (acq.bits.data >> ((8 * cfg.claimOffset) % tlDataBits))(log2Up(pending.size)-1,0)
        when (myEnables(dev)) { io.devices(dev-1).complete := true }
      }.otherwise {
        if (cfg.nPriorities > 0) threshold(claimant) := acq.bits.data
      }
    }
  }.elsewhen (addr >= cfg.enableBase) {
    val enableHart =
      if (cfg.nHarts > 1) (addr - cfg.enableBase)(log2Up(cfg.enableOffset(cfg.nHarts))-1,log2Up(cfg.enableOffset(1)))
      else UInt(0)
    hart := enableHart
    val word =
      if (tlDataBits >= cfg.nHarts) UInt(0)
      else addr(log2Up((cfg.nHarts+7)/8)-1,log2Up(tlDataBytes))
    for (i <- 0 until cfg.nHarts by tlDataBits) {
      when (word === i/tlDataBits) {
        rdata := Cat(myEnables.slice(i, i + tlDataBits).reverse)
        for (j <- 0 until (tlDataBits min (myEnables.size - i))) {
          when (write) { enables(enableHart)(i+j) := masked_wdata(j) }
        }
      }
    }
  }.elsewhen (addr >= cfg.pendingBase) {
    val word =
      if (tlDataBytes >= pending.size) UInt(0)
      else addr(log2Up(pending.size)-1,log2Up(tlDataBytes))
    rdata := pending.asUInt >> (word * tlDataBits)
  }.otherwise {
    val regsPerBeat = tlDataBytes >> log2Up(cfg.priorityBytes)
    val word =
      if (regsPerBeat >= priority.size) UInt(0)
      else addr(log2Up(priority.size*cfg.priorityBytes)-1,log2Up(tlDataBytes))
    for (i <- 0 until priority.size by regsPerBeat) {
      when (word === i/regsPerBeat) {
        rdata := Cat(priority.slice(i, i + regsPerBeat).map(p => Cat(UInt(0, 8*cfg.priorityBytes-p.getWidth), p)).reverse)
        for (j <- 0 until (regsPerBeat min (priority.size - i))) {
          if (cfg.nPriorities > 0) when (write) { priority(i+j) := masked_wdata >> (j * 8 * cfg.priorityBytes) }
        }
      }
    }
  }

  priority(0) := 0
  pending(0) := false
  for (e <- enables)
    e(0) := false

  io.tl.grant.valid := acq.valid
  acq.ready := io.tl.grant.ready
  io.tl.grant.bits := Grant(
    is_builtin_type = Bool(true),
    g_type = acq.bits.getBuiltInGrantType(),
    client_xact_id = acq.bits.client_xact_id,
    manager_xact_id = UInt(0),
    addr_beat = UInt(0),
    data = rdata)
}
