From f504b7a0f50d04eafaad400dc977c95b5398e2a5 Mon Sep 17 00:00:00 2001 From: David Biancolin Date: Tue, 3 Nov 2020 09:13:18 -0800 Subject: [PATCH] [clocking] Improve reference clock selection using a multiple-of-fastest strategy --- .../clocking/DividerOnlyClockGenerator.scala | 71 +++++++++++++++---- .../clocking/SimplePllConfigurationSpec.scala | 24 ++++--- .../firechip/src/main/scala/FireSim.scala | 1 + 3 files changed, 76 insertions(+), 20 deletions(-) diff --git a/generators/chipyard/src/main/scala/clocking/DividerOnlyClockGenerator.scala b/generators/chipyard/src/main/scala/clocking/DividerOnlyClockGenerator.scala index bbf05f38..2b666196 100644 --- a/generators/chipyard/src/main/scala/clocking/DividerOnlyClockGenerator.scala +++ b/generators/chipyard/src/main/scala/clocking/DividerOnlyClockGenerator.scala @@ -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 = { diff --git a/generators/chipyard/src/test/scala/clocking/SimplePllConfigurationSpec.scala b/generators/chipyard/src/test/scala/clocking/SimplePllConfigurationSpec.scala index 897ab0f4..0abe7c50 100644 --- a/generators/chipyard/src/test/scala/clocking/SimplePllConfigurationSpec.scala +++ b/generators/chipyard/src/test/scala/clocking/SimplePllConfigurationSpec.scala @@ -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) } diff --git a/generators/firechip/src/main/scala/FireSim.scala b/generators/firechip/src/main/scala/FireSim.scala index acbdbc6b..ff765970 100644 --- a/generators/firechip/src/main/scala/FireSim.scala +++ b/generators/firechip/src/main/scala/FireSim.scala @@ -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) }