Merge pull request #676 from ucb-bar/diplomatic-clocks-pll-redux
Simple Divider-Only PLL for Multiclock RTL Simulation
This commit is contained in:
42
generators/chipyard/src/main/resources/vsrc/ClockDividerN.sv
Normal file
42
generators/chipyard/src/main/resources/vsrc/ClockDividerN.sv
Normal file
@@ -0,0 +1,42 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
/**
|
||||
* An unsynthesizable divide-by-N clock divider.
|
||||
* Duty cycle is 100 * (ceil(DIV / 2)) / DIV.
|
||||
*/
|
||||
|
||||
module ClockDividerN #(parameter DIV)(output logic clk_out = 1'b0, input clk_in);
|
||||
|
||||
localparam CWIDTH = $clog2(DIV);
|
||||
localparam LOW_CYCLES = DIV / 2;
|
||||
localparam HIGH_TRANSITION = LOW_CYCLES - 1;
|
||||
localparam LOW_TRANSITION = DIV - 1;
|
||||
|
||||
generate
|
||||
if (DIV == 1) begin
|
||||
// This needs to be procedural because of the assignment on declaration
|
||||
always @(clk_in) begin
|
||||
clk_out = clk_in;
|
||||
end
|
||||
end else begin
|
||||
reg [CWIDTH - 1: 0] count = HIGH_TRANSITION[CWIDTH-1:0];
|
||||
// The blocking assignment to clock out is used to conform what was done
|
||||
// in RC's clock dividers.
|
||||
// It should have the effect of preventing registers in the divided clock
|
||||
// domain latching register updates launched by the fast clock-domain edge
|
||||
// that occurs at the same simulated time (as the divided clock edge).
|
||||
always @(posedge clk_in) begin
|
||||
if (count == LOW_TRANSITION[CWIDTH-1:0]) begin
|
||||
clk_out = 1'b0;
|
||||
count <= '0;
|
||||
end
|
||||
else begin
|
||||
if (count == HIGH_TRANSITION[CWIDTH-1:0]) begin
|
||||
clk_out = 1'b1;
|
||||
end
|
||||
count <= count + 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
endmodule // ClockDividerN
|
||||
@@ -31,7 +31,7 @@ class ChipTop(implicit p: Parameters) extends LazyModule with HasTestHarnessFunc
|
||||
val lazySystem = LazyModule(p(BuildSystem)(p)).suggestName("system")
|
||||
|
||||
// The implicitClockSinkNode provides the implicit clock and reset for the System
|
||||
val implicitClockSinkNode = ClockSinkNode(Seq(ClockSinkParameters()))
|
||||
val implicitClockSinkNode = ClockSinkNode(Seq(ClockSinkParameters(name = Some("implicit_clock"))))
|
||||
|
||||
// Generate Clocks and Reset
|
||||
p(ClockingSchemeKey)(this)
|
||||
|
||||
@@ -6,12 +6,14 @@ import scala.collection.mutable.{ArrayBuffer}
|
||||
|
||||
import freechips.rocketchip.prci._
|
||||
import freechips.rocketchip.subsystem.{BaseSubsystem, SubsystemDriveAsyncClockGroupsKey}
|
||||
import freechips.rocketchip.config.{Parameters, Field}
|
||||
import freechips.rocketchip.config.{Parameters, Field, Config}
|
||||
import freechips.rocketchip.diplomacy.{OutwardNodeHandle, InModuleBody, LazyModule}
|
||||
import freechips.rocketchip.util.{ResetCatchAndSync, Pow2ClockDivider}
|
||||
|
||||
import barstools.iocell.chisel._
|
||||
|
||||
import chipyard.clocking.{DividerOnlyClockGenerator, ClockGroupNamePrefixer, ClockGroupFrequencySpecifier}
|
||||
|
||||
/**
|
||||
* Chipyard provides three baseline, top-level reset schemes, set using the
|
||||
* [[GlobalResetSchemeKey]] in a Parameters instance. These are:
|
||||
@@ -77,99 +79,61 @@ object GenerateReset {
|
||||
}
|
||||
|
||||
|
||||
case object ClockingSchemeKey extends Field[ChipTop => Unit](ClockingSchemeGenerators.harnessClock)
|
||||
case object ClockingSchemeKey extends Field[ChipTop => Unit](ClockingSchemeGenerators.dividerOnlyClockGenerator)
|
||||
/*
|
||||
* This is a Seq of assignment functions, that accept a clock name and return an optional frequency.
|
||||
* Functions that appear later in this seq have higher precedence that earlier ones.
|
||||
* If no function returns a non-empty value, the value specified in
|
||||
* [[DefaultClockFrequencyKey]] will be used.
|
||||
*/
|
||||
case object ClockFrequencyAssignersKey extends Field[Seq[(String) => Option[Double]]](Seq.empty)
|
||||
case object DefaultClockFrequencyKey extends Field[Double]()
|
||||
|
||||
class ClockNameMatchesAssignment(name: String, fMHz: Double) extends Config((site, here, up) => {
|
||||
case ClockFrequencyAssignersKey => up(ClockFrequencyAssignersKey, site) ++
|
||||
Seq((cName: String) => if (cName == name) Some(fMHz) else None)
|
||||
})
|
||||
|
||||
class ClockNameContainsAssignment(name: String, fMHz: Double) extends Config((site, here, up) => {
|
||||
case ClockFrequencyAssignersKey => up(ClockFrequencyAssignersKey, site) ++
|
||||
Seq((cName: String) => if (cName.contains(name)) Some(fMHz) else None)
|
||||
})
|
||||
|
||||
object ClockingSchemeGenerators {
|
||||
// A simple clock provider, for testing
|
||||
val harnessClock: ChipTop => Unit = { chiptop =>
|
||||
val dividerOnlyClockGenerator: ChipTop => Unit = { chiptop =>
|
||||
implicit val p = chiptop.p
|
||||
|
||||
val implicitClockSourceNode = ClockSourceNode(Seq(ClockSourceParameters()))
|
||||
chiptop.implicitClockSinkNode := implicitClockSourceNode
|
||||
|
||||
// Drive the diplomaticclock graph of the DigitalTop (if present)
|
||||
val simpleClockGroupSourceNode = chiptop.lazySystem match {
|
||||
case l: BaseSubsystem if (p(SubsystemDriveAsyncClockGroupsKey).isEmpty) => {
|
||||
val n = ClockGroupSourceNode(Seq(ClockGroupSourceParameters()))
|
||||
l.asyncClockGroupsNode := n
|
||||
Some(n)
|
||||
}
|
||||
case _ => None
|
||||
// Requires existence of undriven asyncClockGroups in subsystem
|
||||
val systemAsyncClockGroup = chiptop.lazySystem match {
|
||||
case l: BaseSubsystem if (p(SubsystemDriveAsyncClockGroupsKey).isEmpty) =>
|
||||
l.asyncClockGroupsNode
|
||||
}
|
||||
|
||||
val aggregator = LazyModule(new ClockGroupAggregator("allClocks")).node
|
||||
chiptop.implicitClockSinkNode := ClockGroup() := aggregator
|
||||
systemAsyncClockGroup := ClockGroupNamePrefixer() := aggregator
|
||||
|
||||
val referenceClockSource = ClockSourceNode(Seq(ClockSourceParameters()))
|
||||
(aggregator
|
||||
:= ClockGroupFrequencySpecifier(p(ClockFrequencyAssignersKey), p(DefaultClockFrequencyKey))
|
||||
:= DividerOnlyClockGenerator()
|
||||
:= referenceClockSource)
|
||||
|
||||
|
||||
InModuleBody {
|
||||
//this needs directionality so generateIOFromSignal works
|
||||
val clock_wire = Wire(Input(Clock()))
|
||||
val reset_wire = GenerateReset(chiptop, clock_wire)
|
||||
val (clock_io, clockIOCell) = IOCell.generateIOFromSignal(clock_wire, "clock")
|
||||
chiptop.iocells ++= clockIOCell
|
||||
|
||||
implicitClockSourceNode.out.unzip._1.map { o =>
|
||||
referenceClockSource.out.unzip._1.map { o =>
|
||||
o.clock := clock_wire
|
||||
o.reset := reset_wire
|
||||
}
|
||||
|
||||
simpleClockGroupSourceNode.map { n => n.out.unzip._1.map { out: ClockGroupBundle =>
|
||||
out.member.data.foreach { o =>
|
||||
o.clock := clock_wire
|
||||
o.reset := reset_wire
|
||||
}
|
||||
}}
|
||||
|
||||
chiptop.harnessFunctions += ((th: HasHarnessSignalReferences) => {
|
||||
clock_io := th.harnessClock
|
||||
Nil
|
||||
})
|
||||
Nil })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
val harnessDividedClock: ChipTop => Unit = { chiptop =>
|
||||
implicit val p = chiptop.p
|
||||
|
||||
require(false, "Divided clock is broken until we fix passing onchip clocks to TestHarness objects")
|
||||
|
||||
val implicitClockSourceNode = ClockSourceNode(Seq(ClockSourceParameters()))
|
||||
chiptop.implicitClockSinkNode := implicitClockSourceNode
|
||||
|
||||
val simpleClockGroupSourceNode = chiptop.lazySystem match {
|
||||
case l: BaseSubsystem if (p(SubsystemDriveAsyncClockGroupsKey).isEmpty) => {
|
||||
val n = ClockGroupSourceNode(Seq(ClockGroupSourceParameters()))
|
||||
l.asyncClockGroupsNode := n
|
||||
Some(n)
|
||||
}
|
||||
case _ => throw new Exception("Harness multiclock assumes BaseSubsystem")
|
||||
}
|
||||
|
||||
InModuleBody {
|
||||
// this needs directionality so generateIOFromSignal works
|
||||
val clock_wire = Wire(Input(Clock()))
|
||||
val reset_wire = GenerateReset(chiptop, clock_wire)
|
||||
val (clock_io, clockIOCell) = IOCell.generateIOFromSignal(clock_wire, "clock")
|
||||
chiptop.iocells ++= clockIOCell
|
||||
val div_clock = Pow2ClockDivider(clock_wire, 2)
|
||||
|
||||
implicitClockSourceNode.out.unzip._1.map { o =>
|
||||
o.clock := div_clock
|
||||
o.reset := reset_wire
|
||||
}
|
||||
|
||||
simpleClockGroupSourceNode.map { n => n.out.unzip._1.map { out: ClockGroupBundle =>
|
||||
out.member.elements.map { case (name, data) =>
|
||||
// This is mega hacks, how are you actually supposed to do this?
|
||||
data.clock := (if (name.contains("core")) clock_wire else div_clock)
|
||||
data.reset := reset_wire
|
||||
}
|
||||
}}
|
||||
|
||||
chiptop.harnessFunctions += ((th: HasHarnessSignalReferences) => {
|
||||
clock_io := th.harnessClock
|
||||
Nil
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@ import sifive.blocks.devices.gpio._
|
||||
import sifive.blocks.devices.uart._
|
||||
import sifive.blocks.devices.spi._
|
||||
|
||||
import chipyard.{BuildTop, BuildSystem, ClockingSchemeGenerators, ClockingSchemeKey, TestSuitesKey, TestSuiteHelper}
|
||||
|
||||
import chipyard._
|
||||
|
||||
// -----------------------
|
||||
// Common Config Fragments
|
||||
@@ -159,10 +158,6 @@ class WithNoSubsystemDrivenClocks extends Config((site, here, up) => {
|
||||
case SubsystemDriveAsyncClockGroupsKey => None
|
||||
})
|
||||
|
||||
class WithTileDividedClock extends Config((site, here, up) => {
|
||||
case ClockingSchemeKey => ClockingSchemeGenerators.harnessDividedClock
|
||||
})
|
||||
|
||||
class WithDMIDTM extends Config((site, here, up) => {
|
||||
case ExportDebug => up(ExportDebug, site).copy(protocols = Set(DMI))
|
||||
})
|
||||
@@ -170,3 +165,10 @@ class WithDMIDTM extends Config((site, here, up) => {
|
||||
class WithNoDebug extends Config((site, here, up) => {
|
||||
case DebugModuleKey => None
|
||||
})
|
||||
|
||||
class WithTileFrequency(fMHz: Double) extends ClockNameContainsAssignment("core", fMHz)
|
||||
|
||||
class WithPeripheryBusFrequencyAsDefault extends Config((site, here, up) => {
|
||||
case DefaultClockFrequencyKey => (site(PeripheryBusKey).dtsFrequency.get / (1000 * 1000)).toDouble
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
package chipyard.clocking
|
||||
|
||||
import chisel3._
|
||||
import chisel3.util._
|
||||
|
||||
class ClockDividerN(div: Int) extends BlackBox(Map("DIV" -> div)) with HasBlackBoxResource {
|
||||
require(div > 0);
|
||||
val io = IO(new Bundle {
|
||||
val clk_out = Output(Clock())
|
||||
val clk_in = Input(Clock())
|
||||
})
|
||||
addResource("/vsrc/ClockDividerN.sv")
|
||||
}
|
||||
|
||||
object ClockDivideByN {
|
||||
def apply(clockIn: Clock, div: Int): Clock = {
|
||||
val clockDivider = Module(new ClockDividerN(div))
|
||||
clockDivider.io.clk_in := clockIn
|
||||
clockDivider.io.clk_out
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package chipyard.clocking
|
||||
|
||||
import chisel3._
|
||||
|
||||
import freechips.rocketchip.config.{Parameters}
|
||||
import freechips.rocketchip.diplomacy._
|
||||
import freechips.rocketchip.prci._
|
||||
|
||||
/**
|
||||
* This sort of node can be used when it is a connectivity passthrough, but modifies
|
||||
* the flow of parameters (which may result in changing the names of the underlying signals).
|
||||
*/
|
||||
class ClockGroupParameterModifier(
|
||||
sourceFn: ClockGroupSourceParameters => ClockGroupSourceParameters = { m => m },
|
||||
sinkFn: ClockGroupSinkParameters => ClockGroupSinkParameters = { s => s })(
|
||||
implicit p: Parameters, v: ValName) extends LazyModule {
|
||||
val node = ClockGroupAdapterNode(sourceFn, sinkFn)
|
||||
lazy val module = new LazyRawModuleImp(this) {
|
||||
(node.out zip node.in).map { case ((o, _), (i, _)) =>
|
||||
(o.member.data zip i.member.data).foreach { case (oD, iD) => oD := iD }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes the ClockGroup's name into each member's name field as a prefix. This is
|
||||
* intended to be used before a ClockGroupAggregator so that sources from
|
||||
* different aggregated ClockGroups can be disambiguated by their names.
|
||||
*/
|
||||
object ClockGroupNamePrefixer {
|
||||
def apply()(implicit p: Parameters, valName: ValName): ClockGroupAdapterNode =
|
||||
LazyModule(new ClockGroupParameterModifier(sinkFn = { s => s.copy(members = s.members.zipWithIndex.map { case (m, idx) =>
|
||||
m.copy(name = m.name match {
|
||||
// This matches what the chisel would do if the names were not modified
|
||||
case Some(clockName) => Some(s"${s.name}_${clockName}")
|
||||
case None => Some(s"${s.name}_${idx}")
|
||||
})
|
||||
})})).node
|
||||
}
|
||||
|
||||
/**
|
||||
* [Word from on high is that Strings are in...]
|
||||
* Overrides the take field of all clocks in a group, by attempting to apply a
|
||||
* series of assignment functions:
|
||||
* (name: String) => freq-in-MHz: Option[Double]
|
||||
* to each sink. Later functions that return non-empty values take priority.
|
||||
* The default if all functions return None.
|
||||
*/
|
||||
object ClockGroupFrequencySpecifier {
|
||||
def apply(
|
||||
assigners: Seq[(String) => Option[Double]],
|
||||
defaultFreq: Double)(
|
||||
implicit p: Parameters, valName: ValName): ClockGroupAdapterNode = {
|
||||
|
||||
def lookupFrequencyForName(clock: ClockSinkParameters): ClockSinkParameters = {
|
||||
require(clock.name.nonEmpty, "All clocks in clock group must have an assigned name")
|
||||
val clockFreq = assigners.foldLeft(defaultFreq)(
|
||||
(currentFreq, candidateFunc) => candidateFunc(clock.name.get).getOrElse(currentFreq))
|
||||
|
||||
clock.copy(take = clock.take match {
|
||||
case Some(cp) =>
|
||||
println(s"Clock ${clock.name.get}: using diplomatically specified frequency of ${cp.freqMHz}.")
|
||||
Some(cp)
|
||||
case None => Some(ClockParameters(clockFreq))
|
||||
})
|
||||
}
|
||||
|
||||
LazyModule(new ClockGroupParameterModifier(sinkFn = { s => s.copy(members = s.members.map(lookupFrequencyForName)) })).node
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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 {
|
||||
def computeReferenceFrequencyMHz(
|
||||
requestedOutputs: Seq[ClockParameters],
|
||||
maximumAllowableDivisor: Int = 0xFFFF): ClockParameters = {
|
||||
require(requestedOutputs.nonEmpty)
|
||||
require(!requestedOutputs.contains(0.0))
|
||||
val freqs = requestedOutputs.map(f => BigInt(Math.round(f.freqMHz * 1000 * 1000)))
|
||||
val refFreq = freqs.reduce((a, b) => a * b / a.gcd(b)).toDouble / (1000 * 1000)
|
||||
assert((refFreq / freqs.min.toDouble) < maximumAllowableDivisor.toDouble)
|
||||
ClockParameters(refFreq)
|
||||
}
|
||||
}
|
||||
|
||||
class SimplePllConfiguration(name: String, val sinks: Seq[ClockSinkParameters]) {
|
||||
val referenceFreqMHz = FrequencyUtils.computeReferenceFrequencyMHz(sinks.flatMap(_.take)).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
|
||||
s" Output clock ${sink.name.get}, requested: ${requested} MHz, actual: ${actual} MHz (division of ${division})"
|
||||
}
|
||||
|
||||
val summaryString = preamble + outputSummaries.mkString("\n")
|
||||
ElaborationArtefacts.add(s"${name}.freq-summary", summaryString)
|
||||
println(summaryString)
|
||||
}
|
||||
|
||||
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(FrequencyUtils.computeReferenceFrequencyMHz(u.head.members.flatMap(_.take)))) }
|
||||
)
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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)
|
||||
|
||||
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))
|
||||
sinkB.reset := refClock.reset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object DividerOnlyClockGenerator {
|
||||
def apply()(implicit p: Parameters, valName: ValName) = LazyModule(new DividerOnlyClockGenerator(valName.name)).node
|
||||
}
|
||||
@@ -43,6 +43,7 @@ class AbstractConfig extends Config(
|
||||
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.WithPeripheryBusFrequencyAsDefault ++ // Unspecified clocks will match the frequency specified by the pbus dtsFrequency parameter
|
||||
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)
|
||||
|
||||
@@ -174,10 +174,8 @@ class MMIORocketConfig extends Config(
|
||||
new freechips.rocketchip.subsystem.WithNBigCores(1) ++
|
||||
new chipyard.config.AbstractConfig)
|
||||
|
||||
// NOTE: This config doesn't work yet because SimWidgets in the TestHarness
|
||||
// always get the TestHarness clock. The Tiles and Uncore receive the correct clocks
|
||||
class DividedClockRocketConfig extends Config(
|
||||
new chipyard.config.WithTileDividedClock ++ // Put the Tile on its own clock domain
|
||||
new chipyard.config.WithTileFrequency(200.0) ++
|
||||
new freechips.rocketchip.subsystem.WithRationalRocketTiles ++ // Add rational crossings between RocketTile and uncore
|
||||
new freechips.rocketchip.subsystem.WithNBigCores(1) ++
|
||||
new chipyard.config.AbstractConfig)
|
||||
|
||||
@@ -10,6 +10,7 @@ class AbstractTraceGenConfig extends Config(
|
||||
new chipyard.iobinders.WithTraceGenSuccessPunchthrough ++
|
||||
new chipyard.config.WithTracegenSystem ++
|
||||
new chipyard.config.WithNoSubsystemDrivenClocks ++
|
||||
new chipyard.config.WithPeripheryBusFrequencyAsDefault ++
|
||||
new freechips.rocketchip.subsystem.WithCoherentBusTopology ++
|
||||
new freechips.rocketchip.groundtest.GroundTestBaseConfig)
|
||||
|
||||
|
||||
@@ -69,9 +69,7 @@ class WithSerialBridge extends OverrideHarnessBinder({
|
||||
(system: CanHavePeripheryTLSerial, th: HasHarnessSignalReferences, ports: Seq[ClockedIO[SerialIO]]) => {
|
||||
ports.map { p =>
|
||||
val ram = SerialAdapter.connectHarnessRAM(system.serdesser.get, p, th.harnessReset)
|
||||
withClockAndReset(p.clock, th.harnessReset) {
|
||||
SerialBridge(p.clock, ram.module.io.tsi_ser, MainMemoryConsts.globalName)(GetSystemParameters(system))
|
||||
}
|
||||
SerialBridge(p.clock, ram.module.io.tsi_ser, MainMemoryConsts.globalName)(GetSystemParameters(system))
|
||||
}
|
||||
Nil
|
||||
}
|
||||
@@ -80,7 +78,7 @@ class WithSerialBridge extends OverrideHarnessBinder({
|
||||
class WithNICBridge extends OverrideHarnessBinder({
|
||||
(system: CanHavePeripheryIceNIC, th: HasHarnessSignalReferences, ports: Seq[ClockedIO[NICIOvonly]]) => {
|
||||
val p: Parameters = GetSystemParameters(system)
|
||||
ports.map { n => withClockAndReset(n.clock, th.harnessReset) { NICBridge(n.clock, n.bits)(p) } }
|
||||
ports.map { n => NICBridge(n.clock, n.bits)(p) }
|
||||
Nil
|
||||
}
|
||||
})
|
||||
@@ -120,11 +118,7 @@ class WithFASEDBridge extends OverrideHarnessBinder({
|
||||
|
||||
class WithTracerVBridge extends ComposeHarnessBinder({
|
||||
(system: CanHaveTraceIOModuleImp, th: HasHarnessSignalReferences, ports: Seq[TraceOutputTop]) => {
|
||||
ports.map { p =>
|
||||
p.traces.map(
|
||||
tileTrace => withClockAndReset(tileTrace.clock, tileTrace.reset) { TracerVBridge(tileTrace)(system.p) }
|
||||
)
|
||||
}
|
||||
ports.map { p => p.traces.map(tileTrace => TracerVBridge(tileTrace)(system.p)) }
|
||||
Nil
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,14 +8,15 @@ import chisel3.experimental.{IO}
|
||||
import freechips.rocketchip.prci._
|
||||
import freechips.rocketchip.subsystem.{BaseSubsystem, SubsystemDriveAsyncClockGroupsKey}
|
||||
import freechips.rocketchip.config.{Field, Config, Parameters}
|
||||
import freechips.rocketchip.diplomacy.{LazyModule, InModuleBody}
|
||||
import freechips.rocketchip.util.{ResetCatchAndSync}
|
||||
import freechips.rocketchip.diplomacy.{LazyModule, InModuleBody, ValName}
|
||||
import freechips.rocketchip.util.{ResetCatchAndSync, RecordMap}
|
||||
|
||||
import midas.widgets.{Bridge, PeekPokeBridge, RationalClockBridge, RationalClock}
|
||||
|
||||
import chipyard._
|
||||
import chipyard.harness._
|
||||
import chipyard.iobinders._
|
||||
import chipyard.clocking.{FrequencyUtils, ClockGroupNamePrefixer, ClockGroupFrequencySpecifier, SimplePllConfiguration}
|
||||
|
||||
// Determines the number of times to instantiate the DUT in the harness.
|
||||
// Subsumes legacy supernode support
|
||||
@@ -25,16 +26,6 @@ class WithNumNodes(n: Int) extends Config((pname, site, here) => {
|
||||
case NumNodes => n
|
||||
})
|
||||
|
||||
// Note, the main prerequisite for supporting an additional clock domain in a
|
||||
// FireSim simulation is to supply an additional clock parameter
|
||||
// (RationalClock) to the clock bridge (RationalClockBridge). The bridge
|
||||
// produces a vector of clocks, based on the provided parameter list, which you
|
||||
// may use freely without further modifications to your target design.
|
||||
case class FireSimClockParameters(additionalClocks: Seq[RationalClock]) {
|
||||
def numClocks(): Int = additionalClocks.size + 1
|
||||
}
|
||||
case object FireSimClockKey extends Field[FireSimClockParameters](FireSimClockParameters(Seq()))
|
||||
|
||||
// Hacky: Set before each node is generated. Ideally we'd give IO binders
|
||||
// accesses to the the Harness's parameters instance. We could then alter that.
|
||||
object NodeIdx {
|
||||
@@ -43,107 +34,113 @@ object NodeIdx {
|
||||
def apply(): Int = idx
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Under FireSim's current multiclock implementation there can be only a
|
||||
* single clock bridge. This requires, therefore, that it be instantiated in
|
||||
* the harness and reused across all supernode instances. This class attempts to
|
||||
* memoize its instantiation such that it can be referenced from within a ClockScheme function.
|
||||
*/
|
||||
class ClockBridgeInstantiator {
|
||||
private var _clockRecord: Option[RecordMap[Clock]] = None
|
||||
|
||||
def getClockRecord: RecordMap[Clock] = _clockRecord.get
|
||||
|
||||
def getClockRecordOrInstantiate(allClocks: Seq[RationalClock], baseClockName: String): RecordMap[Clock] = {
|
||||
if (_clockRecord.isEmpty) {
|
||||
require(allClocks.exists(_.name == baseClockName),
|
||||
s"Provided base-clock name, ${baseClockName}, does not match a defined clock. Available clocks:\n " +
|
||||
allClocks.map(_.name).mkString("\n "))
|
||||
|
||||
val baseClock = allClocks.find(_.name == baseClockName).get
|
||||
val simplified = allClocks.map { c =>
|
||||
c.copy(multiplier = c.multiplier * baseClock.divisor, divisor = c.divisor * baseClock.multiplier)
|
||||
.simplify
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes clocks that have the same frequency before instantiating the
|
||||
* clock bridge to avoid unnecessary BUFGCE use.
|
||||
*/
|
||||
val distinct = simplified.foldLeft(Seq(RationalClock(baseClockName, 1, 1))) { case (list, candidate) =>
|
||||
if (list.exists { clock => clock.equalFrequency(candidate) }) list else list :+ candidate
|
||||
}
|
||||
|
||||
val clockBridge = Module(new RationalClockBridge(distinct))
|
||||
val cbVecTuples = distinct.zip(clockBridge.io.clocks)
|
||||
val outputWire = Wire(RecordMap(simplified.map { c => (c.name, Clock()) }:_*))
|
||||
for (parameter <- simplified) {
|
||||
val (_, cbClockField) = cbVecTuples.find(_._1.equalFrequency(parameter)).get
|
||||
outputWire(parameter.name).get := cbClockField
|
||||
}
|
||||
_clockRecord = Some(outputWire)
|
||||
}
|
||||
getClockRecord
|
||||
}
|
||||
}
|
||||
|
||||
case object ClockBridgeInstantiatorKey extends Field[ClockBridgeInstantiator](new ClockBridgeInstantiator)
|
||||
case object FireSimBaseClockNameKey extends Field[String]("implicit_clock")
|
||||
|
||||
class WithFireSimSimpleClocks extends Config((site, here, up) => {
|
||||
case ClockingSchemeKey => { chiptop: ChipTop =>
|
||||
implicit val p = chiptop.p
|
||||
// Figure out what provides this in the chipyard scheme
|
||||
implicit val valName = ValName("FireSimClocking")
|
||||
|
||||
val implicitClockSourceNode = ClockSourceNode(Seq(ClockSourceParameters()))
|
||||
chiptop.implicitClockSinkNode := implicitClockSourceNode
|
||||
|
||||
// Drive the diplomaticclock graph of the DigitalTop (if present)
|
||||
val simpleClockGroupSourceNode = chiptop.lazySystem match {
|
||||
case l: BaseSubsystem if (p(SubsystemDriveAsyncClockGroupsKey).isEmpty) => {
|
||||
val n = ClockGroupSourceNode(Seq(ClockGroupSourceParameters()))
|
||||
l.asyncClockGroupsNode := n
|
||||
Some(n)
|
||||
}
|
||||
case _ => None
|
||||
// Requires existence of undriven asyncClockGroups in subsystem
|
||||
val systemAsyncClockGroup = chiptop.lazySystem match {
|
||||
case l: BaseSubsystem if (p(SubsystemDriveAsyncClockGroupsKey).isEmpty) =>
|
||||
l.asyncClockGroupsNode
|
||||
}
|
||||
|
||||
val aggregator = LazyModule(new ClockGroupAggregator("allClocks")).node
|
||||
(chiptop.implicitClockSinkNode := ClockGroup() := aggregator)
|
||||
(systemAsyncClockGroup := ClockGroupNamePrefixer() := aggregator)
|
||||
|
||||
val inputClockSource = ClockGroupSourceNode(Seq(ClockGroupSourceParameters()))
|
||||
|
||||
(aggregator
|
||||
:= ClockGroupFrequencySpecifier(p(ClockFrequencyAssignersKey), p(DefaultClockFrequencyKey))
|
||||
:= inputClockSource)
|
||||
|
||||
|
||||
InModuleBody {
|
||||
val clock = IO(Input(Clock())).suggestName("clock")
|
||||
val (clockGroupBundle, clockGroupEdge) = inputClockSource.out.head
|
||||
val input_clocks = IO(Input(RecordMap((clockGroupEdge.sink.members.map { m => (m.name.get, Clock()) }):_* )))
|
||||
.suggestName("clocks")
|
||||
val reset = IO(Input(Reset())).suggestName("reset")
|
||||
|
||||
implicitClockSourceNode.out.unzip._1.map { o =>
|
||||
o.clock := clock
|
||||
o.reset := reset
|
||||
(clockGroupBundle.member.data zip input_clocks.data).foreach { case (clockBundle, inputClock) =>
|
||||
clockBundle.clock := inputClock
|
||||
}
|
||||
|
||||
simpleClockGroupSourceNode.map { n => n.out.unzip._1.map { out: ClockGroupBundle =>
|
||||
out.member.data.foreach { o =>
|
||||
o.clock := clock
|
||||
o.reset := reset
|
||||
// Assign resets. The synchronization scheme is still WIP.
|
||||
for ((name, clockBundle) <- clockGroupBundle.member.elements) {
|
||||
if (name.contains("core")) {
|
||||
clockBundle.reset := ResetCatchAndSync(clockBundle.clock, reset.asBool)
|
||||
} else {
|
||||
clockBundle.reset := reset
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
val pllConfig = new SimplePllConfiguration("FireSim RationalClockBridge", clockGroupEdge.sink.members)
|
||||
val rationalClockSpecs = for ((sinkP, division) <- pllConfig.sinkDividerMap) yield {
|
||||
RationalClock(sinkP.name.get, 1, division)
|
||||
}
|
||||
|
||||
chiptop.harnessFunctions += ((th: HasHarnessSignalReferences) => {
|
||||
clock := th.harnessClock
|
||||
reset := th.harnessReset
|
||||
Nil
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
class WithFireSimRationalTileDomain(multiplier: Int, divisor: Int) extends Config((site, here, up) => {
|
||||
case FireSimClockKey => FireSimClockParameters(Seq(RationalClock("TileDomain", multiplier, divisor)))
|
||||
case ClockingSchemeKey => { chiptop: ChipTop =>
|
||||
implicit val p = chiptop.p
|
||||
|
||||
val implicitClockSourceNode = ClockSourceNode(Seq(ClockSourceParameters()))
|
||||
chiptop.implicitClockSinkNode := implicitClockSourceNode
|
||||
|
||||
// Drive the diplomaticclock graph of the DigitalTop (if present)
|
||||
val simpleClockGroupSourceNode = chiptop.lazySystem match {
|
||||
case l: BaseSubsystem if (p(SubsystemDriveAsyncClockGroupsKey).isEmpty) => {
|
||||
val n = ClockGroupSourceNode(Seq(ClockGroupSourceParameters()))
|
||||
l.asyncClockGroupsNode := n
|
||||
Some(n)
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
|
||||
InModuleBody {
|
||||
val uncore_clock = IO(Input(Clock())).suggestName("uncore_clock")
|
||||
val tile_clock = IO(Input(Clock())).suggestName("tile_clock")
|
||||
val reset = IO(Input(Reset())).suggestName("reset")
|
||||
|
||||
implicitClockSourceNode.out.unzip._1.map { o =>
|
||||
o.clock := uncore_clock
|
||||
o.reset := reset
|
||||
}
|
||||
|
||||
simpleClockGroupSourceNode.map { n => n.out.unzip._1.map { out: ClockGroupBundle =>
|
||||
out.member.elements.map { case (name, data) =>
|
||||
// This is mega hacks, how are you actually supposed to do this?
|
||||
if (name.contains("core")) {
|
||||
data.clock := tile_clock
|
||||
data.reset := ResetCatchAndSync(tile_clock, reset.asBool)
|
||||
} else {
|
||||
data.clock := uncore_clock
|
||||
data.reset := reset
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
chiptop.harnessFunctions += ((th: HasHarnessSignalReferences) => {
|
||||
uncore_clock := th.harnessClock
|
||||
reset := th.harnessReset
|
||||
th match {
|
||||
case f: FireSim => tile_clock := f.additionalClocks(0)
|
||||
case _ => throw new Exception("FireSimMultiClock must be used with FireSim")
|
||||
}
|
||||
Nil
|
||||
})
|
||||
input_clocks := p(ClockBridgeInstantiatorKey)
|
||||
.getClockRecordOrInstantiate(rationalClockSpecs.toSeq, p(FireSimBaseClockNameKey))
|
||||
Nil })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
class FireSim(implicit val p: Parameters) extends RawModule with HasHarnessSignalReferences {
|
||||
freechips.rocketchip.util.property.cover.setPropLib(new midas.passes.FireSimPropertyLibrary())
|
||||
val clockBridge = Module(new RationalClockBridge(p(FireSimClockKey).additionalClocks:_*))
|
||||
val harnessClock = clockBridge.io.clocks.head // This is the reference clock
|
||||
val additionalClocks = clockBridge.io.clocks.tail
|
||||
val harnessClock = Wire(Clock())
|
||||
val harnessReset = WireInit(false.B)
|
||||
val peekPokeBridge = PeekPokeBridge(harnessClock, harnessReset)
|
||||
def dutReset = { require(false, "dutReset should not be used in Firesim"); false.B }
|
||||
@@ -165,8 +162,7 @@ class FireSim(implicit val p: Parameters) extends RawModule with HasHarnessSigna
|
||||
d.harnessFunctions.foreach(_(this))
|
||||
ApplyHarnessBinders(this, d.lazySystem, p(HarnessBinders), d.portMap.toMap)
|
||||
}
|
||||
|
||||
|
||||
NodeIdx.increment()
|
||||
}
|
||||
harnessClock := p(ClockBridgeInstantiatorKey).getClockRecord("implicit_clock").get
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ class FireSimArianeConfig extends Config(
|
||||
//* Multiclock Configurations
|
||||
//*********************************************************************************/
|
||||
class FireSimMulticlockRocketConfig extends Config(
|
||||
new WithFireSimRationalTileDomain(2, 1) ++
|
||||
new chipyard.config.WithTileFrequency(6400.0) ++ //lol
|
||||
new WithDefaultFireSimBridges ++
|
||||
new WithDefaultMemModel ++
|
||||
new WithFireSimConfigTweaks ++
|
||||
|
||||
Submodule sims/firesim updated: 3dbe8aee3f...801baeb901
Reference in New Issue
Block a user