package chipyard.clocking

import chisel3._

import freechips.rocketchip.config.{Parameters}
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.prci._
import freechips.rocketchip.util.ElaborationArtefacts

import scala.collection.mutable
import scala.collection.immutable.ListMap

/**
  * TODO: figure out how much division is acceptable in our simulators and redefine this.
  */
object FrequencyUtils {
  /**
    * Adds up the squared error between the generated clocks (refClock / [integer] divider)
    * and the requested frequencies.
    *
    * @param refMHz The candidate reference clock
    * @param desiredFreqMHz A list of the requested output frequencies
    */
  def squaredError(refMHz: Double, desiredFreqMHz: List[Double], sum: Double = 0.0): Double = desiredFreqMHz match {
    case Nil => sum
    case desired :: xs =>
      val divider = Math.round(refMHz / desired)
      val termError = ((refMHz / divider) - desired) / desired
      squaredError(refMHz, xs, sum + termError * termError)
  }

  /**
    * Picks a candidate reference frequency by doing a brute-force search over
    * multiples of the fastest requested clock. Choose the smallest multiple that
    * has an RMS error (across all output frequencies) that is:
    * 1) zero or failing that,
    * 2) is within the relativeThreshold of the best or is less than the absoluteThreshold
    *
    * @param requestedOutputs The desired output frequencies in MHz
    * @param maximumAllowableFreqMHz The maximum allowable reference in MHz
    * @param relativeThreshold See above
    * @param absoluteThreshold See above
    */
  def computeReferenceAsMultipleOfFastestClock(
    requestedOutputs: Seq[ClockParameters],
    maximumAllowableFreqMHz: Double,
    relativeThreshold: Double = 1.10,
    absoluteThreshold: Double = 0.01): ClockParameters = {

    require(requestedOutputs.nonEmpty)
    require(!requestedOutputs.contains(0.0))
    val requestedFreqs = requestedOutputs.map(_.freqMHz)
    val fastestFreq = requestedFreqs.max
    require(fastestFreq <= maximumAllowableFreqMHz)

    val candidateFreqs =
      Seq.tabulate(Math.ceil(maximumAllowableFreqMHz / fastestFreq).toInt)(i => (i + 1) * fastestFreq)
    val errorTuples = candidateFreqs.map { f =>
      f -> Math.sqrt(squaredError(f, requestedFreqs.toList) / requestedFreqs.size)
    }
    val minError = errorTuples.map(_._2).min
    val viableFreqs = errorTuples.collect {
      case (f, error) if (error <= minError * relativeThreshold) || (minError > 0 && error < absoluteThreshold) => f
    }
    ClockParameters(viableFreqs.min)
  }
}

class SimplePllConfiguration(
    name: String,
    val sinks: Seq[ClockSinkParameters],
    maximumAllowableFreqMHz: Double = 16000.0 ) {
  val referenceFreqMHz = FrequencyUtils.computeReferenceAsMultipleOfFastestClock(
    sinks.flatMap(_.take),
    maximumAllowableFreqMHz).freqMHz
  val sinkDividerMap = ListMap((sinks.map({s => (s, Math.round(referenceFreqMHz / s.take.get.freqMHz).toInt) })):_*)

  private val preamble = s"""
    |${name} Frequency Summary
    |  Input Reference Frequency: ${referenceFreqMHz} MHz\n""".stripMargin
  private val outputSummaries = sinkDividerMap.map { case (sink, division) =>
      val requested = sink.take.get.freqMHz
      val actual = referenceFreqMHz / division.toDouble

    }

   val summaryString =  preamble + outputSummaries.mkString("\n")
   def emitSummaries(): Unit = {
     ElaborationArtefacts.add(s"${name}.freq-summary", summaryString)
     println(summaryString)
   }
   def referenceSinkParams(): ClockSinkParameters = sinkDividerMap.find(_._2 == 1).get._1
}

case class DividerOnlyClockGeneratorNode(pllName: String)(implicit valName: ValName)
  extends MixedNexusNode(ClockImp, ClockGroupImp)(
    dFn = { _ => ClockGroupSourceParameters() },
    uFn = { u =>
    require(u.size == 1)
    require(!u.head.members.contains(None),
      "All output clocks in group must set their take parameters. Use a ClockGroupDealiaser")
    ClockSinkParameters(
      name = Some(s"${pllName}_reference_input"),
      take = Some(ClockParameters(new SimplePllConfiguration(pllName, u.head.members).referenceFreqMHz))) }
  )

/**
  * Generates a digital-divider-only PLL model that verilator can simulate.
  * Inspects all take-specified frequencies in the output ClockGroup, calculates a
  * fast reference clock (roughly LCM(requested frequencies)) which is passed up the
  * diplomatic graph, and then generates dividers for each unique requested
  * frequency.
  *
  * Output resets are not synchronized to generated clocks and should be
  * synchronized by the user in a manner they see fit.
  *
  */

class DividerOnlyClockGenerator(pllName: String)(implicit p: Parameters, valName: ValName) extends LazyModule {
  val node = DividerOnlyClockGeneratorNode(pllName)

  lazy val module = new LazyRawModuleImp(this) {
    require(node.out.size == 1, "Idealized PLL expects to generate a single output clock group. Use a ClockGroupAggregator")
    val (refClock, ClockEdgeParameters(_, refSinkParam, _, _)) = node.in.head
    val (outClocks, ClockGroupEdgeParameters(_, outSinkParams,  _, _)) = node.out.head

    val referenceFreq = refSinkParam.take.get.freqMHz
    val pllConfig = new SimplePllConfiguration(pllName, outSinkParams.members)
    pllConfig.emitSummaries()

    val dividedClocks = mutable.HashMap[Int, Clock]()
    def instantiateDivider(div: Int): Clock = {
      val divider = Module(new ClockDividerN(div))
      divider.suggestName(s"ClockDivideBy${div}")
      divider.io.clk_in := refClock.clock
      dividedClocks(div) = divider.io.clk_out
      divider.io.clk_out
    }

    for (((sinkBName, sinkB), sinkP) <- outClocks.member.elements.zip(outSinkParams.members)) {
      val div = pllConfig.sinkDividerMap(sinkP)
      sinkB.clock := dividedClocks.getOrElse(div, instantiateDivider(div))
      // Reset handling and synchronization is expected to be handled by a downstream node
      sinkB.reset := refClock.reset
    }
  }
}

s"  Output clock ${sink.name.get}, requested: ${requested} MHz, actual: ${actual} MHz (division of ${division})"


package chipyard.config

import freechips.rocketchip.config.{Config}

// --------------
// Chipyard abstract ("base") configuration
// NOTE: This configuration is NOT INSTANTIABLE, as it defines a empty system with no tiles
//
// The default set of IOBinders instantiate IOcells and ChipTop IOs for digital IO bundles.
// The default set of HarnessBinders instantiate TestHarness hardware for interacting with ChipTop IOs
// --------------

class AbstractConfig extends Config(
  // The HarnessBinders control generation of hardware in the TestHarness
  new chipyard.harness.WithUARTAdapter ++                       // add UART adapter to display UART on stdout, if uart is present

  new chipyard.harness.WithTieOffL2FBusAXI ++                   // tie-off external AXI4 master, if present
  new chipyard.harness.WithTieOffCustomBootPin ++

  // The IOBinders instantiate ChipTop IOs to match desired digital IOs
  // IOCells are generated for "Chip-like" IOs, while simulation-only IOs are directly punched through
  new chipyard.iobinders.WithAXI4MemPunchthrough ++
  new chipyard.iobinders.WithAXI4MMIOPunchthrough ++
  new chipyard.iobinders.WithL2FBusAXI4Punchthrough ++
  new chipyard.iobinders.WithBlockDeviceIOPunchthrough ++
  new chipyard.iobinders.WithSerialTLIOCells ++
  new chipyard.iobinders.WithDebugIOCells ++
  new chipyard.iobinders.WithUARTIOCells ++
  new chipyard.iobinders.WithGPIOCells ++
  new chipyard.iobinders.WithUARTIOCells ++
  new chipyard.iobinders.WithSPIIOCells ++
  new chipyard.iobinders.WithTraceIOPunchthrough ++
  new chipyard.iobinders.WithExtInterruptIOCells ++
  new chipyard.iobinders.WithCustomBootPin ++

  new testchipip.WithDefaultSerialTL ++                          // use serialized tilelink port to external serialadapter/harnessRAM
  new chipyard.config.WithBootROM ++                             // use default bootrom
  new chipyard.config.WithUART ++                                // add a UART
  new chipyard.config.WithL2TLBs(1024) ++                        // use L2 TLBs
  new chipyard.config.WithNoSubsystemDrivenClocks ++             // drive the subsystem diplomatic clocks from ChipTop instead of using implicit clocks
  new chipyard.config.WithInheritBusFrequencyAssignments ++      // Unspecified clocks within a bus will receive the bus frequency if set
  new chipyard.config.WithPeripheryBusFrequencyAsDefault ++      // Unspecified frequencies with match the pbus frequency (which is always set)
  new chipyard.config.WithMemoryBusFrequency(100.0) ++           // Default 100 MHz mbus
  new chipyard.config.WithPeripheryBusFrequency(100.0) ++        // Default 100 MHz pbus
  new freechips.rocketchip.subsystem.WithJtagDTM ++              // set the debug module to expose a JTAG port
  new freechips.rocketchip.subsystem.WithNoMMIOPort ++           // no top-level MMIO master port (overrides default set in rocketchip)
  new freechips.rocketchip.subsystem.WithNoSlavePort ++          // no top-level MMIO slave port (overrides default set in rocketchip)
  new freechips.rocketchip.subsystem.WithBufferlessBroadcastHub ++ // bufferless (without L2 cache)
  new freechips.rocketchip.subsystem.WithNExtTopInterrupts(0) ++ // no external interrupts
  new chipyard.WithMulticlockCoherentBusTopology ++              // hierarchical buses including mbus+l2
  new freechips.rocketchip.system.BaseConfig)                    // "base" rocketchip system

  new chipyard.harness.WithBlackBoxSimMem ++                    // add SimDRAM DRAM model for axi4 backing memory, if axi4 mem is enabled
  new chipyard.harness.WithSimSerial ++                         // add external serial-adapter and RAM
  new chipyard.harness.WithSimDebug ++                          // add SimJTAG or SimDTM adapters if debug module is enabled
  new chipyard.harness.WithGPIOTiedOff ++                       // tie-off chiptop GPIOs, if GPIOs are present


package chipyard

import freechips.rocketchip.config.{Config}

// ---------------------
// BOOM Configs
// ---------------------

  new boom.common.WithNMediumBooms(1) ++                         // medium boom config
  new chipyard.config.AbstractConfig)

class LargeBoomConfig extends Config(
  new boom.common.WithNLargeBooms(1) ++                          // large boom config
  new chipyard.config.AbstractConfig)

class MegaBoomConfig extends Config(
  new boom.common.WithNMegaBooms(1) ++                           // mega boom config
  new chipyard.config.AbstractConfig)

class DualSmallBoomConfig extends Config(
  new boom.common.WithNSmallBooms(2) ++                          // 2 boom cores
  new chipyard.config.AbstractConfig)

class SmallBoomConfig extends Config(
  new boom.common.WithNSmallBooms(1) ++                          // small boom config
  new chipyard.config.AbstractConfig)


package chipyard

import freechips.rocketchip.config.{Config}
import freechips.rocketchip.diplomacy.{AsynchronousCrossing}

// --------------
// Rocket Configs
// --------------

class RocketConfig extends Config(
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++         // single rocket-core
  new chipyard.config.AbstractConfig)

