//See LICENSE for license details.

package firesim.firesim

import scala.collection.mutable.{LinkedHashMap}

import chisel3._
import chisel3.experimental.{IO}

import freechips.rocketchip.prci._
import freechips.rocketchip.subsystem.{BaseSubsystem, SubsystemDriveAsyncClockGroupsKey}
import freechips.rocketchip.config.{Field, Config, Parameters}
import freechips.rocketchip.diplomacy.{LazyModule, LazyModuleImp, InModuleBody, ValName}
import freechips.rocketchip.util.{ResetCatchAndSync, RecordMap}

import midas.widgets.{Bridge, PeekPokeBridge, RationalClockBridge, RationalClock, ResetPulseBridge, ResetPulseBridgeParameters}

import chipyard._
import chipyard.harness._
import chipyard.iobinders._
import chipyard.clocking._

// Determines the number of times to instantiate the DUT in the harness.
// Subsumes legacy supernode support
case object NumNodes extends Field[Int](1)

class WithNumNodes(n: Int) extends Config((pname, site, here) => {
  case NumNodes => n
})

// Hacky: Set before each node is generated. Ideally we'd give IO binders
// accesses to the the Harness's parameters instance. We could then alter that.
object NodeIdx {
  private var idx = 0
  def increment(): Unit = {idx = idx + 1 }
  def apply(): Int = idx
}


/**
  * Specifies DUT clocks for the rational clock bridge
  *
  * @param allClocks Seq. of RationalClocks that want a clock
  *
  * @param baseClockName Name of domain that the allClocks is rational to
  *
  * @param baseFreqRequested Freq. for the reference domain in Hz
  */
case class BuildTopClockParameters(allClocks: Seq[RationalClock], baseClockName: String, baseFreqRequested: Double)

/**
  * Under FireSim's current multiclock implementation there can be only a
  * single clock bridge. This requires, therefore, that it  be instantiated in
  * the harness and reused across all supernode instances. This class attempts to
  * memoize its instantiation such that it can be referenced from within a ClockScheme function.
  */
class ClockBridgeInstantiator {
  private val _harnessClockMap: LinkedHashMap[String, (Double, Clock)] = LinkedHashMap.empty

  // Assumes that the supernode implementation results in duplicated clocks
  //   (i.e. only 1 set of clocks is generated for all BuildTop designs)
  private var _buildTopClockParams: Option[BuildTopClockParameters] = None
  private val _buildTopClockMap: LinkedHashMap[String, (RationalClock, Clock)] = LinkedHashMap.empty
  private var _buildTopClockRecord: Option[RecordMap[Clock]] = None

  /**
   * Request a clock at a particular frequency
   *
   * @param name An identifier for the associated clock domain
   *
   * @param freqRequested Freq. for the domain in Hz
   */
  def requestClock(name: String, freqRequested: Double): Clock = {
    val clkWire = Wire(new Clock)
    _harnessClockMap(name) = (freqRequested, clkWire)
    clkWire
  }

  /**
   * Get a RecordMap of clocks for a set of input RationalClocks. Used to drive
   * the design elaborated by buildtop
   *
   * @param clockMapParameters Defines the set of required clocks
   */
  def requestClockRecordMap(clockMapParameters: BuildTopClockParameters): RecordMap[Clock] = {
    if (_buildTopClockParams.isDefined) {
      require(_buildTopClockParams.get == clockMapParameters, "Must request same set of clocks on repeated invocations.")
    } else {
      val clockRecord = Wire(RecordMap(clockMapParameters.allClocks.map { c => (c.name, Clock()) }:_*))
      // Build up the mutable structures describing the clocks for the dut
      _buildTopClockParams = Some(clockMapParameters)
      _buildTopClockRecord = Some(clockRecord)

      for (clock <- clockMapParameters.allClocks) {
        val clockWire = Wire(new Clock)
        _buildTopClockMap(clock.name) = (clock, clockWire)
        clockRecord(clock.name).get := clockWire
      }
    }

    _buildTopClockRecord.get
  }

