diff --git a/generators/chipyard/src/main/resources/vsrc/ClockDividerN.sv b/generators/chipyard/src/main/resources/vsrc/ClockDividerN.sv new file mode 100644 index 00000000..33f7a05b --- /dev/null +++ b/generators/chipyard/src/main/resources/vsrc/ClockDividerN.sv @@ -0,0 +1,40 @@ +// See LICENSE for license details. + +/** + * An unsynthesizable divide-by-N clock divider. + * Duty cycle is 100 * (ceil(DIV / 2)) / 2. + */ + +module ClockDividerN #(parameter DIV)(output logic clk_out = 1'b0, input clk_in); + + localparam DIV_COUNTER_WIDTH = $clog2(DIV); + localparam LOW_CYCLES = DIV / 2; + + 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 [DIV_COUNTER_WIDTH - 1: 0] count = '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 == (DIV - 1)) begin + clk_out = 1'b0; + count <= '0; + end + else begin + if (count == (LOW_CYCLES - 1)) begin + clk_out = 1'b1; + end + count <= count + 1'b1; + end + end + end + endgenerate +endmodule // ClockDividerN diff --git a/generators/chipyard/src/main/scala/Clocks.scala b/generators/chipyard/src/main/scala/Clocks.scala index a1ff1b0f..01d6c828 100644 --- a/generators/chipyard/src/main/scala/Clocks.scala +++ b/generators/chipyard/src/main/scala/Clocks.scala @@ -12,6 +12,8 @@ import freechips.rocketchip.util.{ResetCatchAndSync, Pow2ClockDivider} import barstools.iocell.chisel._ +import chipyard.clocking.{IdealizedPLL, ClockGroupDealiaser} + /** * Chipyard provides three baseline, top-level reset schemes, set using the * [[GlobalResetSchemeKey]] in a Parameters instance. These are: @@ -173,6 +175,40 @@ object ClockingSchemeGenerators { Nil }) } + } + val idealizedPLL: ChipTop => Unit = { chiptop => + implicit val p = chiptop.p + + // Requires existence of undriven asyncClockGroups in subsystem + val systemAsyncClockGroup = chiptop.lSystem match { + case l: BaseSubsystem if (p(SubsystemDriveAsyncClockGroupsKey).isEmpty) => + l.asyncClockGroupsNode + } + + val aggregator = ClockGroupAggregator() + chiptop.implicitClockSinkNode := ClockGroup() := aggregator + systemAsyncClockGroup := aggregator + + val referenceClockSource = ClockSourceNode(Seq(ClockSourceParameters())) + aggregator := ClockGroupDealiaser() := IdealizedPLL() := referenceClockSource + + InModuleBody { + + val clock_wire = Wire(Input(Clock())) + val reset_wire = GenerateReset(chiptop, clock_wire) + val (clock_io, clockIOCell) = IOCell.generateIOFromSignal(clock_wire, Some("iocell_clock")) + chiptop.iocells ++= clockIOCell + clock_io.suggestName("clock") + + referenceClockSource.out.unzip._1.map { o => + o.clock := clock_wire + o.reset := reset_wire + } + + chiptop.harnessFunctions += ((th: HasHarnessUtils) => { + clock_io := th.harnessClock + Nil }) + } } } diff --git a/generators/chipyard/src/main/scala/ConfigFragments.scala b/generators/chipyard/src/main/scala/ConfigFragments.scala index cfa465e7..9fb3adb2 100644 --- a/generators/chipyard/src/main/scala/ConfigFragments.scala +++ b/generators/chipyard/src/main/scala/ConfigFragments.scala @@ -28,7 +28,11 @@ import sifive.blocks.devices.spi._ import chipyard.{BuildTop, BuildSystem, ClockingSchemeGenerators, ClockingSchemeKey, TestSuitesKey, TestSuiteHelper} - +// Imports for multiclock sketch +import boom.common.{BoomTile, BoomTileParams} +import ariane.{ArianeTile, ArianeTileParams} +import chipyard.{GenericallyAttachableTile, GenericCrossingParams} +import chipyard.clocking.{ClockNodeInjectionUtils } // ----------------------- // Common Config Fragments // ----------------------- @@ -170,3 +174,24 @@ class WithDMIDTM extends Config((site, here, up) => { class WithNoDebug extends Config((site, here, up) => { case DebugModuleKey => None }) + + +// Multiclock sketch +class WithForcedTileFrequency(fMHz: Double) extends Config((site, here, up) => { + case TilesLocated(InSubsystem) => + val genericAttachParams = up(TilesLocated(InSubsystem), site) map { + case b: BoomTileAttachParams => GenericallyAttachableTile[BoomTile]( + b.tileParams, GenericCrossingParams(b.crossingParams), b.lookup) + case r: RocketTileAttachParams => GenericallyAttachableTile[RocketTile]( + r.tileParams, GenericCrossingParams(r.crossingParams), r.lookup) + case a: ArianeTileAttachParams => GenericallyAttachableTile[ArianeTile]( + a.tileParams, GenericCrossingParams(a.crossingParams), a.lookup) + case g: GenericallyAttachableTile[_] => g + } + genericAttachParams.map(p => p.copy(crossingParams = p.crossingParams.copy( + injectClockNodeFunc = ClockNodeInjectionUtils.forceTakeFrequency(fMHz)))) +}) + +class WithIdealizedPLL extends Config((site, here, up) => { + case ChipyardClockKey => ClockDrivers.idealizedPLL +}) diff --git a/generators/chipyard/src/main/scala/GenericAttachParams.scala b/generators/chipyard/src/main/scala/GenericAttachParams.scala new file mode 100644 index 00000000..f90598af --- /dev/null +++ b/generators/chipyard/src/main/scala/GenericAttachParams.scala @@ -0,0 +1,38 @@ + +package chipyard + +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.config.{Field, Parameters} +import freechips.rocketchip.subsystem._ +import freechips.rocketchip.tile.{LookupByHartIdImpl, TileParams, InstantiableTileParams, BaseTile} + +import chipyard.clocking.ClockNodeInjectionUtils._ + +case class GenericCrossingParams( + crossingType: ClockCrossingType = SynchronousCrossing(), + master: TilePortParamsLike = TileMasterPortParams(), + slave: TilePortParamsLike = TileSlavePortParams(), + mmioBaseAddressPrefixWhere: TLBusWrapperLocation = CBUS, + injectClockNodeFunc: InjectClockNodeFunc = injectIdentityClockNode, + forceSeparateClockReset: Boolean = false) extends TileCrossingParamsLike { + + def injectClockNode(a: Attachable)(implicit p: Parameters) = injectClockNodeFunc(a, p) +} + +object GenericCrossingParams { + def apply(params: TileCrossingParamsLike): GenericCrossingParams = GenericCrossingParams( + params.crossingType, + params.master, + params.slave, + params.mmioBaseAddressPrefixWhere, + (a: Attachable, p: Parameters) => params.injectClockNode(a)(p), + params.forceSeparateClockReset) +} + +case class GenericallyAttachableTile[TT <: BaseTile]( + tileParams: InstantiableTileParams[TT], + crossingParams: GenericCrossingParams, + lookup: LookupByHartIdImpl) extends CanAttachTile { + type TileType = TT +} + diff --git a/generators/chipyard/src/main/scala/clocking/ClockDividerN.scala b/generators/chipyard/src/main/scala/clocking/ClockDividerN.scala new file mode 100644 index 00000000..c9513b88 --- /dev/null +++ b/generators/chipyard/src/main/scala/clocking/ClockDividerN.scala @@ -0,0 +1,23 @@ +// See LICENSE.SiFive 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 + } +} diff --git a/generators/chipyard/src/main/scala/clocking/ClockGroupDealiaser.scala b/generators/chipyard/src/main/scala/clocking/ClockGroupDealiaser.scala new file mode 100644 index 00000000..3ffea3c0 --- /dev/null +++ b/generators/chipyard/src/main/scala/clocking/ClockGroupDealiaser.scala @@ -0,0 +1,51 @@ +package chipyard.clocking + +import chisel3._ + +import freechips.rocketchip.config.{Parameters} +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.prci._ + +/** + * Somewhat hacky. Since not all clocks in a clock group specify a taken frequency + * current, this LazyModule attempts to dealias them, by finding a specified + * clock whose name has the longest matching prefix. + * + * Perhaps another, simpler solution would be to pass a default. + * + */ + +case class ClockGroupDealiaserNode()(implicit valName: ValName) + extends NexusNode(ClockGroupImp)( + dFn = { _ => ClockGroupSourceParameters() }, + uFn = { u => + require(u.size == 1) + val takenClocks = u.head.members.filter(_.take.nonEmpty) + require(takenClocks.nonEmpty, + "At least one sink clock in clock group must specify its take parameter") + u.head.copy(members = takenClocks) + }) + +class ClockGroupDealiaser(name: String)(implicit p: Parameters) extends LazyModule { + val node = ClockGroupDealiaserNode() + + lazy val module = new LazyRawModuleImp(this) { + require(node.out.size == 1, "Must use a ClockGroupAggregator") + val (outClocks, e @ ClockGroupEdgeParameters(_, outSinkParams, _, _)) = node.out.head + val (inClocks, ClockGroupEdgeParameters(_, inSinkParams, _, _)) = node.in.head + val inMap = inClocks.member.data.zip(inSinkParams.members).map({ case (b, p) => p.name -> b}).toMap + + for (((outBName, outB), outName) <- outClocks.member.elements.zip(outSinkParams.members.map(_.name))) { + val inClock = inMap.getOrElse(outName, throw new Exception(""" + | No clock in input group with name: Option matching ${outName}. At least one clock + | with the same must specify a frequency in its take parameter.""".stripMargin)) + // This will be removed. + dontTouch(outB) + outB := inClock + } + } +} + +object ClockGroupDealiaser { + def apply()(implicit p: Parameters, valName: ValName) = LazyModule(new ClockGroupDealiaser(valName.name)).node +} diff --git a/generators/chipyard/src/main/scala/clocking/ClockNodeInjectors.scala b/generators/chipyard/src/main/scala/clocking/ClockNodeInjectors.scala new file mode 100644 index 00000000..981ed327 --- /dev/null +++ b/generators/chipyard/src/main/scala/clocking/ClockNodeInjectors.scala @@ -0,0 +1,29 @@ + +package chipyard.clocking + +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.config.{Parameters} +import freechips.rocketchip.subsystem._ +import freechips.rocketchip.prci.{ClockNode, ClockTempNode, ClockAdapterNode, ClockParameters} +/** + * An adapter node hack c that just throws out the existing sink node + * clock parameters in favor of the provided ones. + */ +class ForceTakeClock(clockParams: Option[ClockParameters])(implicit p: Parameters, v: ValName) extends LazyModule { + val node = ClockAdapterNode(sinkFn = { s => s.copy(take = clockParams) }) + lazy val module = new LazyRawModuleImp(this) { + (node.out zip node.in) map { case ((o, _), (i, _)) => o := i } + } +} + +object ForceTakeClock { + def apply(clockParams: Option[ClockParameters])(implicit p: Parameters, v: ValName): ClockAdapterNode = + LazyModule(new ForceTakeClock(clockParams)).node +} + +object ClockNodeInjectionUtils { + type InjectClockNodeFunc = (Attachable, Parameters) => ClockNode + val injectIdentityClockNode: InjectClockNodeFunc = (a: Attachable, p: Parameters) => ClockTempNode() + def forceTakeFrequency(freqMHz: Double): InjectClockNodeFunc = + (a: Attachable, p: Parameters) => ForceTakeClock(Some(ClockParameters(freqMHz)))(p, ValName("ForcedTakeClock")) +} diff --git a/generators/chipyard/src/main/scala/clocking/IdealizedPLL.scala b/generators/chipyard/src/main/scala/clocking/IdealizedPLL.scala new file mode 100644 index 00000000..3de99e8e --- /dev/null +++ b/generators/chipyard/src/main/scala/clocking/IdealizedPLL.scala @@ -0,0 +1,68 @@ +package chipyard.clocking + +import chisel3._ + +import freechips.rocketchip.config.{Parameters} +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.prci._ + +import scala.collection.mutable + +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) + } +} + +case class IdealizedPLLNode(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)))) } + ) + +class IdealizedPLL(pllName: String)(implicit p: Parameters, valName: ValName) extends LazyModule { + val node = IdealizedPLLNode(pllName) + + lazy val module = new LazyRawModuleImp(this) { + require(node.out.size == 1, "Must use a ClockGroupAggregator") + val (refClock, ClockEdgeParameters(_, refSinkParam, _, _)) = node.in.head + val (outClocks, ClockGroupEdgeParameters(_, outSinkParams, _, _)) = node.out.head + + val referenceFreq = refSinkParam.take.get.freqMHz + val requestedFreqs = outSinkParams.members.map(m => m.name -> m.take) + + 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 requested = sinkP.take.get.freqMHz + val div = Math.round(referenceFreq / requested).toInt + val actual = referenceFreq / div.toDouble + println(s"Clock ${sinkBName}, requested freq: ${requested} MHz. Actual freq: ${actual} MHz via division of ${div}") + sinkB.clock := dividedClocks.getOrElse(div, instantiateDivider(div)) + } + } +} + +object IdealizedPLL { + def apply()(implicit p: Parameters, valName: ValName) = LazyModule(new IdealizedPLL(valName.name)).node +} diff --git a/generators/chipyard/src/main/scala/config/RocketConfigs.scala b/generators/chipyard/src/main/scala/config/RocketConfigs.scala index 16d298fb..a4e1af72 100644 --- a/generators/chipyard/src/main/scala/config/RocketConfigs.scala +++ b/generators/chipyard/src/main/scala/config/RocketConfigs.scala @@ -182,4 +182,11 @@ class DividedClockRocketConfig extends Config( new freechips.rocketchip.subsystem.WithNBigCores(1) ++ new chipyard.config.AbstractConfig) - +// Multiclock Sketch +class ForcedClockRocketConfig extends Config( + new chipyard.config.WithForcedTileFrequency(200) ++ + new chipyard.config.WithIdealizedPLL ++ // Put the Tile on its own clock domain + //new chipyard.config.WithTileDividedClock ++ // Put the Tile on its own clock domain + new freechips.rocketchip.subsystem.WithRationalRocketTiles ++ // Add rational crossings between RocketTile and uncore + new freechips.rocketchip.subsystem.WithNBigCores(1) ++ + new chipyard.config.AbstractConfig)