class TinyRocketConfig extends Config(
  new chipyard.config.WithTLSerialLocation(
    freechips.rocketchip.subsystem.FBUS,
    freechips.rocketchip.subsystem.PBUS) ++                       // attach TL serial adapter to f/p busses
  new chipyard.WithMulticlockIncoherentBusTopology ++             // use incoherent bus topology

class dmiRocketConfig extends Config(
  new chipyard.harness.WithSerialAdapterTiedOff ++               // don't attach an external SimSerial
  new chipyard.config.WithDMIDTM ++                              // have debug module expose a clocked DMI port
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)
// DOC include end: DmiRocket

class LargeSPIFlashROMRocketConfig extends Config(
  new chipyard.harness.WithSimSPIFlashModel(true) ++        // add the SPI flash model in the harness (read-only)
  new chipyard.config.WithSPIFlash ++                       // add the SPI flash controller
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)

class SmallSPIFlashRocketConfig extends Config(
  new chipyard.harness.WithSimSPIFlashModel(false) ++       // add the SPI flash model in the harness (writeable)
  new chipyard.config.WithSPIFlash(0x100000) ++             // add the SPI flash controller (1 MiB)
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)

class SimAXIRocketConfig extends Config(
  new chipyard.harness.WithSimAXIMem ++                     // drive the master AXI4 memory with a SimAXIMem, a 1-cycle magic memory, instead of default SimDRAM
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)

class SimBlockDeviceRocketConfig extends Config(
  new chipyard.harness.WithSimBlockDevice ++                // drive block-device IOs with SimBlockDevice
  new testchipip.WithBlockDevice ++                         // add block-device module to peripherybus
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)

class BlockDeviceModelRocketConfig extends Config(
  new chipyard.harness.WithBlockDeviceModel ++              // drive block-device IOs with a BlockDeviceModel
  new testchipip.WithBlockDevice ++                         // add block-device module to periphery bus
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)

// DOC include start: GPIORocketConfig
class GPIORocketConfig extends Config(
  new chipyard.config.WithGPIO ++                           // add GPIOs to the peripherybus
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)
// DOC include end: GPIORocketConfig

class QuadRocketConfig extends Config(
  new freechips.rocketchip.subsystem.WithNBigCores(4) ++    // quad-core (4 RocketTiles)
  new chipyard.config.AbstractConfig)

class RV32RocketConfig extends Config(
  new freechips.rocketchip.subsystem.WithRV32 ++            // set RocketTiles to be 32-bit
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)

class GB1MemoryRocketConfig extends Config(
  new freechips.rocketchip.subsystem.WithExtMemSize((1<<30) * 1L) ++ // use 1GB simulated external memory
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)

// DOC include start: l1scratchpadrocket
class ScratchpadOnlyRocketConfig extends Config(
  new testchipip.WithSerialPBusMem ++
  new freechips.rocketchip.subsystem.WithNMemoryChannels(0) ++ // remove offchip mem port
  new freechips.rocketchip.subsystem.WithNBanks(0) ++
  new freechips.rocketchip.subsystem.WithNoMemPort ++
  new freechips.rocketchip.subsystem.WithScratchpadsOnly ++    // use rocket l1 DCache scratchpad as base phys mem
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)
// DOC include end: l1scratchpadrocket

class L1ScratchpadRocketConfig extends Config(
  new chipyard.config.WithRocketICacheScratchpad ++         // use rocket ICache scratchpad
  new chipyard.config.WithRocketDCacheScratchpad ++         // use rocket DCache scratchpad
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)

// DOC include start: mbusscratchpadrocket
class MbusScratchpadRocketConfig extends Config(
  new testchipip.WithBackingScratchpad ++                   // add mbus backing scratchpad
  new freechips.rocketchip.subsystem.WithNoMemPort ++       // remove offchip mem port
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)
// DOC include end: mbusscratchpadrocket

// DOC include start: RingSystemBusRocket
class RingSystemBusRocketConfig extends Config(
  new testchipip.WithRingSystemBus ++                       // Ring-topology system bus
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)
// DOC include end: RingSystemBusRocket

class MMIORocketConfig extends Config(
  new freechips.rocketchip.subsystem.WithDefaultMMIOPort ++  // add default external master port
  new freechips.rocketchip.subsystem.WithDefaultSlavePort ++ // add default external slave port
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)

class MulticlockRocketConfig extends Config(
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  // Frequency specifications
  new chipyard.config.WithTileFrequency(1600.0) ++       // Matches the maximum frequency of U540
  new chipyard.config.WithSystemBusFrequency(800.0) ++   // Ditto
  new chipyard.config.WithMemoryBusFrequency(1000.0) ++  // 2x the U540 freq (appropriate for a 128b Mbus)
  new chipyard.config.WithPeripheryBusFrequency(100) ++  // Retains the default pbus frequency
  new chipyard.config.WithSystemBusFrequencyAsDefault ++ // All unspecified clock frequencies, notably the implicit clock, will use the sbus freq (800 MHz)
  //  Crossing specifications
  new chipyard.config.WithCbusToPbusCrossingType(AsynchronousCrossing()) ++ // Add Async crossing between PBUS and CBUS
  new chipyard.config.WithSbusToMbusCrossingType(AsynchronousCrossing()) ++ // Add Async crossings between backside of L2 and MBUS
  new freechips.rocketchip.subsystem.WithRationalRocketTiles ++   // Add rational crossings between RocketTile and uncore
  new testchipip.WithAsynchronousSerialSlaveCrossing ++ // Add Async crossing between serial and MBUS. Its master-side is tied to the FBUS
  new chipyard.config.AbstractConfig)

class TestChipMulticlockRocketConfig extends Config(
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.WithTestChipBusFreqs ++
  new chipyard.config.AbstractConfig)

class LBWIFRocketConfig extends Config(
  new testchipip.WithSerialTLMem(isMainMemory=true) ++      // set lbwif memory base to DRAM_BASE, use as main memory
  new freechips.rocketchip.subsystem.WithNoMemPort ++       // remove AXI4 backing memory
  new freechips.rocketchip.subsystem.WithNBigCores(1) ++
  new chipyard.config.AbstractConfig)

// DOC include start: MulticlockAXIOverSerialConfig
class MulticlockAXIOverSerialConfig extends Config(
  new chipyard.config.WithSystemBusFrequencyAsDefault ++
  new chipyard.config.WithSystemBusFrequency(250) ++
  new chipyard.config.WithPeripheryBusFrequency(250) ++
  new chipyard.config.WithMemoryBusFrequency(250) ++
  new chipyard.config.WithFrontBusFrequency(50) ++
  new chipyard.config.WithTileFrequency(500, Some(1)) ++
  new chipyard.config.WithTileFrequency(250, Some(0)) ++

  new chipyard.config.WithFbusToSbusCrossingType(AsynchronousCrossing()) ++
  new testchipip.WithAsynchronousSerialSlaveCrossing ++
  new freechips.rocketchip.subsystem.WithAsynchronousRocketTiles(
    AsynchronousCrossing().depth,
    AsynchronousCrossing().sourceSync) ++

  new chipyard.harness.WithSimAXIMemOverSerialTL ++ // add SimDRAM DRAM model for axi4 backing memory over the SerDes link, if axi4 mem is enabled
  new chipyard.config.WithSerialTLBackingMemory ++ // remove axi4 mem port in favor of SerialTL memory

  new freechips.rocketchip.subsystem.WithNBigCores(2) ++
  new chipyard.config.AbstractConfig)
// DOC include end: MulticlockAXIOverSerialConfig

  new freechips.rocketchip.subsystem.WithNBanks(0) ++             // remove L2$
  new freechips.rocketchip.subsystem.WithNoMemPort ++             // remove backing memory
  new freechips.rocketchip.subsystem.With1TinyCore ++             // single tiny rocket-core
  new chipyard.config.AbstractConfig)


// See LICENSE for license details.
// Based on Rocket Chip's stage implementation

package chipyard.stage

import freechips.rocketchip.stage.ConfigsAnnotation
import firrtl.options.{HasShellOptions, ShellOption}

/** This hijacks the existing ConfigAnnotation to accept the legacy _-delimited format  */
private[stage] object UnderscoreDelimitedConfigsAnnotation extends HasShellOptions {
  override val options = Seq(
    new ShellOption[String](
      longOption = "legacy-configs",
      toAnnotationSeq = a => {
        val split = a.split(':')
        assert(split.length == 2)
        val packageName = split.head
        val configs     = split.last.split("_")

      },
      helpText = "A string of underscore-delimited configs (configs have decreasing precendence from left to right).",
      shortOption = Some("LC")
    )
  )
}

Seq(new ConfigsAnnotation(configs map { config => if (config contains ".") s"${config}" else s"${packageName}.${config}" } ))


// See LICENSE for license details.
// Based on Rocket Chip's stage implementation

package chipyard.stage

import firrtl.options.Shell

  parser.note("Chipyard Generator Options")
  Seq(
    UnderscoreDelimitedConfigsAnnotation
  )
    .foreach(_.addOptions(parser))
}


// See LICENSE for license details.
// Based on Rocket Chip's stage implementation

package chipyard.stage

import chisel3.stage.{ChiselCli, ChiselStage}
import firrtl.options.PhaseManager.PhaseDependency
import firrtl.options.{Phase, PreservesAll, Shell}
import firrtl.stage.FirrtlCli
import freechips.rocketchip.stage.RocketChipCli
import freechips.rocketchip.system.RocketChipStage

import firrtl.options.{Phase, PhaseManager, PreservesAll, Shell, Stage, StageError, StageMain, Dependency}
import firrtl.options.phases.DeletedWrapper

