WIP - Simple divider-only PLL generation flow

This commit is contained in:
David Biancolin
2020-08-24 11:01:29 -07:00
parent 23a199eccf
commit 895bacea98
9 changed files with 319 additions and 2 deletions

View File

@@ -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

View File

@@ -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 })
}
}
}

View File

@@ -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
})

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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"))
}

View File

@@ -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
}

View File

@@ -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)