[clocking] Improve reference clock selection using a multiple-of-fastest strategy

This commit is contained in:
David Biancolin
2020-11-03 09:13:18 -08:00
parent aa4a44925e
commit f504b7a0f5
3 changed files with 76 additions and 20 deletions

View File

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

View File

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

View File

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