[clocking] Improve reference clock selection using a multiple-of-fastest strategy
This commit is contained in:
@@ -14,21 +14,65 @@ import scala.collection.immutable.ListMap
|
||||
* TODO: figure out how much division is acceptable in our simulators and redefine this.
|
||||
*/
|
||||
object FrequencyUtils {
|
||||
def computeReferenceFrequencyMHz(
|
||||
/**
|
||||
* 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 = 8000.0): ClockParameters = {
|
||||
maximumAllowableFreqMHz: Double,
|
||||
relativeThreshold: Double = 1.10,
|
||||
absoluteThreshold: Double = 0.01): 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 < maximumAllowableFreqMHz,
|
||||
s"Reference frequency ${refFreq} exceeds maximum allowable value of ${maximumAllowableFreqMHz} MHz")
|
||||
ClockParameters(refFreq)
|
||||
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]) {
|
||||
val referenceFreqMHz = FrequencyUtils.computeReferenceFrequencyMHz(sinks.flatMap(_.take)).freqMHz
|
||||
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"""
|
||||
@@ -41,8 +85,10 @@ class SimplePllConfiguration(name: String, val sinks: Seq[ClockSinkParameters])
|
||||
}
|
||||
|
||||
val summaryString = preamble + outputSummaries.mkString("\n")
|
||||
ElaborationArtefacts.add(s"${name}.freq-summary", summaryString)
|
||||
println(summaryString)
|
||||
def emitSummaries(): Unit = {
|
||||
ElaborationArtefacts.add(s"${name}.freq-summary", summaryString)
|
||||
println(summaryString)
|
||||
}
|
||||
}
|
||||
|
||||
case class DividerOnlyClockGeneratorNode(pllName: String)(implicit valName: ValName)
|
||||
@@ -54,7 +100,7 @@ case class DividerOnlyClockGeneratorNode(pllName: String)(implicit valName: ValN
|
||||
"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)))) }
|
||||
take = Some(ClockParameters(new SimplePllConfiguration(pllName, u.head.members).referenceFreqMHz))) }
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -79,6 +125,7 @@ class DividerOnlyClockGenerator(pllName: String)(implicit p: Parameters, valName
|
||||
|
||||
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 = {
|
||||
|
||||
@@ -5,17 +5,25 @@ import freechips.rocketchip.prci._
|
||||
|
||||
class SimplePllConfigurationSpec extends org.scalatest.FlatSpec {
|
||||
|
||||
def conf(freqMHz: Iterable[Double]): SimplePllConfiguration = new SimplePllConfiguration("test",
|
||||
def genConf(freqMHz: Iterable[Double]): SimplePllConfiguration = new SimplePllConfiguration(
|
||||
"testPLL",
|
||||
freqMHz.map({ f => ClockSinkParameters(
|
||||
name = Some(s"desiredFreq_$f"),
|
||||
take = Some(ClockParameters(f))) }).toSeq)
|
||||
take = Some(ClockParameters(f))) }).toSeq,
|
||||
maximumAllowableFreqMHz = 16000.0)
|
||||
|
||||
def tryConf(freqMHz: Double*): Unit = {
|
||||
val freqStr = freqMHz.mkString(", ")
|
||||
it should s"configure for ${freqStr} MHz" in { conf(freqMHz) }
|
||||
def trySuccessfulConf(requestedFreqs: Seq[Double], expected: Double): Unit = {
|
||||
val freqStr = requestedFreqs.mkString(", ")
|
||||
it should s"select a reference of ${expected} MHz for ${freqStr} MHz" in {
|
||||
val conf = genConf(requestedFreqs)
|
||||
conf.emitSummaries
|
||||
assert(expected == conf.referenceFreqMHz)
|
||||
}
|
||||
}
|
||||
|
||||
tryConf(3200.0, 1600.0, 1000.0, 100.0)
|
||||
tryConf(3200.0, 1600.0)
|
||||
tryConf(3200.0, 1066.7)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ class WithFireSimSimpleClocks extends Config((site, here, up) => {
|
||||
}
|
||||
|
||||
val pllConfig = new SimplePllConfiguration("FireSim RationalClockBridge", clockGroupEdge.sink.members)
|
||||
pllConfig.emitSummaries
|
||||
val rationalClockSpecs = for ((sinkP, division) <- pllConfig.sinkDividerMap) yield {
|
||||
RationalClock(sinkP.name.get, 1, division)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user