// See LICENSE.SiFive for license details.

package freechips.rocketchip.tile

import chisel3._
import chisel3.util._

import org.chipsalliance.cde.config.Parameters
import org.chipsalliance.diplomacy.lazymodule._

import freechips.rocketchip.rocket.{DCacheErrors, ICacheErrors}
import freechips.rocketchip.diplomacy.{AddressSet}
import freechips.rocketchip.resources.{SimpleDevice}
import freechips.rocketchip.regmapper.{DescribedReg, RegField, RegFieldDesc, RegFieldGroup}
import freechips.rocketchip.tilelink.TLRegisterNode
import freechips.rocketchip.interrupts.{IntSourceNode, IntSourcePortSimple}
import freechips.rocketchip.util.property

trait BusErrors extends Bundle {
  def toErrorList: List[Option[(Valid[UInt], String, String)]]
}

class L1BusErrors(implicit p: Parameters) extends CoreBundle()(p) with BusErrors {
  val icache = new ICacheErrors
  val dcache = new DCacheErrors

  def toErrorList = List(None,
      Some((icache.bus, "IBUS", "Instruction cache TileLink bus error")),
      icache.correctable.map((_, "I_CORRECTABLE", "Instruction cache or ITIM correctable ECC error")),
      icache.uncorrectable.map((_, "I_UNCORRECTABLE", "ITIM uncorrectable ECC error")),
      None,
      Some((dcache.bus, "DBUS", "Load/Store/PTW TileLink bus error")),
      dcache.correctable.map((_, "D_CORRECTABLE", "Data cache correctable ECC error")),
      dcache.uncorrectable.map((_, "D_UNCORRECTABLE", "Data cache uncorrectable ECC error")))
}

case class BusErrorUnitParams(addr: BigInt, size: Int = 4096)

class BusErrorUnit[T <: BusErrors](t: => T, params: BusErrorUnitParams, beatBytes: Int)(implicit p: Parameters) extends LazyModule {
  val regWidth = 64
  val device = new SimpleDevice("bus-error-unit", Seq("sifive,buserror0"))
  val intNode = IntSourceNode(IntSourcePortSimple(resources = device.int))
  val node = TLRegisterNode(
    address   = Seq(AddressSet(params.addr, params.size-1)),
    device    = device,
    beatBytes = beatBytes)

  lazy val module = new Impl
  class Impl extends LazyModuleImp(this) {
    val io = IO(new Bundle {
      val errors = Flipped(t)
      val interrupt = Output(Bool())
    })

    val sources_and_desc = io.errors.toErrorList
    val sources = sources_and_desc.map(_.map(_._1))
    val sources_enums = sources_and_desc.zipWithIndex.flatMap{case (s, i) => s.map {e => (BigInt(i) -> (e._2, e._3))}}

    val causeWidth = log2Ceil(sources.lastIndexWhere(_.nonEmpty) + 1)
    val (cause, cause_desc) = DescribedReg(UInt(causeWidth.W),
      "cause", "Cause of error event", reset=Some(0.U(causeWidth.W)), volatile=true, enumerations=sources_enums.toMap)

    val (value, value_desc) = DescribedReg(UInt(sources.flatten.map(_.bits.getWidth).max.W),
      "value", "Physical address of error event", reset=None, volatile=true)
    require(value.getWidth <= regWidth)

    val enable = RegInit(VecInit(sources.map(_.nonEmpty.B)))
    val enable_desc =  sources.zipWithIndex.map { case (s, i) =>
      if (s.nonEmpty) RegFieldDesc(s"enable_$i", "", reset=Some(1))
      else RegFieldDesc.reserved
    }

    val global_interrupt = RegInit(VecInit.fill(sources.size)(false.B))
    val global_interrupt_desc = sources.zipWithIndex.map { case (s, i) =>
      if (s.nonEmpty) RegFieldDesc(s"plic_interrupt_$i", "", reset=Some(0))
      else RegFieldDesc.reserved
    }

    val accrued = RegInit(VecInit.fill(sources.size)(false.B))
    val accrued_desc = sources.zipWithIndex.map { case (s, i) =>
      if (s.nonEmpty) RegFieldDesc(s"accrued_$i", "", reset=Some(0), volatile = true)
      else RegFieldDesc.reserved
    }

    val local_interrupt = RegInit(VecInit.fill(sources.size)(false.B))
    val local_interrupt_desc = sources.zipWithIndex.map { case (s, i) =>
      if (s.nonEmpty) RegFieldDesc(s"local_interrupt_$i", "", reset=Some(0))
      else RegFieldDesc.reserved
    }

    val cause_wen = WireDefault(false.B)
    val new_cause = Wire(UInt(causeWidth.W))
    new_cause := DontCare
    val new_value = Wire(UInt(value.getWidth.W))
    new_value := DontCare
    for ((((s, en), acc), i) <- (sources zip enable zip accrued).zipWithIndex; if s.nonEmpty) {
      when (s.get.valid) {
        acc := true.B
        when (en) {
          cause_wen := true.B
          new_cause := i.asUInt
          new_value := s.get.bits
        }
        property.cover(en, s"BusErrorCause_$i", s"Core;;BusErrorCause $i covered")
      }
    }

    when (cause === 0.asUInt && cause_wen) {
      cause := new_cause
      value := new_value
    }

    val (int_out, _) = intNode.out(0)
    io.interrupt := (accrued.asUInt & local_interrupt.asUInt).orR
    int_out(0) := (accrued.asUInt & global_interrupt.asUInt).orR

    def reg(r: UInt, gn: String, d: RegFieldDesc) = RegFieldGroup(gn, None, RegField.bytes(r, (r.getWidth + 7)/8, Some(d)))
    def reg(v: Vec[Bool], gn: String, gd: String, d: Seq[RegFieldDesc]) =
      RegFieldGroup(gn, Some(gd), (v zip d).map {case (r, rd) => RegField(1, r, rd)})
    def numberRegs(x: Seq[Seq[RegField]]) = x.zipWithIndex.map {case (f, i) => (i * regWidth / 8) -> f }

    val omRegMap = node.regmap(numberRegs(Seq(
      reg(cause, "cause", cause_desc),
      reg(value, "value", value_desc),
      reg(enable, "enable", "Event enable mask", enable_desc),
      reg(global_interrupt, "plic_interrupt", "Platform-level interrupt enable mask", global_interrupt_desc),
      reg(accrued, "accrued", "Accrued event mask" ,accrued_desc),
      reg(local_interrupt,  "local_interrupt", "Hart-local interrupt-enable mask", local_interrupt_desc))):_*)

    // hardwire mask bits for unsupported sources to 0
    for ((s, i) <- sources.zipWithIndex; if s.isEmpty) {
      enable(i) := false.B
      global_interrupt(i) := false.B
      accrued(i) := false.B
      local_interrupt(i) := false.B
    }
  }
}