  /**
   * Connect all clocks requested to ClockBridge
   */
  def instantiateFireSimClockBridge: Unit = {
    require(_buildTopClockParams.isDefined, "Must have rational clocks to assign to")
    val BuildTopClockParameters(allClocks, refRatClockName, refRatClockFreq) = _buildTopClockParams.get
    require(_buildTopClockMap.exists(_._1 == refRatClockName),
      s"Provided base-clock name for rational clocks, ${refRatClockName}, doesn't match a name within specified rational clocks." +
      "Available clocks:\n " + _buildTopClockMap.map(_._1).mkString("\n "))

    // Simplify the RationalClocks ratio's
    val refRatClock = _buildTopClockMap.find(_._1 == refRatClockName).get._2._1
    val simpleRatClocks = _buildTopClockMap.map { t =>
      val ratClock = t._2._1
      ratClock.copy(
        multiplier = ratClock.multiplier * refRatClock.divisor,
        divisor = ratClock.divisor * refRatClock.multiplier).simplify
    }

    // Determine all the clock dividers (harness + rational clocks)
    //   Note: Requires that the BuildTop reference frequency is requested with proper freq.
    val refRatSinkParams = ClockSinkParameters(take=Some(ClockParameters(freqMHz=refRatClockFreq / (1000 * 1000))),name=Some(refRatClockName))
    val harSinkParams = _harnessClockMap.map { case (name, (freq, bundle)) =>
      ClockSinkParameters(take=Some(ClockParameters(freqMHz=freq / (1000 * 1000))),name=Some(name))
    }.toSeq
    val allSinkParams = harSinkParams :+ refRatSinkParams

    // Use PLL config to determine overall div's
    val pllConfig = new SimplePllConfiguration("firesimOverallClockBridge", allSinkParams)
    pllConfig.emitSummaries

    // Adjust all BuildTop RationalClocks with the div determined by the PLL
    val refRatDiv = pllConfig.sinkDividerMap(refRatSinkParams)
    val adjRefRatClocks = simpleRatClocks.map { clock =>
      clock.copy(divisor = clock.divisor * refRatDiv).simplify
    }

    // Convert harness clocks to RationalClocks
    val harRatClocks = harSinkParams.map { case ClockSinkParameters(_, _, _, _, clkParamsOpt, nameOpt) =>
      RationalClock(nameOpt.get, 1, pllConfig.referenceFreqMHz.toInt / clkParamsOpt.get.freqMHz.toInt)
    }

    val allAdjRatClks = adjRefRatClocks ++ harRatClocks

    // Removes clocks that have the same frequency before instantiating the
    //   clock bridge to avoid unnecessary BUFGCE use.
    val allDistinctRatClocks = allAdjRatClks.foldLeft(Seq(RationalClock(pllConfig.referenceSinkParams.name.get, 1, 1))) {
      case (list, candidate) => if (list.exists { clock => clock.equalFrequency(candidate) }) list else list :+ candidate
    }

    val clockBridge = Module(new RationalClockBridge(allDistinctRatClocks))
    val cbVecTuples = allDistinctRatClocks.zip(clockBridge.io.clocks)

    // Connect all clocks (harness + BuildTop clocks)
    for (clock <- allAdjRatClks) {
      val (_, cbClockField) = cbVecTuples.find(_._1.equalFrequency(clock)).get
      _buildTopClockMap.get(clock.name).map { case (_, clk) => clk := cbClockField }
      _harnessClockMap.get(clock.name).map { case (_, clk) => clk := cbClockField }
    }
  }
}

case object ClockBridgeInstantiatorKey extends Field[ClockBridgeInstantiator](new ClockBridgeInstantiator)
case object FireSimBaseClockNameKey extends Field[String]("implicit_clock")

class ClocksWithSinkParams(val params: Seq[ClockSinkParameters]) extends Bundle {
  val clocks = Vec(params.size, Clock())
}