class ChipyardStage extends ChiselStage with PreservesAll[Phase] {
  override val shell = new Shell("chipyard") with ChipyardCli with RocketChipCli with ChiselCli with FirrtlCli
  override val targets: Seq[PhaseDependency] = Seq(
    Dependency[freechips.rocketchip.stage.phases.Checks],
    Dependency[freechips.rocketchip.stage.phases.TransformAnnotations],
    Dependency[freechips.rocketchip.stage.phases.PreElaboration],
    Dependency[chisel3.stage.phases.Checks],
    Dependency[chisel3.stage.phases.Elaborate],
    Dependency[freechips.rocketchip.stage.phases.GenerateROMs],
    Dependency[chisel3.stage.phases.AddImplicitOutputFile],
    Dependency[chisel3.stage.phases.AddImplicitOutputAnnotationFile],
    Dependency[chisel3.stage.phases.MaybeAspectPhase],
    Dependency[chisel3.stage.phases.Emitter],
    Dependency[chisel3.stage.phases.Convert],
    Dependency[freechips.rocketchip.stage.phases.GenerateFirrtlAnnos],
    Dependency[freechips.rocketchip.stage.phases.AddDefaultTests],
    Dependency[chipyard.stage.phases.AddDefaultTests],

    Dependency[chipyard.stage.phases.GenerateTestSuiteMakefrags],
    Dependency[freechips.rocketchip.stage.phases.GenerateArtefacts],


// See LICENSE for license details.
// Based on Rocket Chip's stage implementation

package chipyard.stage.phases

import scala.util.Try
import scala.collection.mutable

import chipsalliance.rocketchip.config.Parameters
import chisel3.stage.phases.Elaborate
import firrtl.AnnotationSeq
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.options.{Phase, PreservesAll, Dependency}
import firrtl.options.Viewer.view
import freechips.rocketchip.stage.RocketChipOptions
import freechips.rocketchip.stage.phases.{RocketTestSuiteAnnotation}
import freechips.rocketchip.system.{RocketTestSuite, TestGeneration}
import freechips.rocketchip.subsystem.{TilesLocated, InSubsystem}
import freechips.rocketchip.util.HasRocketChipStageUtils
import freechips.rocketchip.tile.XLen

import chipyard.TestSuiteHelper
import chipyard.TestSuitesKey

class AddDefaultTests extends Phase with PreservesAll[Phase] with HasRocketChipStageUtils {
  // Make sure we run both after RocketChip's version of this phase, and Rocket Chip's annotation emission phase
  // because the RocketTestSuiteAnnotation is not serializable (but is not marked as such).
  override val prerequisites = Seq(
    Dependency[freechips.rocketchip.stage.phases.GenerateFirrtlAnnos],
    Dependency[freechips.rocketchip.stage.phases.AddDefaultTests])
  override val dependents = Seq(Dependency[freechips.rocketchip.stage.phases.GenerateTestSuiteMakefrags])

  private def addTestSuiteAnnotations(implicit p: Parameters): Seq[Annotation] = {
    val annotations = mutable.ArrayBuffer[Annotation]()
    val suiteHelper = new TestSuiteHelper
    // Use Xlen as a proxy for detecting if we are a processor-like target
    // The underlying test suites expect this field to be defined
    val tileParams = p(TilesLocated(InSubsystem)) map (tp => tp.tileParams)

    val (testSuiteAnnos, oAnnos) = annotations.partition {
      case RocketTestSuiteAnnotation(_)  => true
      case o => false
    }
    implicit val p = getConfig(view[RocketChipOptions](annotations).configNames.get).toInstance
    addTestSuiteAnnotations ++ oAnnos
  }
}

    if (p.lift(XLen).nonEmpty)
      // If a custom test suite is set up, use the custom test suite
      annotations += CustomMakefragSnippet(p(TestSuitesKey).apply(tileParams, suiteHelper, p))

    RocketTestSuiteAnnotation(suiteHelper.suites.values.toSeq) +: annotations


// See LICENSE for license details.
// Based on Rocket Chip's stage implementation

package chipyard.stage.phases

import scala.collection.mutable

import firrtl.AnnotationSeq
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.options.{Phase, PreservesAll, StageOptions, Unserializable, Dependency}
import firrtl.options.Viewer.view
import freechips.rocketchip.stage.RocketChipOptions
import freechips.rocketchip.stage.phases.{RocketTestSuiteAnnotation}
import freechips.rocketchip.system.TestGeneration
import freechips.rocketchip.util.HasRocketChipStageUtils

trait MakefragSnippet { self: Annotation =>
  def toMakefrag: String
}

/** Generates a make script to run tests in [[RocketTestSuiteAnnotation]]. */
class GenerateTestSuiteMakefrags extends Phase with PreservesAll[Phase] with HasRocketChipStageUtils {

  // Our annotations tend not to be serializable, but are not marked as such.
  override val prerequisites = Seq(Dependency[freechips.rocketchip.stage.phases.GenerateFirrtlAnnos],
                                   Dependency[chipyard.stage.phases.AddDefaultTests])

  override def transform(annotations: AnnotationSeq): AnnotationSeq = {
    val targetDir = view[StageOptions](annotations).targetDir
    val fileName = s"${view[RocketChipOptions](annotations).longName.get}.d"

    val makefragBuilder = new mutable.StringBuilder()
    val outputAnnotations = annotations.flatMap {
      case RocketTestSuiteAnnotation(tests) =>
        // Unfortunately the gen method of TestGeneration is rocketchip package
        // private, so we either have to copy code in or use the stateful form
        TestGeneration.addSuites(tests)
        None
      case a: MakefragSnippet =>
        makefragBuilder :+ ("\n" + a.toMakefrag)
        None
      case a => Some(a)
    }
    writeOutputFile(targetDir, fileName, TestGeneration.generateMakeFrag ++ makefragBuilder.toString)
    outputAnnotations
  }
}

case class CustomMakefragSnippet(val toMakefrag: String) extends NoTargetAnnotation with MakefragSnippet with Unserializable


package chipyard.unittest

import chisel3._
import freechips.rocketchip.config.Parameters

class TestHarness(implicit val p: Parameters) extends Module {
  val io = IO(new Bundle { val success = Output(Bool()) })
  io.success := Module(new UnitTestSuite).io.finished
}


package chipyard.unittest

import freechips.rocketchip.config.Parameters
import freechips.rocketchip.util.{ElaborationArtefacts, PlusArgArtefacts}

class UnitTestSuite(implicit p: Parameters) extends freechips.rocketchip.unittest.UnitTestSuite {
  ElaborationArtefacts.add("plusArgs", PlusArgArtefacts.serialize_cHeader)
}


//See LICENSE for license details.
package chipyard.clocking

import freechips.rocketchip.prci._

class SimplePllConfigurationSpec extends org.scalatest.flatspec.AnyFlatSpec {

    def genConf(freqMHz: Iterable[Double]): SimplePllConfiguration = new SimplePllConfiguration(
      "testPLL",
      freqMHz.map({ f => ClockSinkParameters(
        name = Some(s"desiredFreq_$f"),
        take = Some(ClockParameters(f))) }).toSeq,
      maximumAllowableFreqMHz = 16000.0)

    def trySuccessfulConf(requestedFreqs: Seq[Double], expected: Double): Unit = {
      val freqStr = requestedFreqs.mkString(", ")

        val conf = genConf(requestedFreqs)
        conf.emitSummaries
        assert(expected == conf.referenceFreqMHz)
      }
    }

    trySuccessfulConf(Seq(3200.0, 1600.0, 1000.0, 100.0), 16000.0)
    trySuccessfulConf(Seq(3200.0, 1600.0), 3200.0)
    trySuccessfulConf(Seq(3200.0, 1066.7), 3200.0)
    trySuccessfulConf(Seq(100, 50, 6.67), 100)
    trySuccessfulConf(Seq(1, 2, 3, 5, 7, 11, 13).map(_ * 10.0), 1560.0)
}

it should s"select a reference of ${expected} MHz for ${freqStr} MHz" in {


// See LICENSE.Berkeley for license details.
// See LICENSE.SiFive for license details.

package junctions
import chisel3._
import chisel3.util._

object NastiConstants {
  // These are all fixed by the standard:
  val LenBits = 8
  val SizeBits = 3
  val BurstBits = 2
  val LockBits = 1
  val CacheBits = 4
  val ProtBits = 3
  val QosBits = 4
  val RespBits = 2

  def CacheReadAllocate = 8.U(CacheBits.W)
  def CacheWriteAllocate = 4.U(CacheBits.W)
  def CacheModifiable = 2.U(CacheBits.W)
  def CacheBufferable = 1.U(CacheBits.W)

  def ProtPrivileged = 1.U(ProtBits.W)
  def ProtInsecure = 2.U(ProtBits.W)
  def ProtInstruction = 4.U(ProtBits.W)

  def BurstFixed = 0.U(BurstBits.W)
  def BurstIncr = 1.U(BurstBits.W)
  def BurstWrap = 2.U(BurstBits.W)

  def RespExOkay = 1.U(RespBits.W)
  def RespSlvErr = 2.U(RespBits.W)
  def RespDevErr = 3.U(RespBits.W)
}

case class NastiBundleParameters(
  addrBits: Int,
  dataBits: Int,
  idBits:   Int) {
  require(dataBits >= 8, s"AXI4 data bits must be >= 8 (got $dataBits)")
  require(addrBits >= 1, s"AXI4 addr bits must be >= 1 (got $addrBits)")
  require(idBits >= 1, s"AXI4 id bits must be >= 1 (got $idBits)")
  require(isPow2(dataBits), s"AXI4 data bits must be pow2 (got $dataBits)")
}

/** aka the AW/AR channel */
class NastiAddressBundle(params: NastiBundleParameters) extends Bundle {
  val id = UInt(params.idBits.W)
  val addr = UInt(params.addrBits.W)
  val len = UInt(NastiConstants.LenBits.W) // number of beats - 1
  val size = UInt(NastiConstants.SizeBits.W) // bytes in beat = 2^size
  val burst = UInt(NastiConstants.BurstBits.W)
  val lock = UInt(NastiConstants.LockBits.W)
  val cache = UInt(NastiConstants.CacheBits.W)
  val prot = UInt(NastiConstants.ProtBits.W)
  val qos = UInt(NastiConstants.QosBits.W) // 0=no QoS, bigger = higher priority
}

object NastiAddressBundle {
  def apply(params: NastiBundleParameters)(id: UInt, addr: UInt, size: UInt, len: UInt = 0.U): NastiAddressBundle = {
    val aw = Wire(new NastiAddressBundle(params))
    aw.id := id
    aw.addr := addr
    aw.len := len
    aw.size := size
    aw.burst := NastiConstants.BurstIncr
    aw.lock := false.B
    aw.cache := 0.U
    aw.prot := 0.U
    aw.qos := 0.U
    aw
  }
}

/** aka the W-channel */
class NastiWriteDataBundle(params: NastiBundleParameters) extends Bundle {
  // id removed
  val data = UInt(params.dataBits.W)
  val strb = UInt((params.dataBits / 8).W)
  val last = Bool()
}

object NastiWriteDataBundle {
  def apply(
    params: NastiBundleParameters
  )(data:   UInt,
    strb:   Option[UInt] = None,
    last:   Bool = true.B
  ): NastiWriteDataBundle = {
    val w = Wire(new NastiWriteDataBundle(params))
    w.strb := strb.getOrElse(Fill(params.dataBits / 8, 1.U))
    w.data := data
    w.last := last
    w
  }
}

/** aka the R-channel */
class NastiReadDataBundle(params: NastiBundleParameters) extends Bundle {
  val id = UInt(params.idBits.W)
  val data = UInt(params.dataBits.W)
  val resp = UInt(NastiConstants.RespBits.W)
  val last = Bool()
}

object NastiReadDataBundle {
  def apply(
    params: NastiBundleParameters
  )(id:     UInt,
    data:   UInt,
    last:   Bool = true.B,
    resp:   UInt = 0.U
  ): NastiReadDataBundle = {
    val r = Wire(new NastiReadDataBundle(params))
    r.id := id
    r.data := data
    r.last := last
    r.resp := resp
    r
  }
}

/** aka the B-channel */
class NastiWriteResponseBundle(params: NastiBundleParameters) extends Bundle {
  val id = UInt(params.idBits.W)
  val resp = UInt(NastiConstants.RespBits.W)
}

object NastiWriteResponseBundle {
  def apply(params: NastiBundleParameters)(id: UInt, resp: UInt = 0.U): NastiWriteResponseBundle = {
    val b = Wire(new NastiWriteResponseBundle(params))
    b.id := id
    b.resp := resp
    b
  }
}

class NastiBundle(params: NastiBundleParameters) extends Bundle {
  val aw = Decoupled(new NastiAddressBundle(params))
  val w = Decoupled(new NastiWriteDataBundle(params))
  val b = Flipped(Decoupled(new NastiWriteResponseBundle(params)))
  val ar = Decoupled(new NastiAddressBundle(params))
  val r = Flipped(Decoupled(new NastiReadDataBundle(params)))
}

class NastiArbiterIO(params: NastiBundleParameters, arbN: Int) extends Bundle {
  val master = Flipped(Vec(arbN, new NastiBundle(params)))
  val slave = new NastiBundle(params)
}

/** Arbitrate among arbN masters requesting to a single slave */
class NastiArbiter(params: NastiBundleParameters, val arbN: Int) extends Module {
  val io = new NastiArbiterIO(params, arbN)

  if (arbN > 1) {
    val arbIdBits = log2Up(arbN)

    val ar_arb = Module(new RRArbiter(new NastiAddressBundle(params), arbN))
    val aw_arb = Module(new RRArbiter(new NastiAddressBundle(params), arbN))

    val slave_r_arb_id = io.slave.r.bits.id(arbIdBits - 1, 0)
    val slave_b_arb_id = io.slave.b.bits.id(arbIdBits - 1, 0)

    val w_chosen = Reg(UInt(arbIdBits.W))
    val w_done = RegInit(true.B)

    when(aw_arb.io.out.fire) {
      w_chosen := aw_arb.io.chosen
      w_done := false.B
    }

    when(io.slave.w.fire && io.slave.w.bits.last) {
      w_done := true.B
    }

    for (i <- 0 until arbN) {
      val m_ar = io.master(i).ar
      val m_aw = io.master(i).aw
      val m_r = io.master(i).r
      val m_b = io.master(i).b
      val a_ar = ar_arb.io.in(i)
      val a_aw = aw_arb.io.in(i)
      val m_w = io.master(i).w

      a_ar <> m_ar
      a_aw <> m_aw

      m_r.valid := io.slave.r.valid && slave_r_arb_id === i.U
      m_r.bits := io.slave.r.bits
      m_r.bits.id := io.slave.r.bits.id >> arbIdBits.U

      m_b.valid := io.slave.b.valid && slave_b_arb_id === i.U
      m_b.bits := io.slave.b.bits
      m_b.bits.id := io.slave.b.bits.id >> arbIdBits.U

      m_w.ready := io.slave.w.ready && w_chosen === i.U && !w_done
    }

    io.slave.r.ready := io.master(slave_r_arb_id).r.ready
    io.slave.b.ready := io.master(slave_b_arb_id).b.ready

    io.slave.w.bits := io.master(w_chosen).w.bits
    io.slave.w.valid := io.master(w_chosen).w.valid && !w_done

    io.slave.ar <> ar_arb.io.out

    io.slave.aw.bits <> aw_arb.io.out.bits
    io.slave.aw.valid := aw_arb.io.out.valid && w_done
    aw_arb.io.out.ready := io.slave.aw.ready && w_done

  } else { io.slave <> io.master.head }
}


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.util._

object Alu {
  val ALU_ADD = 0.U(4.W)
  val ALU_SUB = 1.U(4.W)
  val ALU_AND = 2.U(4.W)
  val ALU_OR = 3.U(4.W)
  val ALU_XOR = 4.U(4.W)
  val ALU_SLT = 5.U(4.W)
  val ALU_SLL = 6.U(4.W)
  val ALU_SLTU = 7.U(4.W)
  val ALU_SRL = 8.U(4.W)
  val ALU_SRA = 9.U(4.W)
  val ALU_COPY_A = 10.U(4.W)
  val ALU_COPY_B = 11.U(4.W)
  val ALU_XXX = 15.U(4.W)
}

class AluIO(width: Int) extends Bundle {
  val A = Input(UInt(width.W))
  val B = Input(UInt(width.W))
  val alu_op = Input(UInt(4.W))
  val out = Output(UInt(width.W))
  val sum = Output(UInt(width.W))
}

import mini.Alu._

trait Alu extends Module {
  def width: Int
  val io: AluIO
}

class AluSimple(val width: Int) extends Alu {

  io.out := MuxLookup(io.alu_op, io.B)(
    Seq(
      ALU_ADD -> (io.A + io.B),
      ALU_SUB -> (io.A - io.B),
      ALU_SRA -> (io.A.asSInt >> shamt).asUInt,
      ALU_SRL -> (io.A >> shamt),
      ALU_SLL -> (io.A << shamt),
      ALU_SLT -> (io.A.asSInt < io.B.asSInt),
      ALU_SLTU -> (io.A < io.B),
      ALU_AND -> (io.A & io.B),
      ALU_OR -> (io.A | io.B),
      ALU_XOR -> (io.A ^ io.B),
      ALU_COPY_A -> io.A
    )
  )

  io.sum := io.A + Mux(io.alu_op(0), -io.B, io.B)
}

class AluArea(val width: Int) extends Alu {
  val io = IO(new AluIO(width))
  val sum = io.A + Mux(io.alu_op(0), -io.B, io.B)
  val cmp =
    Mux(io.A(width - 1) === io.B(width - 1), sum(width - 1), Mux(io.alu_op(1), io.B(width - 1), io.A(width - 1)))
  val shamt = io.B(4, 0).asUInt
  val shin = Mux(io.alu_op(3), io.A, Reverse(io.A))
  val shiftr = (Cat(io.alu_op(0) && shin(width - 1), shin).asSInt >> shamt)(width - 1, 0)
  val shiftl = Reverse(shiftr)

  val out =
    Mux(
      io.alu_op === ALU_ADD || io.alu_op === ALU_SUB,
      sum,
      Mux(
        io.alu_op === ALU_SLT || io.alu_op === ALU_SLTU,
        cmp,
        Mux(
          io.alu_op === ALU_SRA || io.alu_op === ALU_SRL,
          shiftr,
          Mux(
            io.alu_op === ALU_SLL,
            shiftl,
            Mux(
              io.alu_op === ALU_AND,
              io.A & io.B,
              Mux(
                io.alu_op === ALU_OR,
                io.A | io.B,
                Mux(io.alu_op === ALU_XOR, io.A ^ io.B, Mux(io.alu_op === ALU_COPY_A, io.A, io.B))
              )
            )
          )
        )
      )
    )

  io.out := out
  io.sum := sum
}


// See LICENSE for license details.

package mini

import chisel3._
import mini.Control._

class BrCondIO(xlen: Int) extends Bundle {
  val rs1 = Input(UInt(xlen.W))
  val rs2 = Input(UInt(xlen.W))
  val br_type = Input(UInt(3.W))
  val taken = Output(Bool())
}

trait BrCond extends Module {
  def xlen: Int
  val io: BrCondIO
}

class BrCondSimple(val xlen: Int) extends BrCond {
  val io = IO(new BrCondIO(xlen))
  val eq = io.rs1 === io.rs2
  val neq = !eq
  val lt = io.rs1.asSInt < io.rs2.asSInt
  val ge = !lt
  val ltu = io.rs1 < io.rs2
  val geu = !ltu
  io.taken :=
    ((io.br_type === BR_EQ) && eq) ||
      ((io.br_type === BR_NE) && neq) ||
      ((io.br_type === BR_LT) && lt) ||
      ((io.br_type === BR_GE) && ge) ||
      ((io.br_type === BR_LTU) && ltu) ||
      ((io.br_type === BR_GEU) && geu)
}

class BrCondArea(val xlen: Int) extends BrCond {
  val io = IO(new BrCondIO(xlen))
  val diff = io.rs1 - io.rs2
  val neq = diff.orR
  val eq = !neq
  val isSameSign = io.rs1(xlen - 1) === io.rs2(xlen - 1)

  val lt = Mux(isSameSign, diff(xlen - 1), io.rs1(xlen - 1))
  val ltu = Mux(isSameSign, diff(xlen - 1), io.rs2(xlen - 1))
  val ge = !lt


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.util._

object CSR {
  val N = 0.U(3.W)
  val W = 1.U(3.W)
  val S = 2.U(3.W)
  val C = 3.U(3.W)
  val P = 4.U(3.W)

  // Supports machine & user modes
  val PRV_U = 0x0.U(2.W)
  val PRV_M = 0x3.U(2.W)

  // User-level CSR addrs
  val cycle = 0xc00.U(12.W)
  val time = 0xc01.U(12.W)
  val instret = 0xc02.U(12.W)
  val cycleh = 0xc80.U(12.W)
  val timeh = 0xc81.U(12.W)
  val instreth = 0xc82.U(12.W)

  // Supervisor-level CSR addrs
  val cyclew = 0x900.U(12.W)
  val timew = 0x901.U(12.W)
  val instretw = 0x902.U(12.W)
  val cyclehw = 0x980.U(12.W)
  val timehw = 0x981.U(12.W)
  val instrethw = 0x982.U(12.W)

  // Machine-level CSR addrs
  // Machine Information Registers
  val mcpuid = 0xf00.U(12.W)
  val mimpid = 0xf01.U(12.W)
  val mhartid = 0xf10.U(12.W)
  // Machine Trap Setup
  val mstatus = 0x300.U(12.W)
  val mtvec = 0x301.U(12.W)
  val mtdeleg = 0x302.U(12.W)
  val mie = 0x304.U(12.W)
  val mtimecmp = 0x321.U(12.W)
  // Machine Timers and Counters
  val mtime = 0x701.U(12.W)
  val mtimeh = 0x741.U(12.W)
  // Machine Trap Handling
  val mscratch = 0x340.U(12.W)
  val mepc = 0x341.U(12.W)
  val mcause = 0x342.U(12.W)
  val mbadaddr = 0x343.U(12.W)
  val mip = 0x344.U(12.W)
  // Machine HITF
  val mtohost = 0x780.U(12.W)
  val mfromhost = 0x781.U(12.W)

  val regs = List(
    cycle,
    time,
    instret,
    cycleh,
    timeh,
    instreth,
    cyclew,
    timew,
    instretw,
    cyclehw,
    timehw,
    instrethw,
    mcpuid,
    mimpid,
    mhartid,
    mtvec,
    mtdeleg,
    mie,
    mtimecmp,
    mtime,
    mtimeh,
    mscratch,
    mepc,
    mcause,
    mbadaddr,
    mip,
    mtohost,
    mfromhost,
    mstatus
  )
}

object Cause {
  val InstAddrMisaligned = 0x0.U
  val IllegalInst = 0x2.U
  val Breakpoint = 0x3.U
  val LoadAddrMisaligned = 0x4.U
  val StoreAddrMisaligned = 0x6.U
  val Ecall = 0x8.U
}

class CSRIO(xlen: Int) extends Bundle {
  val stall = Input(Bool())
  val cmd = Input(UInt(3.W))
  val in = Input(UInt(xlen.W))
  val out = Output(UInt(xlen.W))
  // Excpetion
  val pc = Input(UInt(xlen.W))
  val addr = Input(UInt(xlen.W))
  val inst = Input(UInt(xlen.W))
  val illegal = Input(Bool())
  val st_type = Input(UInt(2.W))
  val ld_type = Input(UInt(3.W))
  val pc_check = Input(Bool())
  val expt = Output(Bool())
  val evec = Output(UInt(xlen.W))
  val epc = Output(UInt(xlen.W))
  // HTIF
  val host = new HostIO(xlen)
}

class CSR(val xlen: Int) extends Module {
  val io = IO(new CSRIO(xlen))

  val csr_addr = io.inst(31, 20)
  val rs1_addr = io.inst(19, 15)

  // user counters
  val time = RegInit(0.U(xlen.W))
  val timeh = RegInit(0.U(xlen.W))
  val cycle = RegInit(0.U(xlen.W))
  val cycleh = RegInit(0.U(xlen.W))
  val instret = RegInit(0.U(xlen.W))
  val instreth = RegInit(0.U(xlen.W))

  val mcpuid = Cat(
    0.U(2.W) /* RV32I */,
    0.U((xlen - 28).W),
    (1 << ('I' - 'A') /* Base ISA */ |
      1 << ('U' - 'A') /* User Mode */ ).U(26.W)
  )
  val mimpid = 0.U(xlen.W) // not implemented
  val mhartid = 0.U(xlen.W) // only one hart

  // interrupt enable stack
  val PRV = RegInit(CSR.PRV_M)
  val PRV1 = RegInit(CSR.PRV_M)
  val PRV2 = 0.U(2.W)
  val PRV3 = 0.U(2.W)
  val IE = RegInit(false.B)
  val IE1 = RegInit(false.B)
  val IE2 = false.B
  val IE3 = false.B
  // virtualization management field
  val VM = 0.U(5.W)
  // memory privilege
  val MPRV = false.B
  // extention context status
  val XS = 0.U(2.W)
  val FS = 0.U(2.W)
  val SD = 0.U(1.W)
  val mstatus = Cat(SD, 0.U((xlen - 23).W), VM, MPRV, XS, FS, PRV3, IE3, PRV2, IE2, PRV1, IE1, PRV, IE)
  val mtvec = Consts.PC_EVEC.U(xlen.W)
  val mtdeleg = 0x0.U(xlen.W)

  // interrupt registers
  val MTIP = RegInit(false.B)
  val HTIP = false.B
  val STIP = false.B
  val MTIE = RegInit(false.B)
  val HTIE = false.B
  val STIE = false.B
  val MSIP = RegInit(false.B)
  val HSIP = false.B
  val SSIP = false.B
  val MSIE = RegInit(false.B)
  val HSIE = false.B
  val SSIE = false.B
  val mip = Cat(0.U((xlen - 8).W), MTIP, HTIP, STIP, false.B, MSIP, HSIP, SSIP, false.B)
  val mie = Cat(0.U((xlen - 8).W), MTIE, HTIE, STIE, false.B, MSIE, HSIE, SSIE, false.B)

  val mtimecmp = Reg(UInt(xlen.W))

  val mscratch = Reg(UInt(xlen.W))

  val mepc = Reg(UInt(xlen.W))
  val mcause = Reg(UInt(xlen.W))
  val mbadaddr = Reg(UInt(xlen.W))

  val mtohost = RegInit(0.U(xlen.W))
  val mfromhost = Reg(UInt(xlen.W))
  io.host.tohost := mtohost
  when(io.host.fromhost.valid) {
    mfromhost := io.host.fromhost.bits
  }

  val csrFile = Seq(
    BitPat(CSR.cycle) -> cycle,
    BitPat(CSR.time) -> time,
    BitPat(CSR.instret) -> instret,
    BitPat(CSR.cycleh) -> cycleh,
    BitPat(CSR.timeh) -> timeh,
    BitPat(CSR.instreth) -> instreth,
    BitPat(CSR.cyclew) -> cycle,
    BitPat(CSR.timew) -> time,
    BitPat(CSR.instretw) -> instret,
    BitPat(CSR.cyclehw) -> cycleh,
    BitPat(CSR.timehw) -> timeh,
    BitPat(CSR.instrethw) -> instreth,
    BitPat(CSR.mcpuid) -> mcpuid,
    BitPat(CSR.mimpid) -> mimpid,
    BitPat(CSR.mhartid) -> mhartid,
    BitPat(CSR.mtvec) -> mtvec,
    BitPat(CSR.mtdeleg) -> mtdeleg,
    BitPat(CSR.mie) -> mie,
    BitPat(CSR.mtimecmp) -> mtimecmp,
    BitPat(CSR.mtime) -> time,
    BitPat(CSR.mtimeh) -> timeh,
    BitPat(CSR.mscratch) -> mscratch,
    BitPat(CSR.mepc) -> mepc,
    BitPat(CSR.mcause) -> mcause,
    BitPat(CSR.mbadaddr) -> mbadaddr,
    BitPat(CSR.mip) -> mip,
    BitPat(CSR.mtohost) -> mtohost,
    BitPat(CSR.mfromhost) -> mfromhost,
    BitPat(CSR.mstatus) -> mstatus
  )

  io.out := Lookup(csr_addr, 0.U, csrFile).asUInt

  val privValid = csr_addr(9, 8) <= PRV
  val privInst = io.cmd === CSR.P
  val isEcall = privInst && !csr_addr(0) && !csr_addr(8)
  val isEbreak = privInst && csr_addr(0) && !csr_addr(8)
  val isEret = privInst && !csr_addr(0) && csr_addr(8)
  val csrValid = csrFile.map(_._1 === csr_addr).reduce(_ || _)
  val csrRO = csr_addr(11, 10).andR || csr_addr === CSR.mtvec || csr_addr === CSR.mtdeleg
  val wen = io.cmd === CSR.W || io.cmd(1) && rs1_addr.orR
  val wdata = MuxLookup(io.cmd, 0.U)(
    Seq(
      CSR.W -> io.in,
      CSR.S -> (io.out | io.in),
      CSR.C -> (io.out & ~io.in)
    )
  )
  val iaddrInvalid = io.pc_check && io.addr(1)
  val laddrInvalid = MuxLookup(io.ld_type, false.B)(
    Seq(Control.LD_LW -> io.addr(1, 0).orR, Control.LD_LH -> io.addr(0), Control.LD_LHU -> io.addr(0))
  )
  val saddrInvalid =
    MuxLookup(io.st_type, false.B)(Seq(Control.ST_SW -> io.addr(1, 0).orR, Control.ST_SH -> io.addr(0)))
  io.expt := io.illegal || iaddrInvalid || laddrInvalid || saddrInvalid ||
    io.cmd(1, 0).orR && (!csrValid || !privValid) || wen && csrRO ||
    (privInst && !privValid) || isEcall || isEbreak
  io.evec := mtvec + (PRV << 6)
  io.epc := mepc

  // Counters
  time := time + 1.U
  when(time.andR) { timeh := timeh + 1.U }
  cycle := cycle + 1.U
  when(cycle.andR) { cycleh := cycleh + 1.U }
  val isInstRet = io.inst =/= Instructions.NOP && (!io.expt || isEcall || isEbreak) && !io.stall
  when(isInstRet) { instret := instret + 1.U }
  when(isInstRet && instret.andR) { instreth := instreth + 1.U }

  when(!io.stall) {
    when(io.expt) {
      mepc := io.pc >> 2 << 2
      mcause := Mux(
        iaddrInvalid,
        Cause.InstAddrMisaligned,
        Mux(
          laddrInvalid,
          Cause.LoadAddrMisaligned,
          Mux(
            saddrInvalid,
            Cause.StoreAddrMisaligned,
            Mux(isEcall, Cause.Ecall + PRV, Mux(isEbreak, Cause.Breakpoint, Cause.IllegalInst))
          )
        )
      )
      PRV := CSR.PRV_M
      IE := false.B
      PRV1 := PRV
      IE1 := IE
      when(iaddrInvalid || laddrInvalid || saddrInvalid) { mbadaddr := io.addr }
    }.elsewhen(isEret) {
      PRV := PRV1
      IE := IE1
      PRV1 := CSR.PRV_U
      IE1 := true.B
    }.elsewhen(wen) {
      when(csr_addr === CSR.mstatus) {
        PRV1 := wdata(5, 4)
        IE1 := wdata(3)
        PRV := wdata(2, 1)
        IE := wdata(0)
      }
        .elsewhen(csr_addr === CSR.mip) {
          MTIP := wdata(7)
          MSIP := wdata(3)
        }
        .elsewhen(csr_addr === CSR.mie) {
          MTIE := wdata(7)
          MSIE := wdata(3)
        }
        .elsewhen(csr_addr === CSR.mtime) { time := wdata }
        .elsewhen(csr_addr === CSR.mtimeh) { timeh := wdata }
        .elsewhen(csr_addr === CSR.mtimecmp) { mtimecmp := wdata }
        .elsewhen(csr_addr === CSR.mscratch) { mscratch := wdata }
        .elsewhen(csr_addr === CSR.mepc) { mepc := wdata >> 2.U << 2.U }
        .elsewhen(csr_addr === CSR.mcause) { mcause := wdata & (BigInt(1) << (xlen - 1) | 0xf).U }
        .elsewhen(csr_addr === CSR.mbadaddr) { mbadaddr := wdata }
        .elsewhen(csr_addr === CSR.mtohost) { mtohost := wdata }
        .elsewhen(csr_addr === CSR.mfromhost) { mfromhost := wdata }
        .elsewhen(csr_addr === CSR.cyclew) { cycle := wdata }
        .elsewhen(csr_addr === CSR.timew) { time := wdata }
        .elsewhen(csr_addr === CSR.instretw) { instret := wdata }

        .elsewhen(csr_addr === CSR.timehw) { timeh := wdata }
        .elsewhen(csr_addr === CSR.instrethw) { instreth := wdata }
    }
  }
}

.elsewhen(csr_addr === CSR.cyclehw) { cycleh := wdata }


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.util._
import junctions._

class CacheReq(addrWidth: Int, dataWidth: Int) extends Bundle {
  val addr = UInt(addrWidth.W)
  val data = UInt(dataWidth.W)
  val mask = UInt((dataWidth / 8).W)
}

class CacheResp(dataWidth: Int) extends Bundle {
  val data = UInt(dataWidth.W)
}

class CacheIO(addrWidth: Int, dataWidth: Int) extends Bundle {
  val abort = Input(Bool())
  val req = Flipped(Valid(new CacheReq(addrWidth, dataWidth)))
  val resp = Valid(new CacheResp(dataWidth))
}

class CacheModuleIO(nastiParams: NastiBundleParameters, addrWidth: Int, dataWidth: Int) extends Bundle {
  val cpu = new CacheIO(addrWidth, dataWidth)
  val nasti = new NastiBundle(nastiParams)
}

class MetaData(tagLength: Int) extends Bundle {
  val tag = UInt(tagLength.W)
}

object CacheState extends ChiselEnum {
  val sIdle, sReadCache, sWriteCache, sWriteBack, sWriteAck, sRefillReady, sRefill = Value
}

class Cache(val p: CacheConfig, val nasti: NastiBundleParameters, val xlen: Int) extends Module {
  // local parameters
  val nSets = p.nSets
  val bBytes = p.blockBytes
  val bBits = bBytes << 3
  val blen = log2Ceil(bBytes)
  val slen = log2Ceil(nSets)
  val tlen = xlen - (slen + blen)
  val nWords = bBits / xlen
  val wBytes = xlen / 8
  val byteOffsetBits = log2Ceil(wBytes)
  val dataBeats = bBits / nasti.dataBits

  val io = IO(new CacheModuleIO(nasti, addrWidth = xlen, dataWidth = xlen))

  // cache states
  import CacheState._
  val state = RegInit(sIdle)
  // memory
  val v = RegInit(0.U(nSets.W))
  val d = RegInit(0.U(nSets.W))
  val metaMem = SyncReadMem(nSets, new MetaData(tlen))
  val dataMem = Seq.fill(nWords)(SyncReadMem(nSets, Vec(wBytes, UInt(8.W))))

  val addr_reg = Reg(chiselTypeOf(io.cpu.req.bits.addr))
  val cpu_data = Reg(chiselTypeOf(io.cpu.req.bits.data))
  val cpu_mask = Reg(chiselTypeOf(io.cpu.req.bits.mask))

  // Counters
  require(dataBeats > 0)
  val (read_count, read_wrap_out) = Counter(io.nasti.r.fire, dataBeats)
  val (write_count, write_wrap_out) = Counter(io.nasti.w.fire, dataBeats)

  val is_idle = state === sIdle
  val is_read = state === sReadCache
  val is_write = state === sWriteCache
  val is_alloc = state === sRefill && read_wrap_out
  val is_alloc_reg = RegNext(is_alloc)

  val hit = Wire(Bool())
  val wen = is_write && (hit || is_alloc_reg) && !io.cpu.abort || is_alloc
  val ren = !wen && (is_idle || is_read) && io.cpu.req.valid
  val ren_reg = RegNext(ren)

  val addr = io.cpu.req.bits.addr
  val idx = addr(slen + blen - 1, blen)
  val tag_reg = addr_reg(xlen - 1, slen + blen)
  val idx_reg = addr_reg(slen + blen - 1, blen)
  val off_reg = addr_reg(blen - 1, byteOffsetBits)

  val rmeta = metaMem.read(idx, ren)
  val rdata = Cat((dataMem.map(_.read(idx, ren).asUInt)).reverse)
  val rdata_buf = RegEnable(rdata, ren_reg)
  val refill_buf = Reg(Vec(dataBeats, UInt(nasti.dataBits.W)))
  val read = Mux(is_alloc_reg, refill_buf.asUInt, Mux(ren_reg, rdata, rdata_buf))

  hit := v(idx_reg) && rmeta.tag === tag_reg

  // Read Mux
  io.cpu.resp.bits.data := VecInit.tabulate(nWords)(i => read((i + 1) * xlen - 1, i * xlen))(off_reg)
  io.cpu.resp.valid := is_idle || is_read && hit || is_alloc_reg && !cpu_mask.orR

  when(io.cpu.resp.valid) {
    addr_reg := addr
    cpu_data := io.cpu.req.bits.data
    cpu_mask := io.cpu.req.bits.mask
  }

  val wmeta = Wire(new MetaData(tlen))
  wmeta.tag := tag_reg

  val wmask = Mux(!is_alloc, (cpu_mask << Cat(off_reg, 0.U(byteOffsetBits.W))).zext, (-1).S)
  val wdata = Mux(
    !is_alloc,
    Fill(nWords, cpu_data),
    if (refill_buf.size == 1) io.nasti.r.bits.data
    else Cat(io.nasti.r.bits.data, Cat(refill_buf.init.reverse))
  )
  when(wen) {
    v := v.bitSet(idx_reg, true.B)
    d := d.bitSet(idx_reg, !is_alloc)
    when(is_alloc) {
      metaMem.write(idx_reg, wmeta)
    }
    dataMem.zipWithIndex.foreach {
      case (mem, i) =>
        val data = VecInit.tabulate(wBytes)(k => wdata(i * xlen + (k + 1) * 8 - 1, i * xlen + k * 8))
        mem.write(idx_reg, data, wmask((i + 1) * wBytes - 1, i * wBytes).asBools)
        mem.suggestName(s"dataMem_${i}")
    }
  }

  io.nasti.ar.bits := NastiAddressBundle(nasti)(
    0.U,
    (Cat(tag_reg, idx_reg) << blen.U).asUInt,
    log2Up(nasti.dataBits / 8).U,
    (dataBeats - 1).U
  )
  io.nasti.ar.valid := false.B
  // read data
  io.nasti.r.ready := state === sRefill
  when(io.nasti.r.fire) {
    refill_buf(read_count) := io.nasti.r.bits.data
  }

  // write addr
  io.nasti.aw.bits := NastiAddressBundle(nasti)(
    0.U,
    (Cat(rmeta.tag, idx_reg) << blen.U).asUInt,
    log2Up(nasti.dataBits / 8).U,
    (dataBeats - 1).U
  )
  io.nasti.aw.valid := false.B
  // write data
  io.nasti.w.bits := NastiWriteDataBundle(nasti)(
    VecInit.tabulate(dataBeats)(i => read((i + 1) * nasti.dataBits - 1, i * nasti.dataBits))(write_count),
    None,
    write_wrap_out
  )
  io.nasti.w.valid := false.B
  // write resp
  io.nasti.b.ready := false.B

  // Cache FSM
  val is_dirty = v(idx_reg) && d(idx_reg)
  switch(state) {
    is(sIdle) {
      when(io.cpu.req.valid) {
        state := Mux(io.cpu.req.bits.mask.orR, sWriteCache, sReadCache)
      }
    }
    is(sReadCache) {
      when(hit) {
        when(io.cpu.req.valid) {
          state := Mux(io.cpu.req.bits.mask.orR, sWriteCache, sReadCache)
        }.otherwise {
          state := sIdle
        }
      }.otherwise {
        io.nasti.aw.valid := is_dirty
        io.nasti.ar.valid := !is_dirty
        when(io.nasti.aw.fire) {
          state := sWriteBack
        }.elsewhen(io.nasti.ar.fire) {
          state := sRefill
        }
      }
    }
    is(sWriteCache) {
      when(hit || is_alloc_reg || io.cpu.abort) {
        state := sIdle
      }.otherwise {
        io.nasti.aw.valid := is_dirty
        io.nasti.ar.valid := !is_dirty
        when(io.nasti.aw.fire) {
          state := sWriteBack
        }.elsewhen(io.nasti.ar.fire) {
          state := sRefill
        }
      }
    }
    is(sWriteBack) {
      io.nasti.w.valid := true.B
      when(write_wrap_out) {
        state := sWriteAck
      }
    }
    is(sWriteAck) {
      io.nasti.b.ready := true.B
      when(io.nasti.b.fire) {
        state := sRefillReady
      }
    }
    is(sRefillReady) {
      io.nasti.ar.valid := true.B
      when(io.nasti.ar.fire) {
        state := sRefill
      }
    }
    is(sRefill) {
      when(read_wrap_out) {
        state := Mux(cpu_mask.orR, sWriteCache, sIdle)
      }
    }
  }
}

case class CacheConfig(nWays: Int, nSets: Int, blockBytes: Int)


// See LICENSE for license details.

package mini

import junctions.NastiBundleParameters

object MiniConfig {
  def apply(): Config = {
    val xlen = 32
    Config(
      core = CoreConfig(
        xlen = xlen,
        makeAlu = new AluArea(_),
        makeBrCond = new BrCondArea(_),
        makeImmGen = new ImmGenWire(_)
      ),
      cache = CacheConfig(
        nWays = 1,
        nSets = 256,
        blockBytes = 4 * (xlen / 8) // 4 * 32 bits = 16B
      ),
      nasti = NastiBundleParameters(
        addrBits = 32,
        dataBits = 64,
        idBits = 5
      )
    )
  }
}

case class Config(core: CoreConfig, cache: CacheConfig, nasti: NastiBundleParameters)


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.util._

object Control {
  val Y = true.B
  val N = false.B

  // pc_sel
  val PC_4 = 0.U(2.W)
  val PC_ALU = 1.U(2.W)
  val PC_0 = 2.U(2.W)
  val PC_EPC = 3.U(2.W)

  // A_sel
  val A_XXX = 0.U(1.W)
  val A_PC = 0.U(1.W)
  val A_RS1 = 1.U(1.W)

  // B_sel
  val B_XXX = 0.U(1.W)
  val B_IMM = 0.U(1.W)
  val B_RS2 = 1.U(1.W)

  // imm_sel
  val IMM_X = 0.U(3.W)
  val IMM_I = 1.U(3.W)
  val IMM_S = 2.U(3.W)
  val IMM_U = 3.U(3.W)
  val IMM_J = 4.U(3.W)
  val IMM_B = 5.U(3.W)
  val IMM_Z = 6.U(3.W)

  // br_type
  val BR_XXX = 0.U(3.W)
  val BR_LTU = 1.U(3.W)
  val BR_LT = 2.U(3.W)
  val BR_EQ = 3.U(3.W)
  val BR_GEU = 4.U(3.W)
  val BR_GE = 5.U(3.W)
  val BR_NE = 6.U(3.W)

  // st_type
  val ST_XXX = 0.U(2.W)
  val ST_SW = 1.U(2.W)
  val ST_SH = 2.U(2.W)
  val ST_SB = 3.U(2.W)

  // ld_type
  val LD_XXX = 0.U(3.W)
  val LD_LW = 1.U(3.W)
  val LD_LH = 2.U(3.W)
  val LD_LB = 3.U(3.W)
  val LD_LHU = 4.U(3.W)
  val LD_LBU = 5.U(3.W)

  // wb_sel
  val WB_ALU = 0.U(2.W)
  val WB_MEM = 1.U(2.W)
  val WB_PC4 = 2.U(2.W)
  val WB_CSR = 3.U(2.W)

  import Alu._
  import Instructions._

  // format: off
  val default =
  //                                                              kill                        wb_en  illegal?
  //             pc_sel  A_sel   B_sel  imm_sel   alu_op   br_type  |  st_type ld_type wb_sel  | csr_cmd |
  //               |       |       |     |          |          |    |     |       |       |    |  |      |
             List(PC_4  , A_XXX,  B_XXX, IMM_X, ALU_XXX   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, Y)
  val map = Array(
    LUI   -> List(PC_4  , A_PC,   B_IMM, IMM_U, ALU_COPY_B, BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    AUIPC -> List(PC_4  , A_PC,   B_IMM, IMM_U, ALU_ADD   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    JAL   -> List(PC_ALU, A_PC,   B_IMM, IMM_J, ALU_ADD   , BR_XXX, Y, ST_XXX, LD_XXX, WB_PC4, Y, CSR.N, N),
    JALR  -> List(PC_ALU, A_RS1,  B_IMM, IMM_I, ALU_ADD   , BR_XXX, Y, ST_XXX, LD_XXX, WB_PC4, Y, CSR.N, N),
    BEQ   -> List(PC_4  , A_PC,   B_IMM, IMM_B, ALU_ADD   , BR_EQ , N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
    BNE   -> List(PC_4  , A_PC,   B_IMM, IMM_B, ALU_ADD   , BR_NE , N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
    BLT   -> List(PC_4  , A_PC,   B_IMM, IMM_B, ALU_ADD   , BR_LT , N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
    BGE   -> List(PC_4  , A_PC,   B_IMM, IMM_B, ALU_ADD   , BR_GE , N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
    BLTU  -> List(PC_4  , A_PC,   B_IMM, IMM_B, ALU_ADD   , BR_LTU, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
    BGEU  -> List(PC_4  , A_PC,   B_IMM, IMM_B, ALU_ADD   , BR_GEU, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
    LB    -> List(PC_0  , A_RS1,  B_IMM, IMM_I, ALU_ADD   , BR_XXX, Y, ST_XXX, LD_LB , WB_MEM, Y, CSR.N, N),
    LH    -> List(PC_0  , A_RS1,  B_IMM, IMM_I, ALU_ADD   , BR_XXX, Y, ST_XXX, LD_LH , WB_MEM, Y, CSR.N, N),
    LW    -> List(PC_0  , A_RS1,  B_IMM, IMM_I, ALU_ADD   , BR_XXX, Y, ST_XXX, LD_LW , WB_MEM, Y, CSR.N, N),
    LBU   -> List(PC_0  , A_RS1,  B_IMM, IMM_I, ALU_ADD   , BR_XXX, Y, ST_XXX, LD_LBU, WB_MEM, Y, CSR.N, N),
    LHU   -> List(PC_0  , A_RS1,  B_IMM, IMM_I, ALU_ADD   , BR_XXX, Y, ST_XXX, LD_LHU, WB_MEM, Y, CSR.N, N),
    SB    -> List(PC_4  , A_RS1,  B_IMM, IMM_S, ALU_ADD   , BR_XXX, N, ST_SB , LD_XXX, WB_ALU, N, CSR.N, N),
    SH    -> List(PC_4  , A_RS1,  B_IMM, IMM_S, ALU_ADD   , BR_XXX, N, ST_SH , LD_XXX, WB_ALU, N, CSR.N, N),
    SW    -> List(PC_4  , A_RS1,  B_IMM, IMM_S, ALU_ADD   , BR_XXX, N, ST_SW , LD_XXX, WB_ALU, N, CSR.N, N),
    ADDI  -> List(PC_4  , A_RS1,  B_IMM, IMM_I, ALU_ADD   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SLTI  -> List(PC_4  , A_RS1,  B_IMM, IMM_I, ALU_SLT   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SLTIU -> List(PC_4  , A_RS1,  B_IMM, IMM_I, ALU_SLTU  , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    XORI  -> List(PC_4  , A_RS1,  B_IMM, IMM_I, ALU_XOR   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    ORI   -> List(PC_4  , A_RS1,  B_IMM, IMM_I, ALU_OR    , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    ANDI  -> List(PC_4  , A_RS1,  B_IMM, IMM_I, ALU_AND   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SLLI  -> List(PC_4  , A_RS1,  B_IMM, IMM_I, ALU_SLL   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SRLI  -> List(PC_4  , A_RS1,  B_IMM, IMM_I, ALU_SRL   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SRAI  -> List(PC_4  , A_RS1,  B_IMM, IMM_I, ALU_SRA   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    ADD   -> List(PC_4  , A_RS1,  B_RS2, IMM_X, ALU_ADD   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SUB   -> List(PC_4  , A_RS1,  B_RS2, IMM_X, ALU_SUB   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SLL   -> List(PC_4  , A_RS1,  B_RS2, IMM_X, ALU_SLL   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SLT   -> List(PC_4  , A_RS1,  B_RS2, IMM_X, ALU_SLT   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SLTU  -> List(PC_4  , A_RS1,  B_RS2, IMM_X, ALU_SLTU  , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    XOR   -> List(PC_4  , A_RS1,  B_RS2, IMM_X, ALU_XOR   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SRL   -> List(PC_4  , A_RS1,  B_RS2, IMM_X, ALU_SRL   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    SRA   -> List(PC_4  , A_RS1,  B_RS2, IMM_X, ALU_SRA   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    OR    -> List(PC_4  , A_RS1,  B_RS2, IMM_X, ALU_OR    , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    AND   -> List(PC_4  , A_RS1,  B_RS2, IMM_X, ALU_AND   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, Y, CSR.N, N),
    FENCE -> List(PC_4  , A_XXX,  B_XXX, IMM_X, ALU_XXX   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
    FENCEI-> List(PC_0  , A_XXX,  B_XXX, IMM_X, ALU_XXX   , BR_XXX, Y, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N),
    CSRRW -> List(PC_0  , A_RS1,  B_XXX, IMM_X, ALU_COPY_A, BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.W, N),
    CSRRS -> List(PC_0  , A_RS1,  B_XXX, IMM_X, ALU_COPY_A, BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.S, N),
    CSRRC -> List(PC_0  , A_RS1,  B_XXX, IMM_X, ALU_COPY_A, BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.C, N),
    CSRRWI-> List(PC_0  , A_XXX,  B_XXX, IMM_Z, ALU_XXX   , BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.W, N),
    CSRRSI-> List(PC_0  , A_XXX,  B_XXX, IMM_Z, ALU_XXX   , BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.S, N),
    CSRRCI-> List(PC_0  , A_XXX,  B_XXX, IMM_Z, ALU_XXX   , BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, Y, CSR.C, N),
    ECALL -> List(PC_4  , A_XXX,  B_XXX, IMM_X, ALU_XXX   , BR_XXX, N, ST_XXX, LD_XXX, WB_CSR, N, CSR.P, N),
    EBREAK-> List(PC_4  , A_XXX,  B_XXX, IMM_X, ALU_XXX   , BR_XXX, N, ST_XXX, LD_XXX, WB_CSR, N, CSR.P, N),
    ERET  -> List(PC_EPC, A_XXX,  B_XXX, IMM_X, ALU_XXX   , BR_XXX, Y, ST_XXX, LD_XXX, WB_CSR, N, CSR.P, N),
    WFI   -> List(PC_4  , A_XXX,  B_XXX, IMM_X, ALU_XXX   , BR_XXX, N, ST_XXX, LD_XXX, WB_ALU, N, CSR.N, N))
  // format: on
}

class ControlSignals extends Bundle {
  val inst = Input(UInt(32.W))
  val pc_sel = Output(UInt(2.W))
  val inst_kill = Output(Bool())
  val A_sel = Output(UInt(1.W))
  val B_sel = Output(UInt(1.W))
  val imm_sel = Output(UInt(3.W))

  val br_type = Output(UInt(3.W))
  val st_type = Output(UInt(2.W))
  val ld_type = Output(UInt(3.W))
  val wb_sel = Output(UInt(2.W))
  val wb_en = Output(Bool())
  val csr_cmd = Output(UInt(3.W))
  val illegal = Output(Bool())
}

class Control extends Module {
  val io = IO(new ControlSignals)
  val ctrlSignals = ListLookup(io.inst, Control.default, Control.map)

  // Control signals for Fetch
  io.pc_sel := ctrlSignals(0)
  io.inst_kill := ctrlSignals(6).asBool

  // Control signals for Execute
  io.A_sel := ctrlSignals(1)
  io.B_sel := ctrlSignals(2)
  io.imm_sel := ctrlSignals(3)
  io.alu_op := ctrlSignals(4)
  io.br_type := ctrlSignals(5)
  io.st_type := ctrlSignals(7)

  // Control signals for Write Back
  io.ld_type := ctrlSignals(8)
  io.wb_sel := ctrlSignals(9)
  io.wb_en := ctrlSignals(10).asBool
  io.csr_cmd := ctrlSignals(11)
  io.illegal := ctrlSignals(12)
}


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.util.Valid

case class CoreConfig(
  xlen:       Int,
  makeAlu:    Int => Alu = new AluSimple(_),
  makeBrCond: Int => BrCond = new BrCondSimple(_),
  makeImmGen: Int => ImmGen = new ImmGenWire(_))

class HostIO(xlen: Int) extends Bundle {
  val fromhost = Flipped(Valid(UInt(xlen.W)))
  val tohost = Output(UInt(xlen.W))
}

class CoreIO(xlen: Int) extends Bundle {
  val host = new HostIO(xlen)
  val icache = Flipped(new CacheIO(xlen, xlen))
  val dcache = Flipped(new CacheIO(xlen, xlen))
}

class Core(val conf: CoreConfig) extends Module {
  val io = IO(new CoreIO(conf.xlen))
  val dpath = Module(new Datapath(conf))
  val ctrl = Module(new Control)

  io.host <> dpath.io.host
  dpath.io.icache <> io.icache
  dpath.io.dcache <> io.dcache


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.util._
import chisel3.experimental.BundleLiterals._

object Consts {
  val PC_START = 0x200
  val PC_EVEC = 0x100
}

class DatapathIO(xlen: Int) extends Bundle {
  val host = new HostIO(xlen)
  val icache = Flipped(new CacheIO(xlen, xlen))
  val dcache = Flipped(new CacheIO(xlen, xlen))
  val ctrl = Flipped(new ControlSignals)
}

class FetchExecutePipelineRegister(xlen: Int) extends Bundle {
  val inst = chiselTypeOf(Instructions.NOP)
  val pc = UInt(xlen.W)
}

class ExecuteWritebackPipelineRegister(xlen: Int) extends Bundle {
  val inst = chiselTypeOf(Instructions.NOP)
  val pc = UInt(xlen.W)
  val alu = UInt(xlen.W)
  val csr_in = UInt(xlen.W)
}

class Datapath(val conf: CoreConfig) extends Module {
  val io = IO(new DatapathIO(conf.xlen))
  val csr = Module(new CSR(conf.xlen))
  val regFile = Module(new RegFile(conf.xlen))
  val alu = Module(conf.makeAlu(conf.xlen))
  val immGen = Module(conf.makeImmGen(conf.xlen))
  val brCond = Module(conf.makeBrCond(conf.xlen))

  import Control._

  /** Pipeline State Registers * */

  /** *** Fetch / Execute Registers ****
    */
  val fe_reg = RegInit(
    (new FetchExecutePipelineRegister(conf.xlen)).Lit(
      _.inst -> Instructions.NOP,
      _.pc -> 0.U
    )
  )

  /** *** Execute / Write Back Registers ****
    */
  val ew_reg = RegInit(
    (new ExecuteWritebackPipelineRegister(conf.xlen)).Lit(
      _.inst -> Instructions.NOP,
      _.pc -> 0.U,
      _.alu -> 0.U,
      _.csr_in -> 0.U
    )
  )

  /** **** Control signals ****
    */
  val st_type = Reg(io.ctrl.st_type.cloneType)
  val ld_type = Reg(io.ctrl.ld_type.cloneType)
  val wb_sel = Reg(io.ctrl.wb_sel.cloneType)
  val wb_en = Reg(Bool())
  val csr_cmd = Reg(io.ctrl.csr_cmd.cloneType)
  val illegal = Reg(Bool())
  val pc_check = Reg(Bool())

  /** **** Fetch ****
    */
  val started = RegNext(reset.asBool)
  val stall = !io.icache.resp.valid || !io.dcache.resp.valid
  val pc = RegInit(Consts.PC_START.U(conf.xlen.W) - 4.U(conf.xlen.W))
  // Next Program Counter
  val next_pc = MuxCase(
    pc + 4.U,
    IndexedSeq(
      stall -> pc,
      csr.io.expt -> csr.io.evec,
      (io.ctrl.pc_sel === PC_EPC) -> csr.io.epc,
      ((io.ctrl.pc_sel === PC_ALU) || (brCond.io.taken)) -> (alu.io.sum >> 1.U << 1.U),
      (io.ctrl.pc_sel === PC_0) -> pc
    )
  )
  val inst =
    Mux(started || io.ctrl.inst_kill || brCond.io.taken || csr.io.expt, Instructions.NOP, io.icache.resp.bits.data)
  pc := next_pc
  io.icache.req.bits.addr := next_pc
  io.icache.req.bits.data := 0.U
  io.icache.req.bits.mask := 0.U
  io.icache.req.valid := !stall
  io.icache.abort := false.B

  // Pipelining
  when(!stall) {
    fe_reg.pc := pc
    fe_reg.inst := inst
  }

  /** **** Execute ****
    */
  io.ctrl.inst := fe_reg.inst

  // regFile read
  val rd_addr = fe_reg.inst(11, 7)
  val rs1_addr = fe_reg.inst(19, 15)
  val rs2_addr = fe_reg.inst(24, 20)
  regFile.io.raddr1 := rs1_addr
  regFile.io.raddr2 := rs2_addr

  // gen immdeates
  immGen.io.inst := fe_reg.inst
  immGen.io.sel := io.ctrl.imm_sel

  // bypass
  val wb_rd_addr = ew_reg.inst(11, 7)
  val rs1hazard = wb_en && rs1_addr.orR && (rs1_addr === wb_rd_addr)
  val rs2hazard = wb_en && rs2_addr.orR && (rs2_addr === wb_rd_addr)
  val rs1 = Mux(wb_sel === WB_ALU && rs1hazard, ew_reg.alu, regFile.io.rdata1)
  val rs2 = Mux(wb_sel === WB_ALU && rs2hazard, ew_reg.alu, regFile.io.rdata2)

  // ALU operations
  alu.io.A := Mux(io.ctrl.A_sel === A_RS1, rs1, fe_reg.pc)

  alu.io.alu_op := io.ctrl.alu_op

  // Branch condition calc
  brCond.io.rs1 := rs1
  brCond.io.rs2 := rs2
  brCond.io.br_type := io.ctrl.br_type

  // D$ access
  val daddr = Mux(stall, ew_reg.alu, alu.io.sum) >> 2.U << 2.U
  val woffset = (alu.io.sum(1) << 4.U).asUInt | (alu.io.sum(0) << 3.U).asUInt
  io.dcache.req.valid := !stall && (io.ctrl.st_type.orR || io.ctrl.ld_type.orR)
  io.dcache.req.bits.addr := daddr
  io.dcache.req.bits.data := rs2 << woffset
  io.dcache.req.bits.mask := MuxLookup(Mux(stall, st_type, io.ctrl.st_type), "b0000".U)(
    Seq(ST_SW -> "b1111".U, ST_SH -> ("b11".U << alu.io.sum(1, 0)), ST_SB -> ("b1".U << alu.io.sum(1, 0)))
  )

  // Pipelining
  when(reset.asBool || !stall && csr.io.expt) {
    st_type := 0.U
    ld_type := 0.U
    wb_en := false.B
    csr_cmd := 0.U
    illegal := false.B
    pc_check := false.B
  }.elsewhen(!stall && !csr.io.expt) {
    ew_reg.pc := fe_reg.pc
    ew_reg.inst := fe_reg.inst
    ew_reg.alu := alu.io.out
    ew_reg.csr_in := Mux(io.ctrl.imm_sel === IMM_Z, immGen.io.out, rs1)
    st_type := io.ctrl.st_type
    ld_type := io.ctrl.ld_type
    wb_sel := io.ctrl.wb_sel
    wb_en := io.ctrl.wb_en
    csr_cmd := io.ctrl.csr_cmd
    illegal := io.ctrl.illegal
    pc_check := io.ctrl.pc_sel === PC_ALU
  }

  // Load
  val loffset = (ew_reg.alu(1) << 4.U).asUInt | (ew_reg.alu(0) << 3.U).asUInt
  val lshift = io.dcache.resp.bits.data >> loffset
  val load = MuxLookup(ld_type, io.dcache.resp.bits.data.zext)(
    Seq(
      LD_LH -> lshift(15, 0).asSInt,
      LD_LB -> lshift(7, 0).asSInt,
      LD_LHU -> lshift(15, 0).zext,
      LD_LBU -> lshift(7, 0).zext
    )
  )

  // CSR access
  csr.io.stall := stall
  csr.io.in := ew_reg.csr_in
  csr.io.cmd := csr_cmd
  csr.io.inst := ew_reg.inst
  csr.io.pc := ew_reg.pc
  csr.io.addr := ew_reg.alu
  csr.io.illegal := illegal
  csr.io.pc_check := pc_check
  csr.io.ld_type := ld_type
  csr.io.st_type := st_type
  io.host <> csr.io.host

  // Regfile Write
  val regWrite =
    MuxLookup(wb_sel, ew_reg.alu.zext)(
      Seq(WB_MEM -> load, WB_PC4 -> (ew_reg.pc + 4.U).zext, WB_CSR -> csr.io.out.zext)
    ).asUInt

  regFile.io.wen := wb_en && !stall && !csr.io.expt
  regFile.io.waddr := wb_rd_addr
  regFile.io.wdata := regWrite

  // Abort store when there's an excpetion
  io.dcache.abort := csr.io.expt

  // TODO: re-enable through AOP
//  if (p(Trace)) {
//    printf(
//      "PC: %x, INST: %x, REG[%d] <- %x\n",
//      ew_reg.pc,
//      ew_reg.inst,
//      Mux(regFile.io.wen, wb_rd_addr, 0.U),
//      Mux(regFile.io.wen, regFile.io.wdata, 0.U)
//    )
//  }
}

alu.io.B := Mux(io.ctrl.B_sel === B_RS2, rs2, immGen.io.out)


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.util._
import mini.Control._

class ImmGenIO(xlen: Int) extends Bundle {
  val inst = Input(UInt(xlen.W))
  val sel = Input(UInt(3.W))
  val out = Output(UInt(xlen.W))
}

trait ImmGen extends Module {
  def xlen: Int
  val io: ImmGenIO
}

class ImmGenWire(val xlen: Int) extends ImmGen {
  val io = IO(new ImmGenIO(xlen))
  val Iimm = io.inst(31, 20).asSInt
  val Simm = Cat(io.inst(31, 25), io.inst(11, 7)).asSInt
  val Bimm = Cat(io.inst(31), io.inst(7), io.inst(30, 25), io.inst(11, 8), 0.U(1.W)).asSInt

}

class ImmGenMux(val xlen: Int) extends ImmGen {
  val io = IO(new ImmGenIO(xlen))
  val sign = Mux(io.sel === IMM_Z, 0.S, io.inst(31).asSInt)
  val b30_20 = Mux(io.sel === IMM_U, io.inst(30, 20).asSInt, sign)
  val b19_12 = Mux(io.sel =/= IMM_U && io.sel =/= IMM_J, sign, io.inst(19, 12).asSInt)
  val b11 = Mux(
    io.sel === IMM_U || io.sel === IMM_Z,
    0.S,
    Mux(io.sel === IMM_J, io.inst(20).asSInt, Mux(io.sel === IMM_B, io.inst(7).asSInt, sign))
  )
  val b10_5 = Mux(io.sel === IMM_U || io.sel === IMM_Z, 0.U, io.inst(30, 25))
  val b4_1 = Mux(
    io.sel === IMM_U,
    0.U,
    Mux(io.sel === IMM_S || io.sel === IMM_B, io.inst(11, 8), Mux(io.sel === IMM_Z, io.inst(19, 16), io.inst(24, 21)))
  )
  val b0 =
    Mux(io.sel === IMM_S, io.inst(7), Mux(io.sel === IMM_I, io.inst(20), Mux(io.sel === IMM_Z, io.inst(15), 0.U)))

  io.out := Cat(sign, b30_20, b19_12, b11, b10_5, b4_1, b0).asSInt.asUInt
}

  val Uimm = Cat(io.inst(31, 12), 0.U(12.W)).asSInt
  val Jimm = Cat(io.inst(31), io.inst(19, 12), io.inst(20), io.inst(30, 25), io.inst(24, 21), 0.U(1.W)).asSInt
  val Zimm = io.inst(19, 15).zext


// See LICENSE for license details.

package mini

import circt.stage.ChiselStage
object Main extends App {
  val config = MiniConfig()
  ChiselStage.emitSystemVerilogFile(
    new Tile(

      nastiParams = config.nasti,
      cacheParams = config.cache
    ),
    args
  )
}


// See LICENSE for license details.

package mini

import chisel3._

class RegFileIO(xlen: Int) extends Bundle {
  val raddr1 = Input(UInt(5.W))
  val raddr2 = Input(UInt(5.W))

  val regs = Mem(32, UInt(xlen.W))
  io.rdata1 := Mux(io.raddr1.orR, regs(io.raddr1), 0.U)
  io.rdata2 := Mux(io.raddr2.orR, regs(io.raddr2), 0.U)
  when(io.wen & io.waddr.orR) {
    regs(io.waddr) := io.wdata
  }
}

  val rdata1 = Output(UInt(xlen.W))
  val rdata2 = Output(UInt(xlen.W))
  val wen = Input(Bool())
  val waddr = Input(UInt(5.W))
  val wdata = Input(UInt(xlen.W))


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.util._
import junctions._

class MemArbiterIO(params: NastiBundleParameters) extends Bundle {
  val icache = Flipped(new NastiBundle(params))
  val dcache = Flipped(new NastiBundle(params))
  val nasti = new NastiBundle(params)
}

object MemArbiterState extends ChiselEnum {
  val sIdle, sICacheRead, sDCacheRead, sDCacheWrite, sDCacheAck = Value
}

class MemArbiter(params: NastiBundleParameters) extends Module {
  val io = IO(new MemArbiterIO(params))

  import MemArbiterState._
  val state = RegInit(sIdle)

  // Write Address
  io.nasti.aw.bits := io.dcache.aw.bits
  io.nasti.aw.valid := io.dcache.aw.valid && state === sIdle
  io.dcache.aw.ready := io.nasti.aw.ready && state === sIdle
  io.icache.aw := DontCare

  // Write Data
  io.nasti.w.bits := io.dcache.w.bits
  io.nasti.w.valid := io.dcache.w.valid && state === sDCacheWrite
  io.dcache.w.ready := io.nasti.w.ready && state === sDCacheWrite
  io.icache.w := DontCare

  // Write Ack
  io.dcache.b.bits := io.nasti.b.bits
  io.dcache.b.valid := io.nasti.b.valid && state === sDCacheAck
  io.nasti.b.ready := io.dcache.b.ready && state === sDCacheAck
  io.icache.b := DontCare

  // Read Address
  io.nasti.ar.bits := NastiAddressBundle(params)(
    Mux(io.dcache.ar.valid, io.dcache.ar.bits.id, io.icache.ar.bits.id),
    Mux(io.dcache.ar.valid, io.dcache.ar.bits.addr, io.icache.ar.bits.addr),
    Mux(io.dcache.ar.valid, io.dcache.ar.bits.size, io.icache.ar.bits.size),
    Mux(io.dcache.ar.valid, io.dcache.ar.bits.len, io.icache.ar.bits.len)
  )
  io.nasti.ar.valid := (io.icache.ar.valid || io.dcache.ar.valid) &&
    !io.nasti.aw.valid && state === sIdle
  io.dcache.ar.ready := io.nasti.ar.ready && !io.nasti.aw.valid && state === sIdle
  io.icache.ar.ready := io.dcache.ar.ready && !io.dcache.ar.valid

  // Read Data
  io.icache.r.bits := io.nasti.r.bits
  io.dcache.r.bits := io.nasti.r.bits
  io.icache.r.valid := io.nasti.r.valid && state === sICacheRead
  io.dcache.r.valid := io.nasti.r.valid && state === sDCacheRead
  io.nasti.r.ready := io.icache.r.ready && state === sICacheRead ||
    io.dcache.r.ready && state === sDCacheRead

  switch(state) {
    is(sIdle) {
      when(io.dcache.aw.fire) {
        state := sDCacheWrite
      }.elsewhen(io.dcache.ar.fire) {
        state := sDCacheRead
      }.elsewhen(io.icache.ar.fire) {
        state := sICacheRead
      }
    }
    is(sICacheRead) {
      when(io.nasti.r.fire && io.nasti.r.bits.last) {
        state := sIdle
      }
    }
    is(sDCacheRead) {
      when(io.nasti.r.fire && io.nasti.r.bits.last) {
        state := sIdle
      }
    }
    is(sDCacheWrite) {
      when(io.dcache.w.fire && io.dcache.w.bits.last) {
        state := sDCacheAck
      }
    }
    is(sDCacheAck) {
      when(io.nasti.b.fire) {
        state := sIdle
      }
    }
  }
}

class TileIO(xlen: Int, nastiParams: NastiBundleParameters) extends Bundle {
  val host = new HostIO(xlen)
  val nasti = new NastiBundle(nastiParams)
}

object Tile {

}

class Tile(val coreParams: CoreConfig, val nastiParams: NastiBundleParameters, val cacheParams: CacheConfig)
    extends Module {
  val io = IO(new TileIO(coreParams.xlen, nastiParams))
  val core = Module(new Core(coreParams))
  val icache = Module(new Cache(cacheParams, nastiParams, coreParams.xlen))
  val dcache = Module(new Cache(cacheParams, nastiParams, coreParams.xlen))
  val arb = Module(new MemArbiter(nastiParams))

  io.host <> core.io.host
  core.io.icache <> icache.io.cpu
  core.io.dcache <> dcache.io.cpu
  arb.io.icache <> icache.io.nasti
  arb.io.dcache <> dcache.io.nasti
  io.nasti <> arb.io.nasti
}

def apply(config: Config): Tile = new Tile(config.core, config.nasti, config.cache)


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.testers._
import chisel3.util._
import chiseltest._
import chiseltest.formal._
import org.scalatest.flatspec.AnyFlatSpec

class AluTester(alu: => Alu) extends BasicTester with TestUtils {
  import Alu._
  val dut = Module(alu)
  val ctrl = Module(new Control)
  val xlen = dut.width

  val (cntr, done) = Counter(true.B, insts.size)
  val rs1 = Seq.fill(insts.size)(rnd.nextInt()).map(toBigInt)
  val rs2 = Seq.fill(insts.size)(rnd.nextInt()).map(toBigInt)
  val sum = VecInit(rs1.zip(rs2).map { case (a, b) => toBigInt(a.toInt + b.toInt).U(xlen.W) })
  val diff = VecInit(rs1.zip(rs2).map { case (a, b) => toBigInt(a.toInt - b.toInt).U(xlen.W) })
  val and = VecInit(rs1.zip(rs2).map { case (a, b) => (a & b).U(xlen.W) })
  val or = VecInit(rs1.zip(rs2).map { case (a, b) => (a | b).U(xlen.W) })
  val xor = VecInit(rs1.zip(rs2).map { case (a, b) => (a ^ b).U(xlen.W) })
  val slt = VecInit(rs1.zip(rs2).map { case (a, b) => (if (a.toInt < b.toInt) 1 else 0).U(xlen.W) })
  val sltu = VecInit(rs1.zip(rs2).map { case (a, b) => (if (a < b) 1 else 0).U(xlen.W) })
  val sll = VecInit(rs1.zip(rs2).map { case (a, b) => toBigInt(a.toInt << (b.toInt & 0x1f)).U(xlen.W) })
  val srl = VecInit(rs1.zip(rs2).map { case (a, b) => toBigInt(a.toInt >>> (b.toInt & 0x1f)).U(xlen.W) })
  val sra = VecInit(rs1.zip(rs2).map { case (a, b) => toBigInt(a.toInt >> (b.toInt & 0x1f)).U(xlen.W) })
  val out = (
    Mux(
      dut.io.alu_op === ALU_ADD,
      sum(cntr),
      Mux(
        dut.io.alu_op === ALU_SUB,
        diff(cntr),
        Mux(
          dut.io.alu_op === ALU_AND,
          and(cntr),
          Mux(
            dut.io.alu_op === ALU_OR,
            or(cntr),
            Mux(
              dut.io.alu_op === ALU_XOR,
              xor(cntr),
              Mux(
                dut.io.alu_op === ALU_SLT,
                slt(cntr),
                Mux(
                  dut.io.alu_op === ALU_SLTU,
                  sltu(cntr),
                  Mux(
                    dut.io.alu_op === ALU_SLL,
                    sll(cntr),
                    Mux(
                      dut.io.alu_op === ALU_SRL,
                      srl(cntr),
                      Mux(dut.io.alu_op === ALU_SRA, sra(cntr), Mux(dut.io.alu_op === ALU_COPY_A, dut.io.A, dut.io.B))
                    )
                  )
                )
              )
            )
          )
        )
      )
    ),
    Mux(dut.io.alu_op(0), diff(cntr), sum(cntr))
  )

  ctrl.io.inst := VecInit(insts)(cntr)
  dut.io.alu_op := ctrl.io.alu_op
  dut.io.A := VecInit(rs1.map(_.U))(cntr)
  dut.io.B := VecInit(rs2.map(_.U))(cntr)

  assert(dut.io.out === out._1)
  assert(dut.io.sum === out._2)
  printf(
    "Counter: %d, OP: 0x%x, A: 0x%x, B: 0x%x, OUT: 0x%x ?= 0x%x, SUM: 0x%x ?= 0x%x\n",
    cntr,
    dut.io.alu_op,
    dut.io.A,
    dut.io.B,
    dut.io.out,
    out._1,
    dut.io.sum,
    out._2
  )
}

class ALUTests extends AnyFlatSpec with ChiselScalatestTester with Formal {
  "ALUSimple" should "pass" in {
    test(new AluTester(new AluSimple(32))).runUntilStop()
  }
  "AluArea" should "pass" in {
    test(new AluTester(new AluArea(32))).runUntilStop()
  }
  "AluArea" should "be equivalent to AluSimple" in {
    // since there is no state (registers/memory) in the ALU, a single cycle check is enough to prove equivalence
    verify(new AluEquivalenceCheck(new AluArea(32)), Seq(BoundedCheck(1)))
  }
}

class AluEquivalenceCheck(other: => Alu) extends Module {
  val dut = Module(other)
  val ref = Module(new AluSimple(dut.width))

  // arbitrary inputs
  val io = IO(chiselTypeOf(dut.io))

  // connect the same inputs to both modules (the outputs will be overwritten to always connect to the reference)
  dut.io <> io; ref.io <> io

  // check to ensure that outputs are the same
  assert(ref.io.out === dut.io.out, "out: expected: %d, actual: %d", ref.io.out, dut.io.out)
  assert(ref.io.sum === dut.io.sum, "sum: %d, actual: %d", ref.io.sum, dut.io.sum)
}


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.testers._
import chisel3.util._
import chiseltest._
import chiseltest.formal._
import org.scalatest.flatspec.AnyFlatSpec

class BrCondTester(br: => BrCond) extends BasicTester with TestUtils {
  import Control._
  val dut = Module(br)
  val ctrl = Module(new Control)

  override val insts = Seq
    .fill(10)(
      Seq(
        B(Funct3.BEQ, 0, 0, 0),
        B(Funct3.BNE, 0, 0, 0),
        B(Funct3.BLT, 0, 0, 0),
        B(Funct3.BGE, 0, 0, 0),
        B(Funct3.BLTU, 0, 0, 0),
        B(Funct3.BGEU, 0, 0, 0)
      )
    )
    .flatten

  val (cntr, done) = Counter(true.B, insts.size)
  val rs1 = Seq.fill(insts.size)(rnd.nextInt()).map(toBigInt)
  val rs2 = Seq.fill(insts.size)(rnd.nextInt()).map(toBigInt)
  val eq = VecInit(rs1.zip(rs2).map { case (a, b) => (a == b).B })
  val ne = VecInit(rs1.zip(rs2).map { case (a, b) => (a != b).B })
  val lt = VecInit(rs1.zip(rs2).map { case (a, b) => (a.toInt < b.toInt).B })
  val ge = VecInit(rs1.zip(rs2).map { case (a, b) => (a.toInt >= b.toInt).B })
  val ltu = VecInit(rs1.zip(rs2).map { case (a, b) => (a < b).B })
  val geu = VecInit(rs1.zip(rs2).map { case (a, b) => (a >= b).B })
  val out = Mux(
    dut.io.br_type === BR_EQ,
    eq(cntr),
    Mux(
      dut.io.br_type === BR_NE,
      ne(cntr),
      Mux(
        dut.io.br_type === BR_LT,
        lt(cntr),
        Mux(
          dut.io.br_type === BR_GE,
          ge(cntr),
          Mux(dut.io.br_type === BR_LTU, ltu(cntr), Mux(dut.io.br_type === BR_GEU, geu(cntr), false.B))
        )
      )
    )
  )

  ctrl.io.inst := VecInit(insts)(cntr)
  dut.io.br_type := ctrl.io.br_type
  dut.io.rs1 := VecInit(rs1.map(_.U))(cntr)
  dut.io.rs2 := VecInit(rs2.map(_.U))(cntr)

  assert(dut.io.taken === out)
  printf(
    "Counter: %d, BrType: 0x%x, rs1: 0x%x, rs2: 0x%x, Taken: %d ?= %d\n",
    cntr,
    dut.io.br_type,
    dut.io.rs1,
    dut.io.rs2,
    dut.io.taken,
    out
  )
}

class BrCondTests extends AnyFlatSpec with ChiselScalatestTester with Formal {
  "BrCondSimple" should "pass" in {
    test(new BrCondTester(new BrCondSimple(32))).runUntilStop()
  }
  "BrCondArea" should "pass" in {
    test(new BrCondTester(new BrCondArea(32))).runUntilStop()
  }
  "BrCondArea" should "be equivalent to BrCondSimple" in {
    // since there is no state (registers/memory) in the BrCond, a single cycle check is enough to prove equivalence
    verify(new BrCondEquivalenceCheck(new BrCondArea(32)), Seq(BoundedCheck(1)))
  }
}

class BrCondEquivalenceCheck(other: => BrCond) extends Module {
  val dut = Module(other)
  val ref = Module(new BrCondSimple(dut.xlen))

  // arbitrary inputs
  val io = IO(chiselTypeOf(dut.io))

  // connect the same inputs to both modules (the outputs will be overwritten to always connect to the reference)
  dut.io <> io; ref.io <> io

  // check to ensure that outputs are the same
  assert(ref.io.taken === dut.io.taken, "taken: expected: %d, actual: %d", ref.io.taken, dut.io.taken)
}


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.testers._
import chisel3.util._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

class CSRTester(c: => CSR, trace: Boolean = false) extends BasicTester with TestUtils {
  import Control._
  val dut = Module(c)
  val ctrl = Module(new Control)
  val xlen = dut.xlen

  override val insts =
    CSR.regs.map(csr => I(rand_fn3, 0, rand_rs1.litValue, csr.litValue)) ++
      CSR.regs.map(csr => SYS(Funct3.CSRRW, 0, csr, rand_rs1.litValue)) ++
      CSR.regs.map(csr => SYS(Funct3.CSRRS, 0, csr, rand_rs1.litValue)) ++
      CSR.regs.map(csr => SYS(Funct3.CSRRC, 0, csr, rand_rs1.litValue)) ++
      CSR.regs.map(csr => SYS(Funct3.CSRRWI, 0, csr, rand_rs1.litValue)) ++
      CSR.regs.map(csr => SYS(Funct3.CSRRSI, 0, csr, rand_rs1.litValue)) ++
      CSR.regs.map(csr => SYS(Funct3.CSRRCI, 0, csr, rand_rs1.litValue)) ++
      CSR.regs.map(csr => I(rand_fn3, 0, rand_rs1.litValue, csr.litValue)) ++ List[UInt](
      // system insts
      Instructions.ECALL,
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      Instructions.EBREAK,
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      Instructions.ERET,
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      // illegal addr
      J(rand_rd.litValue, rnd.nextInt()),
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      JR(rand_rd.litValue, rand_rs1.litValue, rnd.nextInt()),
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      L(Funct3.LW, rand_rd.litValue, rand_rs1.litValue, rand_rs2.litValue),
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      L(Funct3.LH, rand_rd.litValue, rand_rs1.litValue, rand_rs2.litValue),
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      L(Funct3.LHU, rand_rd.litValue, rand_rs1.litValue, rand_rs2.litValue),
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      S(Funct3.SW, rand_rd.litValue, rand_rs1.litValue, rand_rs2.litValue),
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      S(Funct3.SH, rand_rd.litValue, rand_rs1.litValue, rand_rs2.litValue),
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      // illegal inst
      rand_inst,
      SYS(Funct3.CSRRC, 0, CSR.mcause, 0),
      // Check counters
      SYS(Funct3.CSRRC, 0, CSR.time, 0),
      SYS(Funct3.CSRRC, 0, CSR.cycle, 0),
      SYS(Funct3.CSRRC, 0, CSR.instret, 0),
      SYS(Funct3.CSRRC, 0, CSR.mfromhost, 0)
    )

  val (cntr, done) = Counter(true.B, insts.size)
  val pc = Seq.fill(insts.size)(rnd.nextInt()).map(toBigInt)
  val addr = Seq.fill(insts.size)(rnd.nextInt()).map(toBigInt)
  val data = Seq.fill(insts.size)(rnd.nextInt()).map(toBigInt)
  val regs = CSR.regs
    .map(_.litValue)
    .map { addr =>
      addr -> RegInit(
        if (addr == CSR.mcpuid.litValue) (BigInt(1) << ('I' - 'A') | BigInt(1) << ('U' - 'A')).U(xlen.W)
        else if (addr == CSR.mstatus.litValue) (CSR.PRV_M.litValue << 4 | CSR.PRV_M.litValue << 1).U(xlen.W)
        else if (addr == CSR.mtvec.litValue) Consts.PC_EVEC.U(xlen.W)
        else 0.U(xlen.W)
      )
    }
    .toMap

  ctrl.io.inst := VecInit(insts)(cntr)
  dut.io.inst := ctrl.io.inst
  dut.io.cmd := ctrl.io.csr_cmd
  dut.io.illegal := ctrl.io.illegal
  dut.io.st_type := ctrl.io.st_type
  dut.io.ld_type := ctrl.io.ld_type
  dut.io.pc_check := ctrl.io.pc_sel === PC_ALU
  dut.io.pc := VecInit(pc.map(_.U))(cntr)
  dut.io.addr := VecInit(addr.map(_.U))(cntr)
  dut.io.in := VecInit(data.map(_.U))(cntr)

  dut.io.stall := false.B
  dut.io.host.fromhost.valid := false.B
  dut.io.host.fromhost.bits := 0.U

  // values known statically
  val _csr_addr = insts.map(csr)
  val _rs1_addr = insts.map(rs1)
  val _csr_ro = _csr_addr.map(x =>
    ((((x >> 11) & 0x1) > 0x0) && (((x >> 10) & 0x1) > 0x0)) ||
      x == CSR.mtvec.litValue || x == CSR.mtdeleg.litValue
  )
  val _csr_valid = _csr_addr.map(x => CSR.regs.exists(_.litValue == x))
  // should be <= prv in runtime
  val _prv_level = _csr_addr.map(x => (x >> 8) & 0x3)
  // should consider prv in runtime
  val _is_ecall = _csr_addr.map(x => ((x & 0x1) == 0x0) && (((x >> 8) & 0x1) == 0x0))
  val _is_ebreak = _csr_addr.map(x => ((x & 0x1) > 0x0) && (((x >> 8) & 0x1) == 0x0))
  val _is_eret = _csr_addr.map(x => ((x & 0x1) == 0x0) && (((x >> 8) & 0x1) > 0x0))
  // should consider pc_check in runtime
  val _iaddr_invalid = addr.map(x => ((x >> 1) & 0x1) > 0)
  // should consider ld_type & st_type
  val _waddr_invalid = addr.map(x => ((x >> 1) & 0x1) > 0 || (x & 0x1) > 0)
  val _haddr_invalid = addr.map(x => (x & 0x1) > 0)

  // values known in runtime
  val csr_addr = Wire(UInt())
  val rs1_addr = Wire(UInt())
  val csr_ro = Wire(Bool())
  val csr_valid = Wire(Bool())
  csr_addr := VecInit(_csr_addr.map(_.U))(cntr)
  rs1_addr := VecInit(_rs1_addr.map(_.U))(cntr)
  csr_ro := VecInit(_csr_ro.map(_.B))(cntr)
  csr_valid := VecInit(_csr_valid.map(_.B))(cntr)
  val wen = dut.io.cmd === CSR.W || dut.io.cmd(1) && rs1_addr =/= 0.U
  val prv1 = (regs(CSR.mstatus.litValue) >> 4.U).asUInt & 0x3.U
  val ie1 = (regs(CSR.mstatus.litValue) >> 3.U).asUInt & 0x1.U
  val prv = (regs(CSR.mstatus.litValue) >> 1.U).asUInt & 0x3.U
  val ie = regs(CSR.mstatus.litValue) & 0x1.U
  val prv_inst = dut.io.cmd === CSR.P
  val prv_valid = VecInit(_prv_level.map(_.U))(cntr) <= prv
  val iaddr_invalid = VecInit(_iaddr_invalid.map(_.B))(cntr) && dut.io.pc_check
  val laddr_invalid =
    VecInit(_haddr_invalid.map(_.B))(cntr) && (dut.io.ld_type === LD_LH || dut.io.ld_type === LD_LHU) ||
      VecInit(_waddr_invalid.map(_.B))(cntr) && (dut.io.ld_type === LD_LW)
  val saddr_invalid =
    VecInit(_haddr_invalid.map(_.B))(cntr) && dut.io.st_type === ST_SH ||
      VecInit(_waddr_invalid.map(_.B))(cntr) && dut.io.st_type === ST_SW
  val is_ecall = prv_inst && VecInit(_is_ecall.map(_.B))(cntr)
  val is_ebreak = prv_inst && VecInit(_is_ebreak.map(_.B))(cntr)
  val is_eret = prv_inst && VecInit(_is_eret.map(_.B))(cntr)
  val exception = dut.io.illegal || iaddr_invalid || laddr_invalid || saddr_invalid ||
    (((dut.io.cmd & 0x3.U) > 0.U) && (!csr_valid || !prv_valid)) ||
    (csr_ro && wen) || (prv_inst && !prv_valid) || is_ecall || is_ebreak
  val instret = dut.io.inst =/= nop && (!exception || is_ecall || is_ebreak)

  val rdata = Lookup(
    csr_addr,
    0.U,
    regs.toSeq.map {
      case (addr, reg) => BitPat(addr.U(12.W)) -> reg
    }
  )
  val wdata = Lookup(
    dut.io.cmd,
    0.U,
    Seq(
      BitPat(CSR.W) -> dut.io.in,
      BitPat(CSR.S) -> (dut.io.in | rdata),
      BitPat(CSR.C) -> ((~dut.io.in).asUInt & rdata)
    )
  )

  // compute state
  regs(CSR.time.litValue) := regs(CSR.time.litValue) + 1.U
  regs(CSR.timew.litValue) := regs(CSR.timew.litValue) + 1.U
  regs(CSR.mtime.litValue) := regs(CSR.mtime.litValue) + 1.U
  regs(CSR.cycle.litValue) := regs(CSR.cycle.litValue) + 1.U
  regs(CSR.cyclew.litValue) := regs(CSR.cyclew.litValue) + 1.U
  when(regs(CSR.time.litValue).andR) {
    regs(CSR.mtime.litValue) := regs(CSR.mtime.litValue) + 1.U
    regs(CSR.timeh.litValue) := regs(CSR.timeh.litValue) + 1.U
    regs(CSR.timehw.litValue) := regs(CSR.timehw.litValue) + 1.U
  }
  when(regs(CSR.cycle.litValue).andR) {
    regs(CSR.cycleh.litValue) := regs(CSR.cycleh.litValue) + 1.U
    regs(CSR.cyclehw.litValue) := regs(CSR.cyclehw.litValue) + 1.U
  }
  when(instret) {
    regs(CSR.instret.litValue) := regs(CSR.instret.litValue) + 1.U
    regs(CSR.instretw.litValue) := regs(CSR.instret.litValue) + 1.U
    when(regs(CSR.instret.litValue).andR) {
      regs(CSR.instreth.litValue) := regs(CSR.instreth.litValue) + 1.U
      regs(CSR.instrethw.litValue) := regs(CSR.instrethw.litValue) + 1.U
    }
  }

  when(exception) {
    regs(CSR.mepc.litValue) := (dut.io.pc >> 2.U) << 2.U
    regs(CSR.mcause.litValue) :=
      Mux(
        iaddr_invalid,
        Cause.InstAddrMisaligned,
        Mux(
          laddr_invalid,
          Cause.LoadAddrMisaligned,
          Mux(
            saddr_invalid,
            Cause.StoreAddrMisaligned,
            Mux(
              prv_inst && VecInit(is_ecall)(cntr),
              Cause.Ecall + prv,
              Mux(prv_inst && VecInit(is_ebreak)(cntr), Cause.Breakpoint, Cause.IllegalInst)
            )
          )
        )
      )
    regs(CSR.mstatus.litValue) := (prv << 4.U).asUInt | (ie << 3.U).asUInt | (CSR.PRV_M << 1.U).asUInt | 0.U
    when(iaddr_invalid || laddr_invalid || saddr_invalid) {
      regs(CSR.mbadaddr.litValue) := dut.io.addr
    }
  }.elsewhen(is_eret) {
    regs(CSR.mstatus.litValue) := (CSR.PRV_U << 4.U).asUInt | (1.U << 3.U).asUInt | (prv1 << 1.U).asUInt | ie1
  }.elsewhen(wen) {
    when(csr_addr === CSR.mstatus) {
      regs(CSR.mstatus.litValue) := wdata(5, 0)
    }.elsewhen(csr_addr === CSR.mip) {
      regs(CSR.mip.litValue) := (wdata(7) << 7.U).asUInt | (wdata(3) << 3.U).asUInt
    }.elsewhen(csr_addr === CSR.mie) {
      regs(CSR.mie.litValue) := (wdata(7) << 7.U).asUInt | (wdata(3) << 3.U).asUInt
    }.elsewhen(csr_addr === CSR.mepc) {
      regs(CSR.mepc.litValue) := (wdata >> 2.U) << 2.U
    }.elsewhen(csr_addr === CSR.mcause) {
      regs(CSR.mcause.litValue) := wdata & ((BigInt(1) << 31) | 0xf).U
    }.elsewhen(csr_addr === CSR.timew || csr_addr === CSR.mtime) {
      regs(CSR.time.litValue) := wdata
      regs(CSR.timew.litValue) := wdata
      regs(CSR.mtime.litValue) := wdata
    }.elsewhen(csr_addr === CSR.timehw || csr_addr === CSR.mtimeh) {
      regs(CSR.timeh.litValue) := wdata
      regs(CSR.timehw.litValue) := wdata
      regs(CSR.mtimeh.litValue) := wdata
    }.elsewhen(csr_addr === CSR.cyclew) {
      regs(CSR.cycle.litValue) := wdata
      regs(CSR.cyclew.litValue) := wdata
    }.elsewhen(csr_addr === CSR.cyclehw) {
      regs(CSR.cycleh.litValue) := wdata
      regs(CSR.cyclehw.litValue) := wdata
    }.elsewhen(csr_addr === CSR.instretw) {
      regs(CSR.instret.litValue) := wdata
      regs(CSR.instretw.litValue) := wdata
    }.elsewhen(csr_addr === CSR.instrethw) {
      regs(CSR.instreth.litValue) := wdata
      regs(CSR.instrethw.litValue) := wdata
    }.elsewhen(csr_addr === CSR.mtimecmp) {
      regs(CSR.mtimecmp.litValue) := wdata
    }.elsewhen(csr_addr === CSR.mscratch) {
      regs(CSR.mscratch.litValue) := wdata
    }.elsewhen(csr_addr === CSR.mbadaddr) {
      regs(CSR.mbadaddr.litValue) := wdata
    }.elsewhen(csr_addr === CSR.mtohost) {
      regs(CSR.mtohost.litValue) := wdata
    }.elsewhen(csr_addr === CSR.mfromhost) {
      regs(CSR.mfromhost.litValue) := wdata
    }
  }

  val epc = Wire(UInt())
  val evec = Wire(UInt())
  epc := regs(CSR.mepc.litValue)
  evec := regs(CSR.mtvec.litValue) + (prv << 6.U).asUInt

  when(cntr.orR) {
    assert(dut.io.out === rdata)
    assert(dut.io.epc === epc)
    assert(dut.io.evec === evec)
    assert(dut.io.expt === exception)
  }

  if (trace) {
    printf("*** Counter: %d ***\n", cntr)
    printf("[in] inst: 0x%x, pc: 0x%x, daddr: 0x%x, in: 0x%x\n", dut.io.inst, dut.io.pc, dut.io.addr, dut.io.in)
    printf(
      "     cmd: 0x%x, st_type: 0x%x, ld_type: 0x%x, illegal: %d, pc_check: %d\n",
      dut.io.cmd,
      dut.io.st_type,
      dut.io.ld_type,
      dut.io.illegal,
      dut.io.pc_check
    )
    printf("[state] csr addr: %x\n", csr_addr)
    regs.toSeq.sortWith(_._1 < _._1).foreach {
      case (addr, reg) => printf(s" ${addr.toString(16)} -> 0x%x\n", reg)
    }
    printf(
      "[out] read: 0x%x =? 0x%x, epc: 0x%x =? 0x%x, evec: 0x%x ?= 0x%x, expt: %d ?= %d\n",
      dut.io.out,
      rdata,
      dut.io.epc,
      epc,
      dut.io.evec,
      evec,
      dut.io.expt,
      exception
    )
  }
}

class CSRTests extends AnyFlatSpec with ChiselScalatestTester {
  "CSR" should "pass" in {
    test(new CSRTester(new CSR(xlen = 32))).runUntilStop()
  }
}