248 lines
9.7 KiB
Scala
248 lines
9.7 KiB
Scala
package barstools.tapeout.transforms.clkgen
|
|
|
|
import net.jcazevedo.moultingyaml._
|
|
import firrtl.annotations._
|
|
import chisel3.experimental._
|
|
import chisel3._
|
|
import firrtl._
|
|
import firrtl.transforms.DedupModules
|
|
|
|
object ClkAnnotationsYaml extends DefaultYamlProtocol {
|
|
implicit val _clksrc = yamlFormat3(ClkSrc)
|
|
implicit val _sink = yamlFormat1(Sink)
|
|
implicit val _clkport = yamlFormat2(ClkPortAnnotation)
|
|
implicit val _genclk = yamlFormat4(GeneratedClk)
|
|
implicit val _clkmod = yamlFormat2(ClkModAnnotation)
|
|
}
|
|
case class ClkSrc(period: Double, waveform: Seq[Double] = Seq(), async: Seq[String] = Seq()) {
|
|
def getWaveform = if (waveform == Seq.empty) Seq(0, period/2) else waveform
|
|
// async = ids of top level clocks that are async with this clk
|
|
// Default is 50% duty cycle, period units is default
|
|
require(getWaveform.sorted == getWaveform, "Waveform edges must be in order")
|
|
require(getWaveform.length == 2, "Must specify time for rising edge, then time for falling edge")
|
|
}
|
|
|
|
case class Sink(src: Option[ClkSrc] = None)
|
|
|
|
case class ClkPortAnnotation(tag: Option[Sink] = None, id: String) {
|
|
import ClkAnnotationsYaml._
|
|
def serialize: String = this.toYaml.prettyPrint
|
|
}
|
|
|
|
abstract class ClkModType {
|
|
def serialize: String
|
|
}
|
|
case object ClkMux extends ClkModType {
|
|
def serialize: String = "mux"
|
|
}
|
|
case object ClkDiv extends ClkModType {
|
|
def serialize: String = "div"
|
|
}
|
|
case object ClkGen extends ClkModType {
|
|
def serialize: String = "gen"
|
|
}
|
|
|
|
// Unlike typical SDC, starts at 0.
|
|
// Otherwise, see pg. 63 of "Constraining Designs for Synthesis and Timing Analysis"
|
|
// by S. Gangadharan
|
|
// original clk: |-----|_____|-----|_____|
|
|
// edges: 0 1 2 3 4
|
|
// div. by 4, 50% duty cycle --> edges = 0, 2, 4
|
|
// ---> |-----------|___________|
|
|
// sources = source id's
|
|
case class GeneratedClk(
|
|
id: String,
|
|
sources: Seq[String] = Seq(),
|
|
referenceEdges: Seq[Int] = Seq(),
|
|
period: Option[Double] = None) {
|
|
require(referenceEdges.sorted == referenceEdges, "Edges must be in order for generated clk")
|
|
if (referenceEdges.nonEmpty) require(referenceEdges.length % 2 == 1, "# of reference edges must be odd!")
|
|
}
|
|
|
|
case class ClkModAnnotation(tpe: String, generatedClks: Seq[GeneratedClk]) {
|
|
|
|
def modType: ClkModType = HasClkAnnotation.modType(tpe)
|
|
|
|
modType match {
|
|
case ClkDiv =>
|
|
generatedClks foreach { c =>
|
|
require(c.referenceEdges.nonEmpty, "Reference edges must be defined for clk divider!")
|
|
require(c.sources.length == 1, "Clk divider output can only have 1 source")
|
|
require(c.period.isEmpty, "No period should be specified for clk divider output")
|
|
}
|
|
case ClkMux =>
|
|
generatedClks foreach { c =>
|
|
require(c.referenceEdges.isEmpty, "Reference edges must not be defined for clk mux!")
|
|
require(c.period.isEmpty, "No period should be specified for clk mux output")
|
|
require(c.sources.nonEmpty, "Clk muxes must have sources!")
|
|
}
|
|
case ClkGen =>
|
|
generatedClks foreach { c =>
|
|
require(c.referenceEdges.isEmpty, "Reference edges must not be defined for clk gen!")
|
|
require(c.sources.isEmpty, "Clk generators shouldn't have constrained sources")
|
|
require(c.period.nonEmpty, "Clk generator output period should be specified!")
|
|
}
|
|
}
|
|
import ClkAnnotationsYaml._
|
|
def serialize: String = this.toYaml.prettyPrint
|
|
}
|
|
|
|
abstract class FirrtlClkTransformAnnotation {
|
|
def targetName: String
|
|
}
|
|
|
|
// Firrtl version
|
|
case class TargetClkModAnnoF(target: ModuleName, anno: ClkModAnnotation) extends FirrtlClkTransformAnnotation with SingleTargetAnnotation[ModuleName] {
|
|
def duplicate(n: ModuleName): TargetClkModAnnoF = this.copy(target = n)
|
|
def getAnno = Annotation(target, classOf[ClkSrcTransform], anno.serialize)
|
|
def targetName = target.name
|
|
def modType = anno.modType
|
|
def generatedClks = anno.generatedClks
|
|
def getAllClkPorts = anno.generatedClks.map(x =>
|
|
List(List(x.id), x.sources).flatten).flatten.distinct.map(Seq(targetName, _).mkString("."))
|
|
}
|
|
|
|
// Chisel version
|
|
case class TargetClkModAnnoC(target: Module, anno: ClkModAnnotation) extends ChiselAnnotation {
|
|
def toFirrtl = TargetClkModAnnoF(target.toNamed, anno)
|
|
}
|
|
|
|
// Firrtl version
|
|
case class TargetClkPortAnnoF(target: ComponentName, anno: ClkPortAnnotation) extends FirrtlClkTransformAnnotation with SingleTargetAnnotation[ComponentName] {
|
|
def duplicate(n: ComponentName): TargetClkPortAnnoF = this.copy(target = n)
|
|
def getAnno = Annotation(target, classOf[ClkSrcTransform], anno.serialize)
|
|
def targetName = Seq(target.module.name, target.name).mkString(".")
|
|
def modId = Seq(target.module.name, anno.id).mkString(".")
|
|
def sink = anno.tag
|
|
}
|
|
|
|
// Chisel version
|
|
case class TargetClkPortAnnoC(target: Element, anno: ClkPortAnnotation) extends ChiselAnnotation {
|
|
def toFirrtl = TargetClkPortAnnoF(target.toNamed, anno)
|
|
}
|
|
|
|
object HasClkAnnotation {
|
|
|
|
import ClkAnnotationsYaml._
|
|
|
|
def modType(tpe: String): ClkModType = tpe match {
|
|
case s: String if s == ClkMux.serialize => ClkMux
|
|
case s: String if s == ClkDiv.serialize => ClkDiv
|
|
case s: String if s == ClkGen.serialize => ClkGen
|
|
case _ => throw new Exception("Clock module annotaiton type invalid")
|
|
}
|
|
|
|
def unapply(a: Annotation): Option[FirrtlClkTransformAnnotation] = a match {
|
|
case Annotation(f, t, s) if t == classOf[ClkSrcTransform] => f match {
|
|
case m: ModuleName =>
|
|
Some(TargetClkModAnnoF(m, s.parseYaml.convertTo[ClkModAnnotation]))
|
|
case c: ComponentName =>
|
|
Some(TargetClkPortAnnoF(c, s.parseYaml.convertTo[ClkPortAnnotation]))
|
|
case _ => throw new Exception("Clk source annotation only valid on module or component!")
|
|
}
|
|
case _ => None
|
|
}
|
|
|
|
def apply(annos: Seq[Annotation]): Option[(Seq[TargetClkModAnnoF],Seq[TargetClkPortAnnoF])] = {
|
|
// Get all clk-related annotations
|
|
val clkAnnos = annos.map(x => unapply(x)).flatten
|
|
val targets = clkAnnos.map(x => x.targetName)
|
|
require(targets.distinct.length == targets.length, "Only 1 clk related annotation is allowed per component/module")
|
|
if (clkAnnos.length == 0) None
|
|
else {
|
|
val componentAnnos = clkAnnos.filter {
|
|
case TargetClkPortAnnoF(ComponentName(_, ModuleName(_, _)), _) => true
|
|
case _ => false
|
|
}.map(x => x.asInstanceOf[TargetClkPortAnnoF])
|
|
val associatedMods = componentAnnos.map(x => x.target.module.name)
|
|
val moduleAnnos = clkAnnos.filter {
|
|
case TargetClkModAnnoF(ModuleName(m, _), _) =>
|
|
require(associatedMods contains m, "Clk modules should always have clk port annotations!")
|
|
true
|
|
case _ => false
|
|
}.map(x => x.asInstanceOf[TargetClkModAnnoF])
|
|
Some((moduleAnnos, componentAnnos))
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Applies to both black box + normal module
|
|
trait IsClkModule {
|
|
|
|
self: chisel3.Module =>
|
|
|
|
doNotDedup(this)
|
|
|
|
private def extractElementNames(signal: Data): Seq[String] = {
|
|
val names = signal match {
|
|
case elt: Record =>
|
|
elt.elements.map { case (key, value) => extractElementNames(value).map(x => key + "_" + x) }.toSeq.flatten
|
|
case elt: Vec[_] =>
|
|
elt.zipWithIndex.map { case (elt, i) => extractElementNames(elt).map(x => i + "_" + x) }.toSeq.flatten
|
|
case elt: Element => Seq("")
|
|
case elt => throw new Exception(s"Cannot extractElementNames for type ${elt.getClass}")
|
|
}
|
|
names.map(s => s.stripSuffix("_"))
|
|
}
|
|
|
|
// TODO: Replace!
|
|
def extractElements(signal: Data): Seq[Element] = {
|
|
signal match {
|
|
case elt: Record =>
|
|
elt.elements.map { case (key, value) => extractElements(value) }.toSeq.flatten
|
|
case elt: Vec[_] =>
|
|
elt.map { elt => extractElements(elt) }.toSeq.flatten
|
|
case elt: Element => Seq(elt)
|
|
case elt => throw new Exception(s"Cannot extractElements for type ${elt.getClass}")
|
|
}
|
|
}
|
|
|
|
def getIOName(signal: Element): String = {
|
|
val possibleNames = extractElements(io).zip(extractElementNames(io)).map {
|
|
case (sig, name) if sig == signal => Some(name)
|
|
case _ => None
|
|
}.flatten
|
|
if (possibleNames.length == 1) possibleNames.head
|
|
else throw new Exception("You can only get the name of an io port!")
|
|
}
|
|
|
|
def annotateDerivedClks(tpe: ClkModType, generatedClks: Seq[GeneratedClk]): Unit =
|
|
annotateDerivedClks(ClkModAnnotation(tpe.serialize, generatedClks))
|
|
def annotateDerivedClks(anno: ClkModAnnotation): Unit = annotateDerivedClks(this, anno)
|
|
def annotateDerivedClks(m: Module, anno: ClkModAnnotation): Unit =
|
|
annotate(TargetClkModAnnoC(m, anno))
|
|
|
|
def annotateClkPort(p: Element): Unit = annotateClkPort(p, None, "")
|
|
def annotateClkPort(p: Element, sink: Sink): Unit = annotateClkPort(p, Some(sink), "")
|
|
def annotateClkPort(p: Element, id: String): Unit = annotateClkPort(p, None, id)
|
|
def annotateClkPort(p: Element, sink: Sink, id: String): Unit = annotateClkPort(p, Some(sink), id)
|
|
def annotateClkPort(p: Element, sink: Option[Sink], id: String): Unit = {
|
|
// If no id is specified, it'll try to figure out a name, assuming p is an io port
|
|
val newId = id match {
|
|
case "" =>
|
|
getIOName(p)
|
|
case _ => id
|
|
}
|
|
annotateClkPort(p, ClkPortAnnotation(sink, newId))
|
|
}
|
|
|
|
def annotateClkPort(p: Element, anno: ClkPortAnnotation): Unit = {
|
|
DataMirror.directionOf(p) match {
|
|
case chisel3.core.ActualDirection.Input =>
|
|
require(anno.tag.nonEmpty, "Module inputs must be clk sinks")
|
|
require(anno.tag.get.src.isEmpty,
|
|
"Clock module (not top) input clks should not have clk period, etc. specified")
|
|
case chisel3.core.ActualDirection.Output =>
|
|
require(anno.tag.isEmpty, "Module outputs must not be clk sinks (they're sources!)")
|
|
case _ =>
|
|
throw new Exception("Clk port direction must be specified!")
|
|
}
|
|
p match {
|
|
case _: chisel3.core.Clock =>
|
|
case _ => throw new Exception("Clock port must be of type Clock")
|
|
}
|
|
annotate(TargetClkPortAnnoC(p, anno))
|
|
}
|
|
}
|