class WithFireSimSimpleClocks extends OverrideLazyIOBinder({
  (system: HasChipyardPRCI) => {
    implicit val p = GetSystemParameters(system)
    // Figure out what provides this in the chipyard scheme
    implicit val valName = ValName("FireSimClocking")

    val implicitClockSinkNode = ClockSinkNode(Seq(ClockSinkParameters(name = Some("implicit_clock"))))
    system.connectImplicitClockSinkNode(implicitClockSinkNode)
    InModuleBody {
      val implicit_clock = implicitClockSinkNode.in.head._1.clock
      val implicit_reset = implicitClockSinkNode.in.head._1.reset
      system.asInstanceOf[BaseSubsystem].module match { case l: LazyModuleImp => {
        l.clock := implicit_clock
        l.reset := implicit_reset
      }}
    }

    val inputClockSource = ClockGroupSourceNode(Seq(ClockGroupSourceParameters()))
    system.allClockGroupsNode := inputClockSource

    InModuleBody {
      val (clockGroupBundle, clockGroupEdge) = inputClockSource.out.head
      val reset_io = IO(Input(AsyncReset())).suggestName("async_reset")

      val input_clocks = IO(Input(new ClocksWithSinkParams(clockGroupEdge.sink.members)))
        .suggestName("clocks")

      (clockGroupBundle.member.data zip input_clocks.clocks).foreach { case (clockBundle, inputClock) =>
        clockBundle.clock := inputClock
        clockBundle.reset := reset_io
      }

      (Seq(reset_io, input_clocks), Nil)
    }
  }
})

class WithFireSimHarnessClockBinder extends OverrideHarnessBinder({
  (system: HasChipyardPRCI, th: FireSim, ports: Seq[Data]) => {
    implicit val p = th.p
    ports.map ({
      case c: ClocksWithSinkParams => {
        val pllConfig = new SimplePllConfiguration("firesimBuildTopClockGenerator", c.params)
        pllConfig.emitSummaries
        th.setRefClockFreq(pllConfig.referenceFreqMHz)
        val rationalClockSpecs = for ((sinkP, division) <- pllConfig.sinkDividerMap) yield {
          RationalClock(sinkP.name.get, 1, division)
        }
        val input_clocks: RecordMap[Clock] = p(ClockBridgeInstantiatorKey).requestClockRecordMap(
          BuildTopClockParameters(
            rationalClockSpecs.toSeq,
            p(FireSimBaseClockNameKey),
            pllConfig.referenceFreqMHz * (1000 * 1000)))
        (c.clocks zip c.params) map ({ case (clock, param) =>
          clock := input_clocks(param.name.get).get
        })
      }
      case r: Reset => r := th.buildtopReset.asAsyncReset
    })
  }
})

class FireSim(implicit val p: Parameters) extends RawModule with HasHarnessSignalReferences {
  freechips.rocketchip.util.property.cover.setPropLib(new midas.passes.FireSimPropertyLibrary())

  val buildtopClock = Wire(Clock())
  val buildtopReset = WireInit(false.B)
  // The peek-poke bridge must still be instantiated even though it's
  // functionally unused. This will be removed in a future PR.
  val dummy = WireInit(false.B)
  val peekPokeBridge = PeekPokeBridge(buildtopClock, dummy)

  val resetBridge = Module(new ResetPulseBridge(ResetPulseBridgeParameters()))
  // In effect, the bridge counts the length of the reset in terms of this clock.
  resetBridge.io.clock := buildtopClock
  buildtopReset := resetBridge.io.reset
  // Ensures FireSim-synthesized assertions and instrumentation is disabled
  // while buildtopReset is asserted.  This ensures assertions do not fire at
  // time zero in the event their local reset is delayed (typically because it
  // has been pipelined)
  midas.targetutils.GlobalResetCondition(buildtopReset)

  def dutReset = { require(false, "dutReset should not be used in Firesim"); false.B }
  def success = { require(false, "success should not be used in Firesim"); false.B }

  // Instantiate multiple instances of the DUT to implement supernode
  for (i <- 0 until p(NumNodes)) {
    // It's not a RC bump without some hacks...
    // Copy the AsyncClockGroupsKey to generate a fresh node on each
    // instantiation of the dut, otherwise the initial instance will be
    // reused across each node
    import freechips.rocketchip.subsystem.AsyncClockGroupsKey
    val lazyModule = LazyModule(p(BuildTop)(p))
    val module = Module(lazyModule.module)

    lazyModule match { case d: HasIOBinders =>
      ApplyHarnessBinders(this, d.lazySystem, d.portMap)
    }
    NodeIdx.increment()
  }

  buildtopClock := p(ClockBridgeInstantiatorKey).requestClock("buildtop_reference_clock", getRefClockFreq * (1000 * 1000))

  p(ClockBridgeInstantiatorKey).instantiateFireSimClockBridge
}
