Add Pads + other utilities (#7)
[stevo]: adds a bunch of pad frame commits, as well as beginning work on clocking annotations and constraints * start add io pads pass * save progress adding yaml pad info * saving some semi-presentable work -- parses yaml for pad templates and associates templates with ports * added black boxes to the module; still need to hook up * added supply pad yaml example; added option to not include pad for an IO, blackboxed that cat + bit extraction functions * rewrite createbbs and some other parts of the transform * finally got blackboxhelper to work -- seems there was a typo in the firrtl pass (?) have not connected them up properly in the padframe * finished first version of pad transform; need to add bells and whistles + special case stuff * made a bunch of changes in firrtl to shorthand things * done with padframe for signals * started major refactoring; first of pad yaml stuff * forgot to update verilogTemplate -> verilog * rename ParsePadYaml -> ChipPadsYaml; moved some stuff * separated out stuff that describes pads i.e. direction, type, side * forgot to update import for yamlhelpers * trying to make the process of creating annotations more structured * saving annotation helpers but prob better to switch to yaml * saving changes -- reworking annotations * fixing some bugs; properly annotated ports with pads * annotate supply pads * lesson (re)learned. cleaned up constants * finished adding supply pads to pad frame; still need to generate io file * also committing updated transform; still without io file * big typo was causing pad verilog files not to be generated * verilator passes with transform; had to fix verilog bb typo * added unused pads; added more thorough tests + did visual inspection of output; made some port types more explicit * renamed files/classes to be clearer * started creating pad io template * update spec so that transform order matters * get rid of logger * went around in circles with blackboxhelper + way to annotate * finished adding + testing pad.io creation * starting clkgen pass -- made model for asynchronously reset clk divider + wrappers for programmatic bundling * temporarily locating albert's utility functions here * saving work on clk constraints * redid input config passing -- pass in tech directory instead; seems like getting clk sink, src, and relationship works * not done; need to pause to do tapeout-y things. the clk gen pass gets all the clks and their sources, but i need to build a proper graph to handle clks coming out of muxes
This commit is contained in:
248
tapeout/src/main/scala/transforms/clkgen/ClkAnnotations.scala
Normal file
248
tapeout/src/main/scala/transforms/clkgen/ClkAnnotations.scala
Normal file
@@ -0,0 +1,248 @@
|
||||
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 {
|
||||
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) {
|
||||
def getAnno = ChiselAnnotation(target, classOf[ClkSrcTransform], anno.serialize)
|
||||
}
|
||||
|
||||
// Firrtl version
|
||||
case class TargetClkPortAnnoF(target: ComponentName, anno: ClkPortAnnotation) extends FirrtlClkTransformAnnotation {
|
||||
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) {
|
||||
def getAnno = ChiselAnnotation(target, classOf[ClkSrcTransform], anno.serialize)
|
||||
}
|
||||
|
||||
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 =>
|
||||
|
||||
private def doNotDedup(module: Module): Unit = {
|
||||
annotate(ChiselAnnotation(module, classOf[DedupModules], "nodedup!"))
|
||||
}
|
||||
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).getAnno)
|
||||
|
||||
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 = {
|
||||
p.dir match {
|
||||
case chisel3.core.Direction.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.Direction.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).getAnno)
|
||||
}
|
||||
}
|
||||
128
tapeout/src/main/scala/transforms/clkgen/ClkDivider.scala
Normal file
128
tapeout/src/main/scala/transforms/clkgen/ClkDivider.scala
Normal file
@@ -0,0 +1,128 @@
|
||||
package barstools.tapeout.transforms.clkgen
|
||||
|
||||
import chisel3.experimental.{withClockAndReset, withClock, withReset}
|
||||
import chisel3._
|
||||
import chisel3.util.RegInit
|
||||
import barstools.tapeout.transforms._
|
||||
import chisel3.util.HasBlackBoxInline
|
||||
|
||||
// WARNING: ONLY WORKS WITH VERILATOR B/C YOU NEED ASYNC RESET!
|
||||
|
||||
class SEClkDividerIO(phases: Seq[Int]) extends Bundle {
|
||||
val reset = Input(Bool())
|
||||
val inClk = Input(Clock())
|
||||
val outClks = Output(CustomIndexedBundle(Clock(), phases))
|
||||
override def cloneType = (new SEClkDividerIO(phases)).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
class SEClkDividerBB(phases: Seq[Int], f: String) extends BlackBox with HasBlackBoxInline {
|
||||
val verilog = scala.io.Source.fromFile(f).getLines.mkString("\n")
|
||||
// names without io
|
||||
val io = IO(new SEClkDividerIO(phases))
|
||||
val modName = this.getClass.getSimpleName
|
||||
require(verilog contains modName, "Clk divider Verilog module must be named ClkDividerBB")
|
||||
io.elements foreach { case (field, elt) =>
|
||||
require(verilog contains field, s"Verilog file should contain io ${field}")}
|
||||
setInline(s"${modName}.v", verilog)
|
||||
}
|
||||
|
||||
class AsyncRegInit extends BlackBox with HasBlackBoxInline {
|
||||
val io = IO(new Bundle {
|
||||
val clk = Input(Clock())
|
||||
val reset = Input(Bool())
|
||||
val init = Input(Bool())
|
||||
val in = Input(Bool())
|
||||
val out = Output(Bool())
|
||||
})
|
||||
|
||||
setInline("AsyncRegInit.v",
|
||||
s"""
|
||||
|module AsyncRegInit(
|
||||
| input clk,
|
||||
| input reset,
|
||||
| input init,
|
||||
| input in,
|
||||
| output reg out
|
||||
|);
|
||||
| always @ (posedge clk or posedge reset) begin
|
||||
| if (reset) begin
|
||||
| out <= init;
|
||||
| end else begin
|
||||
| out <= in;
|
||||
| end
|
||||
| end
|
||||
|endmodule
|
||||
""".stripMargin)
|
||||
}
|
||||
|
||||
object AsyncRegInit {
|
||||
def apply(clk: Clock, reset: Bool, init: Bool): AsyncRegInit = {
|
||||
val asyncRegInit = Module(new AsyncRegInit)
|
||||
asyncRegInit.io.clk := clk
|
||||
asyncRegInit.io.reset := reset
|
||||
asyncRegInit.io.init := init
|
||||
asyncRegInit
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Convert analogFile into implicit?
|
||||
// If syncReset = false, it's implied that reset is strobed before any clk rising edge happens
|
||||
// i.e. when this is a clkgen fed by another clkgen --> need to adjust the indexing b/c
|
||||
// you're already shifting on the first clk rising edge
|
||||
class SEClkDivider(divBy: Int, phases: Seq[Int], analogFile: String = "", syncReset: Boolean = true)
|
||||
extends Module with IsClkModule {
|
||||
|
||||
require(phases.distinct.length == phases.length, "Phases should be distinct!")
|
||||
|
||||
val io = IO(new SEClkDividerIO(phases))
|
||||
|
||||
annotateClkPort(io.inClk, Sink())
|
||||
|
||||
val referenceEdges = phases.map(p => Seq(2 * p, 2 * (p + 1), 2 * (p + divBy)))
|
||||
|
||||
val generatedClks = io.outClks.elements.zip(referenceEdges).map { case ((field, eltx), edges) =>
|
||||
val elt = eltx.asInstanceOf[Element]
|
||||
annotateClkPort(elt)
|
||||
GeneratedClk(getIOName(elt), sources = Seq(getIOName(io.inClk)), edges)
|
||||
}.toSeq
|
||||
|
||||
annotateDerivedClks(ClkDiv, generatedClks)
|
||||
|
||||
require(divBy >= 1, "Clk division factor must be >= 1")
|
||||
|
||||
divBy match {
|
||||
case i: Int if i == 1 =>
|
||||
require(phases == Seq(0), "Clk division by 1 shouldn't generate new phases")
|
||||
io.outClks(0) := io.inClk
|
||||
case i: Int if i > 1 && analogFile == "" =>
|
||||
// Shift register based clock divider (duty cycle is NOT 50%)
|
||||
val initVals = Seq(true.B) ++ Seq.fill(divBy - 1)(false.B)
|
||||
|
||||
/************ Real design assumes asnyc reset!!!
|
||||
withClockAndReset(io.inClk, io.reset) {
|
||||
val regs = initVals.map(i => RegInit(i))
|
||||
// Close the loop
|
||||
regs.head := regs.last
|
||||
// Shift register
|
||||
regs.tail.zip(regs.init) foreach { case (lhs, rhs) => lhs := rhs }
|
||||
// Assign register output to correct clk out
|
||||
phases foreach { idx => io.outClks(idx) := regs(idx).asClock }
|
||||
}
|
||||
*************/
|
||||
|
||||
val regs = initVals.map(i => AsyncRegInit(io.inClk, io.reset, i))
|
||||
regs.head.io.in := regs.last.io.out
|
||||
regs.tail.zip(regs.init) foreach { case (lhs, rhs) => lhs.io.in := rhs.io.out }
|
||||
phases foreach { idx =>
|
||||
val regIdx = if (syncReset) idx else (idx + 1) % divBy
|
||||
io.outClks(idx) := regs(regIdx).io.out.asClock
|
||||
}
|
||||
|
||||
case _ =>
|
||||
if (new java.io.File(analogFile).exists) {
|
||||
val bb = Module(new SEClkDividerBB(phases, analogFile))
|
||||
io <> bb.io
|
||||
}
|
||||
else throw new Exception("Clock divider Verilog file invalid!")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package barstools.tapeout.transforms.clkgen
|
||||
|
||||
import firrtl._
|
||||
import firrtl.annotations._
|
||||
import firrtl.passes._
|
||||
import firrtl.ir._
|
||||
|
||||
class ClkSrcTransform extends Transform with SimpleRun {
|
||||
|
||||
override def inputForm: CircuitForm = LowForm
|
||||
override def outputForm: CircuitForm = LowForm
|
||||
|
||||
override def execute(state: CircuitState): CircuitState = {
|
||||
val collectedAnnos = HasClkAnnotation(getMyAnnotations(state))
|
||||
collectedAnnos match {
|
||||
// Transform not used
|
||||
case None => CircuitState(state.circuit, LowForm)
|
||||
case Some((clkModAnnos, clkPortAnnos)) =>
|
||||
val targetDir = barstools.tapeout.transforms.GetTargetDir(state)
|
||||
val passSeq = Seq(
|
||||
InferTypes,
|
||||
new CreateClkConstraints(clkModAnnos, clkPortAnnos, targetDir)
|
||||
)
|
||||
CircuitState(runPasses(state.circuit, passSeq), LowForm)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// See license file for details
|
||||
|
||||
package barstools.tapeout.transforms.clkgen
|
||||
|
||||
import firrtl.passes.clocklist._
|
||||
import firrtl.annotations._
|
||||
import firrtl.ir._
|
||||
import firrtl.Utils._
|
||||
import barstools.tapeout.transforms._
|
||||
import scala.collection.immutable.ListMap
|
||||
|
||||
// TODO: Really should be moved out of memlib
|
||||
import firrtl.passes.memlib.AnalysisUtils._
|
||||
import firrtl.passes._
|
||||
|
||||
// TODO: Wait until Albert merges into firrtl
|
||||
import firrtl.analyses._
|
||||
|
||||
class CreateClkConstraints(
|
||||
clkModAnnos: Seq[TargetClkModAnnoF],
|
||||
clkPortAnnos: Seq[TargetClkPortAnnoF],
|
||||
targetDir: String) extends Pass {
|
||||
|
||||
def name = "Create clock constraints"
|
||||
|
||||
// TODO: Are annotations only valid on ports?
|
||||
|
||||
def run(c: Circuit): Circuit = {
|
||||
|
||||
val top = c.main
|
||||
|
||||
// Remove everything from the circuit, unless it has a clock type
|
||||
// This simplifies the circuit drastically so InlineInstances doesn't take forever.
|
||||
val onlyClockCircuit = RemoveAllButClocks.run(c)
|
||||
|
||||
val instanceGraph = new InstanceGraph(onlyClockCircuit)
|
||||
|
||||
val clkModNames = clkModAnnos.map(x => x.targetName)
|
||||
// ** Module name -> Absolute path of (unique) instance
|
||||
val clkMods = clkModNames.map { x =>
|
||||
// NoDeDup was run so only 1 instance of each module should exist
|
||||
val inst = instanceGraph.findInstancesInHierarchy(x)
|
||||
require(inst.length == 1, "Clk modules should have not ben dedup-ed")
|
||||
// Return map of module name to absolute path as a string
|
||||
// Note: absolute path doesn't contain top module + to work with inlineInstances,
|
||||
// delimit with $
|
||||
x -> inst.head.tail.map(y => y.name).mkString("$")
|
||||
}.toMap
|
||||
|
||||
val clkPortIds = clkPortAnnos.map { a => a.modId }
|
||||
require(clkPortIds.distinct.length == clkPortIds.length, "All clk port IDs must be unique!")
|
||||
|
||||
val allModClkPorts = clkModAnnos.map { x =>
|
||||
val modClkPorts = x.getAllClkPorts
|
||||
require(modClkPorts.intersect(clkPortIds).length == modClkPorts.length,
|
||||
"Clks given relationships via clk modules must have been annotated as clk ports")
|
||||
modClkPorts
|
||||
}.flatten.distinct
|
||||
|
||||
val clkPortMap = clkPortIds.zip(clkPortAnnos).toMap
|
||||
val clkModMap = clkModNames.zip(clkModAnnos).toMap
|
||||
|
||||
val (clkSinksTemp, clkSrcsTemp) = clkPortAnnos.partition {
|
||||
case TargetClkPortAnnoF(_, ClkPortAnnotation(tag, _)) if tag.nonEmpty => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def convertClkPortAnnoToMap(annos: Seq[TargetClkPortAnnoF]): ListMap[String, String] =
|
||||
ListMap(annos.map { x =>
|
||||
val target = x.target
|
||||
val absPath = {
|
||||
if (top == target.module.name) LowerName(target.name)
|
||||
else Seq(clkMods(target.module.name), LowerName(target.name)).mkString(".")
|
||||
}
|
||||
x.modId -> absPath
|
||||
}.sortBy(_._1): _*)
|
||||
|
||||
// ** clk port -> absolute path
|
||||
val clkSinks = convertClkPortAnnoToMap(clkSinksTemp)
|
||||
val clkSrcs = convertClkPortAnnoToMap(clkSrcsTemp)
|
||||
|
||||
clkSrcs foreach { case (id, path) =>
|
||||
require(allModClkPorts contains id, "All clock source properties must be defined by their respective modules") }
|
||||
|
||||
// Don't inline clock modules
|
||||
val modulesToInline = (c.modules.collect {
|
||||
case Module(_, n, _, _) if n != top && !clkModNames.contains(n) =>
|
||||
ModuleName(n, CircuitName(top))
|
||||
}).toSet
|
||||
|
||||
val inlineTransform = new InlineInstances
|
||||
val inlinedCircuit = inlineTransform.run(onlyClockCircuit, modulesToInline, Set()).circuit
|
||||
|
||||
val topModule = inlinedCircuit.modules.find(_.name == top).getOrElse(throwInternalError)
|
||||
|
||||
// Build a hashmap of connections to use for getOrigins
|
||||
val connects = getConnects(topModule)
|
||||
|
||||
// Clk sinks are either inputs to clock modules or top clk inputs --> separate
|
||||
// ** clk port -> absolute path
|
||||
val (topClks, clkModSinks) = clkSinks.partition {
|
||||
case (modId, absPath) if modId.split("\\.").head == top => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
// Must be 1:1 originally!
|
||||
def flipMapping(m: ListMap[String, String]): ListMap[String, String] =
|
||||
m.map { case (a, b) => b -> a }
|
||||
|
||||
val clkSrcsFlip = flipMapping(clkSrcs)
|
||||
val topClksFlip = flipMapping(topClks)
|
||||
|
||||
// Find origins of clk mod sinks
|
||||
val clkModSinkToSourceMap = clkModSinks.map { case (sinkId, sinkAbsPath) =>
|
||||
val sourceAbsPath = getOrigin(connects, sinkAbsPath).serialize
|
||||
val sourceId = {
|
||||
// sources of sinks are generated clks or top level clk inputs
|
||||
if (clkSrcsFlip.contains(sourceAbsPath)) clkSrcsFlip(sourceAbsPath)
|
||||
else if (topClksFlip.contains(sourceAbsPath)) topClksFlip(sourceAbsPath)
|
||||
else throw new Exception(s"Absolute path of clk source for $sinkId not found!")
|
||||
}
|
||||
sinkId -> sourceId
|
||||
}
|
||||
|
||||
c.modules.foreach {
|
||||
case mod: DefModule =>
|
||||
mod.ports.foreach {
|
||||
case Port(_, n, dir, tpe)
|
||||
if tpe == ClockType &&
|
||||
((dir == Input && mod.name == top) || (dir == Output && clkModNames.contains(mod.name))) =>
|
||||
clkPortAnnos.find(x =>
|
||||
// TODO: Not sufficiently general for output clks? Might have forgotten to label a clk module...
|
||||
LowerName(x.target.name) == n && x.target.module.name == mod.name).getOrElse(
|
||||
throw new Exception("All top module input clks/clk module output clocks must be sinks/sources!"))
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
||||
// Find sinks used to derive clk mod sources
|
||||
val clkModSourceToSinkMap: Seq[(String, Seq[String])] = clkModAnnos.map(x => {
|
||||
val modName = x.targetName
|
||||
x.generatedClks.map(y => Seq(modName, y.id).mkString(".") -> y.sources.map(z => Seq(modName, z).mkString(".")))
|
||||
} ).flatten
|
||||
|
||||
topClks.foreach {x => println(s"top clk: $x")}
|
||||
clkModSinks.foreach { x => println(s"clk sink: $x")}
|
||||
clkSrcs.foreach { x => println(s"gen clk: $x")}
|
||||
clkModSinkToSourceMap.foreach { x => println(s"sink -> src: $x")}
|
||||
clkModSourceToSinkMap.foreach { x => println(s"src -> dependent sinks: $x")}
|
||||
c
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package barstools.tapeout.transforms.pads
|
||||
|
||||
import firrtl._
|
||||
import firrtl.annotations._
|
||||
import firrtl.passes._
|
||||
import firrtl.ir._
|
||||
import barstools.tapeout.transforms._
|
||||
|
||||
// Main Add IO Pad transform operates on low Firrtl
|
||||
class AddIOPadsTransform extends Transform with SimpleRun {
|
||||
|
||||
override def inputForm: CircuitForm = LowForm
|
||||
override def outputForm: CircuitForm = LowForm
|
||||
|
||||
override def execute(state: CircuitState): CircuitState = {
|
||||
val collectedAnnos = HasPadAnnotation(getMyAnnotations(state))
|
||||
collectedAnnos match {
|
||||
// Transform not used
|
||||
case None => CircuitState(state.circuit, LowForm)
|
||||
case Some(x) =>
|
||||
val techLoc = (new TechnologyLocation).get(state)
|
||||
// Get foundry pad templates from yaml
|
||||
val foundryPads = FoundryPadsYaml.parse(techLoc)
|
||||
val portPads = AnnotatePortPads(state.circuit, x.topModName, foundryPads, x.componentAnnos,
|
||||
HasPadAnnotation.getSide(x.defaultPadSide))
|
||||
val supplyPads = AnnotateSupplyPads(foundryPads, x.supplyAnnos)
|
||||
val (circuitWithBBs, bbAnnotations) = CreatePadBBs(state.circuit, portPads, supplyPads)
|
||||
val namespace = Namespace(state.circuit)
|
||||
val padFrameName = namespace newName s"${x.topModName}_PadFrame"
|
||||
val topInternalName = namespace newName s"${x.topModName}_Internal"
|
||||
val targetDir = barstools.tapeout.transforms.GetTargetDir(state)
|
||||
PadPlacementFile.generate(techLoc, targetDir, padFrameName, portPads, supplyPads)
|
||||
val passSeq = Seq(
|
||||
Legalize,
|
||||
ResolveGenders,
|
||||
// Types really need to be known...
|
||||
InferTypes,
|
||||
new AddPadFrame(x.topModName, padFrameName, topInternalName, portPads, supplyPads),
|
||||
RemoveEmpty,
|
||||
CheckInitialization,
|
||||
InferTypes,
|
||||
Uniquify,
|
||||
ResolveKinds,
|
||||
ResolveGenders
|
||||
)
|
||||
// Expects BlackBox helper to be run after to inline pad Verilog!
|
||||
val prevAnnos = state.annotations.getOrElse(AnnotationMap(Seq.empty)).annotations
|
||||
val cs = CircuitState(
|
||||
runPasses(circuitWithBBs, passSeq),
|
||||
LowForm,
|
||||
Some(AnnotationMap(prevAnnos ++ bbAnnotations))
|
||||
)
|
||||
// TODO: *.f file is overwritten on subsequent executions, but it doesn't seem to be used anywhere?
|
||||
(new firrtl.transforms.BlackBoxSourceHelper).execute(cs)
|
||||
}
|
||||
}
|
||||
}
|
||||
135
tapeout/src/main/scala/transforms/pads/AddPadFrame.scala
Normal file
135
tapeout/src/main/scala/transforms/pads/AddPadFrame.scala
Normal file
@@ -0,0 +1,135 @@
|
||||
// See LICENSE for license details.
|
||||
|
||||
package barstools.tapeout.transforms.pads
|
||||
|
||||
import firrtl.annotations._
|
||||
import firrtl.ir._
|
||||
import firrtl._
|
||||
import firrtl.passes._
|
||||
|
||||
// Analog is like UInt, SInt; it's not a direction (which is kind of weird)
|
||||
// WARNING: Analog type is associated with Verilog InOut! i.e. even if digital pads are tri-statable, b/c tristate
|
||||
// requires an additional ctrl signal, digital pads must be operated in a single "static" condition here; Analog will
|
||||
// be paired with analog pads
|
||||
|
||||
class AddPadFrame(
|
||||
topMod: String,
|
||||
padFrameName: String,
|
||||
topInternalName: String,
|
||||
ioPads: Seq[PortIOPad],
|
||||
supplyPads: Seq[TopSupplyPad]) extends Pass {
|
||||
|
||||
def name: String = "Add Padframe"
|
||||
|
||||
def run(c: Circuit): Circuit = {
|
||||
// New modules consist of old modules (with top renamed to internal) + padFrame + newTop
|
||||
val newMods = c.modules.map {
|
||||
case mod: Module if mod.name == topMod =>
|
||||
// Original top module is now internal module
|
||||
mod.copy(name = topInternalName)
|
||||
case m => m
|
||||
} ++ Seq(buildPadFrame(), buildTopWrapper())
|
||||
|
||||
// Reparent so circuit top is whatever uses pads!
|
||||
// TODO: Can the top level be a blackbox?
|
||||
c.copy(modules = newMods, main = topMod)
|
||||
}
|
||||
|
||||
def intName(p: PortIOPad) = s"${p.portName}_Int"
|
||||
def extName(p: PortIOPad) = s"${p.portName}_Ext"
|
||||
|
||||
def buildTopWrapper(): Module = {
|
||||
// outside -> padframe -> internal
|
||||
// Top (with same name) contains 1) padframe + 2) internal signals
|
||||
val padFrameInst = WDefInstance(padFrameName, padFrameName)
|
||||
val topInternalInst = WDefInstance(topInternalName, topInternalName)
|
||||
val padFrameRef = WRef(padFrameName)
|
||||
val topInternalRef = WRef(topInternalName)
|
||||
val connects = ioPads.map { p =>
|
||||
val io = WRef(p.portName)
|
||||
val intIo = WSubField(topInternalRef, p.portName)
|
||||
val padFrameIntIo = WSubField(padFrameRef, intName(p))
|
||||
val padFrameExtIo = WSubField(padFrameRef, extName(p))
|
||||
p.port.tpe match {
|
||||
case AnalogType(_) =>
|
||||
// Analog pads only have 1 port
|
||||
// If Analog port doesn't have associated pad, don't hook it up to the padframe
|
||||
val analogAttachInt = Seq(Attach(NoInfo, Seq(io, intIo)))
|
||||
if (p.pad.isEmpty) analogAttachInt
|
||||
else analogAttachInt :+ Attach(NoInfo, Seq(io, padFrameExtIo))
|
||||
case _ => p.portDirection match {
|
||||
case Input =>
|
||||
// input to padframe ; padframe to internal
|
||||
Seq(Connect(NoInfo, padFrameExtIo, io), Connect(NoInfo, intIo, padFrameIntIo))
|
||||
case Output =>
|
||||
// internal to padframe ; padframe to output
|
||||
Seq(Connect(NoInfo, padFrameIntIo, intIo), Connect(NoInfo, io, padFrameExtIo))
|
||||
}
|
||||
}
|
||||
}.flatten
|
||||
val stmts = Seq(padFrameInst, topInternalInst) ++ connects
|
||||
val ports = ioPads.map(p => p.port)
|
||||
Module(NoInfo, topMod, ports = ports, body = Block(stmts))
|
||||
}
|
||||
|
||||
def buildPadFrame(): Module = {
|
||||
// Internal = connection to original RTL; External = connection to outside world
|
||||
// Note that for analog pads, since there's only 1 port, only _Ext is used
|
||||
val intPorts = ioPads.map(p => p.port.tpe match {
|
||||
case AnalogType(_) => None
|
||||
case _ => Some(p.port.copy(name = intName(p), direction = Utils.swap(p.portDirection)))
|
||||
}).flatten
|
||||
val extPorts = ioPads.map(p => p.port.tpe match {
|
||||
// If an analog port doesn't have a pad associated with it, don't add it to the padframe
|
||||
case AnalogType(_) if p.pad.isEmpty => None
|
||||
case _ => Some(p.port.copy(name = extName(p)))
|
||||
} ).flatten
|
||||
// Only create pad black boxes for ports that require them
|
||||
val ioPadInsts = ioPads.filter(x => !x.pad.isEmpty).map(p => WDefInstance(p.firrtlBBName, p.firrtlBBName))
|
||||
// Connect to pad only if used ; otherwise leave dangling for Analog
|
||||
// and just connect through for digital (assumes no supplies)
|
||||
val connects = ioPads.map { p =>
|
||||
val intRef = WRef(intName(p), p.port.tpe)
|
||||
val extRef = WRef(extName(p), p.port.tpe)
|
||||
p.pad match {
|
||||
// No pad needed -- just connect through
|
||||
case None => p.port.tpe match {
|
||||
case AnalogType(_) =>
|
||||
Seq(EmptyStmt)
|
||||
case _ =>
|
||||
val (lhs, rhs) = p.portDirection match {
|
||||
case Input => (intRef, extRef)
|
||||
case Output => (extRef, intRef)
|
||||
}
|
||||
Seq(Connect(NoInfo, lhs, rhs))
|
||||
}
|
||||
// Add pad
|
||||
case Some(x) =>
|
||||
val padRef = WRef(p.firrtlBBName)
|
||||
p.port.tpe match {
|
||||
// Analog type has 1:1 mapping to inout
|
||||
case AnalogType(_) =>
|
||||
val padIORef = WSubField(padRef, AnalogPad.ioName)
|
||||
Seq(Attach(NoInfo, Seq(padIORef, extRef)))
|
||||
// Normal verilog in/out can be mapped to uint, sint, or clocktype, so need cast
|
||||
case _ =>
|
||||
val padBBType = UIntType(getWidth(p.port.tpe))
|
||||
val padInRef = WSubField(padRef, DigitalPad.inName, padBBType, UNKNOWNGENDER)
|
||||
val padOutRef = WSubField(padRef, DigitalPad.outName, padBBType, UNKNOWNGENDER)
|
||||
val (rhsPadIn, lhsPadOut) = p.portDirection match {
|
||||
case Input => (extRef, intRef)
|
||||
case Output => (intRef, extRef)
|
||||
}
|
||||
// Pad inputs are treated as UInts, so need to do type conversion
|
||||
// from type to UInt pad input; from pad output to type
|
||||
Seq(
|
||||
Connect(NoInfo, padInRef, castRhs(padBBType, rhsPadIn)),
|
||||
Connect(NoInfo, lhsPadOut, castRhs(p.port.tpe, padOutRef)))
|
||||
}
|
||||
}
|
||||
}.flatten
|
||||
val supplyPadInsts = supplyPads.map(p => p.instNames.map(n => WDefInstance(n, p.firrtlBBName))).flatten
|
||||
Module(NoInfo, padFrameName, ports = intPorts ++ extPorts, body = Block(ioPadInsts ++ connects ++ supplyPadInsts))
|
||||
}
|
||||
|
||||
}
|
||||
135
tapeout/src/main/scala/transforms/pads/AnnotatePortPads.scala
Normal file
135
tapeout/src/main/scala/transforms/pads/AnnotatePortPads.scala
Normal file
@@ -0,0 +1,135 @@
|
||||
package barstools.tapeout.transforms.pads
|
||||
|
||||
import firrtl.annotations._
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.passes._
|
||||
import barstools.tapeout.transforms._
|
||||
|
||||
// TODO: Make some trait with commonalities between IO Pad + supply pad
|
||||
|
||||
// Pads associated with IO Ports! (Not supplies!)
|
||||
case class PortIOPad(
|
||||
pad: Option[FoundryPad],
|
||||
padSide: PadSide,
|
||||
port: Port) {
|
||||
|
||||
def arrayInstNamePrefix(mod: String): String = Seq(mod, firrtlBBName, getPadName).mkString("/")
|
||||
def arrayInstNameSuffix: String = pad match {
|
||||
case None => throw new Exception("Port needs to use pad to get array instance name!")
|
||||
case Some(x) => "/" + x.padInstName
|
||||
}
|
||||
|
||||
def portName = port.name
|
||||
def portWidth = bitWidth(port.tpe).intValue
|
||||
def portDirection = port.direction
|
||||
def padOrientation = padSide.orientation
|
||||
def padType = pad match {
|
||||
case None => NoPad
|
||||
case Some(x) => x.padType
|
||||
}
|
||||
|
||||
def widthParamName = "WIDTH"
|
||||
def getPadName: String = pad match {
|
||||
case None => throw new Exception("Cannot get pad name when no pad specified!")
|
||||
case Some(x) => x.getName(portDirection, padOrientation)
|
||||
}
|
||||
def getPadArrayName: String = Seq(getPadName, "array").mkString("_")
|
||||
// Firrtl black box name must be unique, even though the parameterized Verilog modules don't
|
||||
// need to have separate names
|
||||
def firrtlBBName = Seq(getPadArrayName, portName).mkString("_")
|
||||
|
||||
// Note: This includes both the pad wrapper + an additional wrapper for n-bit wide to
|
||||
// multiple pad conversion!
|
||||
def createPadInline(): String = {
|
||||
// For blackboxing bit extraction/concatenation (with module arrays)
|
||||
def io(): String = padType match {
|
||||
case DigitalPad =>
|
||||
s"""| input [${widthParamName}-1:0] ${DigitalPad.inName},
|
||||
| output reg [${widthParamName}-1:0] ${DigitalPad.outName}""".stripMargin
|
||||
case AnalogPad =>
|
||||
s" inout [${widthParamName}-1:0] ${AnalogPad.ioName}"
|
||||
case _ => throw new Exception("IO pad can only be digital or analog")
|
||||
}
|
||||
def assignIO(): String = padType match {
|
||||
case DigitalPad =>
|
||||
s"""| .${DigitalPad.inName}(${DigitalPad.inName}),
|
||||
| .${DigitalPad.outName}(${DigitalPad.outName})""".stripMargin
|
||||
case AnalogPad =>
|
||||
s" .${AnalogPad.ioName}(${AnalogPad.ioName})"
|
||||
case _ => throw new Exception("IO pad can only be digital or analog")
|
||||
}
|
||||
def getPadVerilog(): String = pad match {
|
||||
case None => throw new Exception("Cannot get Verilog when no pad specified!")
|
||||
case Some(x) => x.getVerilog(portDirection, padOrientation)
|
||||
}
|
||||
s"""inline
|
||||
|${getPadArrayName}.v
|
||||
|${getPadVerilog}
|
||||
|module ${getPadArrayName} #(
|
||||
| parameter int ${widthParamName}=1
|
||||
|)(
|
||||
|${io}
|
||||
|);
|
||||
| ${getPadName} ${getPadName}[${widthParamName}-1:0](
|
||||
|${assignIO}
|
||||
| );
|
||||
|endmodule""".stripMargin
|
||||
}
|
||||
}
|
||||
|
||||
object AnnotatePortPads {
|
||||
def apply(
|
||||
c: Circuit,
|
||||
topMod: String,
|
||||
pads: Seq[FoundryPad],
|
||||
componentAnnos: Seq[TargetIOPadAnnoF],
|
||||
defaultSide: PadSide): Seq[PortIOPad] = {
|
||||
|
||||
def lowerAnnotations(): Seq[TargetIOPadAnnoF] = {
|
||||
componentAnnos map { x => x.target match {
|
||||
case c: ComponentName => x.copy(target = c.copy(name = LowerName(c.name)))
|
||||
case _ => throw new Exception("Not a component annotation! Can't lower!")
|
||||
}}
|
||||
}
|
||||
|
||||
// Make annotations match low form
|
||||
val annos = lowerAnnotations()
|
||||
|
||||
def getPortIOPad(port: Port): PortIOPad = {
|
||||
val portAnnos = annos.find(_.targetName == port.name)
|
||||
// Ports can only be digital or analog
|
||||
val padTypeRequired = port.tpe match {
|
||||
case AnalogType(_) => AnalogPad
|
||||
case _ => DigitalPad
|
||||
}
|
||||
val validPads = pads.filter(_.padType == padTypeRequired)
|
||||
require(validPads.length > 0, s"No ${padTypeRequired.serialize} pads specified in the config yaml file!")
|
||||
portAnnos match {
|
||||
case None =>
|
||||
// If no pad-related annotation is found on a port, use defaults based off of port type
|
||||
PortIOPad(Some(validPads.head), defaultSide, port)
|
||||
case Some(x) =>
|
||||
x.anno match {
|
||||
case NoIOPadAnnotation(_) =>
|
||||
// Some ports might not want attached pads
|
||||
PortIOPad(None, defaultSide, port)
|
||||
case IOPadAnnotation(padSide, padName) if padName.isEmpty =>
|
||||
// If no pad name is used, select the first valid pad based off of port type
|
||||
PortIOPad(Some(validPads.head), HasPadAnnotation.getSide(padSide), port)
|
||||
case IOPadAnnotation(padSide, padName) =>
|
||||
// If name doesn't match any provided -- maybe someone typoed?
|
||||
validPads.find(_.name == padName) match {
|
||||
case None =>
|
||||
throw new Exception(
|
||||
s"Pad name associated with ${port.name} doesn't match valid pad names. Did you typo?")
|
||||
case Some(x) =>
|
||||
PortIOPad(Some(x), HasPadAnnotation.getSide(padSide), port)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Top MUST be internal module
|
||||
c.modules.filter(_.name == topMod).head.ports.map(x => getPortIOPad(x))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package barstools.tapeout.transforms.pads
|
||||
|
||||
import firrtl.annotations._
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.passes._
|
||||
|
||||
case class TopSupplyPad(
|
||||
pad: FoundryPad,
|
||||
padSide: PadSide,
|
||||
num: Int
|
||||
) {
|
||||
|
||||
// TODO: These should be pulled into some common trait (supply + io)!
|
||||
|
||||
def arrayInstNamePrefix(mod: String): Seq[String] = {
|
||||
instNames.map(n => Seq(mod, n, pad.padInstName).mkString("/"))
|
||||
}
|
||||
def supplySetNum = pad.getSupplySetNum
|
||||
|
||||
def padType = pad.padType
|
||||
require(pad.padType == SupplyPad)
|
||||
|
||||
def padOrientation = padSide.orientation
|
||||
def getPadName = pad.getName(NoDirection, padOrientation)
|
||||
def firrtlBBName = getPadName
|
||||
private def instNamePrefix = Seq(firrtlBBName, padSide.serialize).mkString("_")
|
||||
def instNames = (0 until num).map(i => Seq(instNamePrefix, i.toString).mkString("_"))
|
||||
|
||||
def createPadInline(): String = {
|
||||
def getPadVerilog(): String = pad.getVerilog(NoDirection, padOrientation)
|
||||
s"""inline
|
||||
|${getPadName}.v
|
||||
|${getPadVerilog}""".stripMargin
|
||||
}
|
||||
}
|
||||
|
||||
object AnnotateSupplyPads {
|
||||
def apply(
|
||||
pads: Seq[FoundryPad],
|
||||
supplyAnnos: Seq[SupplyAnnotation]
|
||||
): Seq[TopSupplyPad] = {
|
||||
supplyAnnos.map( a =>
|
||||
pads.find(_.name == a.padName) match {
|
||||
case None =>
|
||||
throw new Exception(s"Supply pad ${a.padName} not found in Yaml file!")
|
||||
case Some(x) =>
|
||||
Seq(
|
||||
TopSupplyPad(x, Left, a.leftSide),
|
||||
TopSupplyPad(x, Right, a.rightSide),
|
||||
TopSupplyPad(x, Top, a.topSide),
|
||||
TopSupplyPad(x, Bottom, a.bottomSide))
|
||||
}
|
||||
).flatten.filter(_.num > 0)
|
||||
}
|
||||
}
|
||||
76
tapeout/src/main/scala/transforms/pads/ChiselTopModule.scala
Normal file
76
tapeout/src/main/scala/transforms/pads/ChiselTopModule.scala
Normal file
@@ -0,0 +1,76 @@
|
||||
package barstools.tapeout.transforms.pads
|
||||
|
||||
import chisel3._
|
||||
import barstools.tapeout.transforms.clkgen._
|
||||
import chisel3.experimental._
|
||||
import firrtl.transforms.DedupModules
|
||||
|
||||
// TODO: Move out of pads
|
||||
|
||||
// NOTE: You can't really annotate outside of the module itself UNLESS you break up the compile step in 2 i.e.
|
||||
// annotate post-Chisel but pre-Firrtl (unfortunate non-generator friendly downside).
|
||||
// It's recommended to have a Tapeout specific TopModule wrapper.
|
||||
// LIMITATION: All signals of a bus must be on the same chip side
|
||||
|
||||
// Chisel-y annotations
|
||||
abstract class TopModule(
|
||||
supplyAnnos: Seq[SupplyAnnotation] = Seq.empty,
|
||||
defaultPadSide: PadSide = Top,
|
||||
coreWidth: Int = 0,
|
||||
coreHeight: Int = 0,
|
||||
usePads: Boolean = true,
|
||||
override_clock: Option[Clock] = None,
|
||||
override_reset: Option[Bool] = None) extends Module(override_clock, override_reset) with IsClkModule {
|
||||
|
||||
override def annotateClkPort(p: Element, anno: ClkPortAnnotation): Unit = {
|
||||
p.dir match {
|
||||
case chisel3.core.Direction.Input =>
|
||||
require(anno.tag.nonEmpty, "Top Module input clks must be clk sinks")
|
||||
require(anno.tag.get.src.nonEmpty,
|
||||
"Top module input clks must have clk period, etc. specified")
|
||||
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).getAnno)
|
||||
}
|
||||
|
||||
override def annotateDerivedClks(m: Module, anno: ClkModAnnotation): Unit =
|
||||
throw new Exception("Top module cannot be pure clock module!")
|
||||
|
||||
// Annotate module as top module (that requires pad transform)
|
||||
// Specify the yaml file that indicates how pads are templated,
|
||||
// the default chip side that pads should be placed (if nothing is specified per IO),
|
||||
// and supply annotations: supply pad name, location, and #
|
||||
def createPads(): Unit = if (usePads) {
|
||||
val modulePadAnnotation = ModulePadAnnotation(
|
||||
defaultPadSide = defaultPadSide.serialize,
|
||||
coreWidth = coreWidth,
|
||||
coreHeight = coreHeight,
|
||||
supplyAnnos = supplyAnnos
|
||||
)
|
||||
annotate(TargetModulePadAnnoC(this, modulePadAnnotation).getAnno)
|
||||
}
|
||||
|
||||
// Annotate IO with side + pad name
|
||||
def annotatePad(sig: Element, side: PadSide = defaultPadSide, name: String = ""): Unit = if (usePads) {
|
||||
val anno = IOPadAnnotation(side.serialize, name)
|
||||
annotate(TargetIOPadAnnoC(sig, anno).getAnno)
|
||||
}
|
||||
def annotatePad(sig: Aggregate, name: String): Unit = annotatePad(sig, side = defaultPadSide, name)
|
||||
def annotatePad(sig: Aggregate, side: PadSide): Unit = annotatePad(sig, side, name = "")
|
||||
def annotatePad(sig: Aggregate, side: PadSide, name: String): Unit =
|
||||
extractElements(sig) foreach { x => annotatePad(x, side, name) }
|
||||
|
||||
// There may be cases where pads were inserted elsewhere. If that's the case, allow certain IO to
|
||||
// not have pads auto added. Note that annotatePad and noPad are mutually exclusive!
|
||||
def noPad(sig: Element): Unit = if (usePads) annotate(TargetIOPadAnnoC(sig, NoIOPadAnnotation()).getAnno)
|
||||
def noPad(sig: Aggregate): Unit = extractElements(sig) foreach { x => noPad(x) }
|
||||
|
||||
// Since this is a super class, this should be the first thing that gets run
|
||||
// (at least when the module is actually at the top -- currently no guarantees otherwise :( firrtl limitation)
|
||||
createPads()
|
||||
}
|
||||
109
tapeout/src/main/scala/transforms/pads/CreatePadBBs.scala
Normal file
109
tapeout/src/main/scala/transforms/pads/CreatePadBBs.scala
Normal file
@@ -0,0 +1,109 @@
|
||||
package barstools.tapeout.transforms.pads
|
||||
|
||||
import firrtl.annotations._
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.transforms._
|
||||
|
||||
object CreatePadBBs {
|
||||
|
||||
private [barstools] case class UsedPadInfo(
|
||||
// The following are found with both supply + io pads
|
||||
padInline: String, // Verilog txt
|
||||
padName: String, // Pad module name
|
||||
padType: PadType, // Pad type: supply, analog, digital
|
||||
// The following only affects io pads (due to using parameterized modules for bit extraction / cat)
|
||||
padArrayName: String, // Name of parameterized pad wrapper (that does bit extract/cat)
|
||||
firrtlBBName: String, // Unique Firrtl name of each parameterized pad wrapper
|
||||
portWidth: Int // Port width for analog/digital
|
||||
)
|
||||
|
||||
def convertToUsedPad(p: PortIOPad): UsedPadInfo = {
|
||||
UsedPadInfo(
|
||||
padInline = p.createPadInline,
|
||||
padName = p.getPadName,
|
||||
padType = p.padType,
|
||||
padArrayName = p.getPadArrayName,
|
||||
firrtlBBName = p.firrtlBBName,
|
||||
portWidth = p.portWidth)
|
||||
}
|
||||
|
||||
def convertToUsedPad(p: TopSupplyPad): UsedPadInfo = {
|
||||
UsedPadInfo(
|
||||
padInline = p.createPadInline,
|
||||
padName = p.getPadName,
|
||||
padType = p.padType,
|
||||
// Supply pads don't require bit extraction / cat so don't care
|
||||
padArrayName = p.getPadName,
|
||||
firrtlBBName = p.getPadName,
|
||||
portWidth = 0)
|
||||
}
|
||||
|
||||
def checkLegalPadName(namespace: Namespace, usedPads: Seq[UsedPadInfo]): Unit = {
|
||||
usedPads foreach { x =>
|
||||
if (namespace contains x.padName)
|
||||
throw new Exception(s"Pad name ${x.padName} already used!")
|
||||
if (namespace contains x.padArrayName)
|
||||
throw new Exception(s"Pad array ${x.padArrayName} name already used!")
|
||||
if (namespace contains x.firrtlBBName)
|
||||
throw new Exception(s"Firrtl black box ${x.firrtlBBName} name already used!")
|
||||
}
|
||||
}
|
||||
|
||||
def apply(
|
||||
c: Circuit,
|
||||
ioPads: Seq[PortIOPad],
|
||||
supplyPads: Seq[TopSupplyPad]): (Circuit, Seq[Annotation]) = {
|
||||
|
||||
// Add black boxes for both supply + (used) io pads
|
||||
val usedPads = ioPads.filter(x => x.pad.nonEmpty).map(convertToUsedPad(_)) ++ supplyPads.map(convertToUsedPad(_))
|
||||
checkLegalPadName(Namespace(c), usedPads)
|
||||
|
||||
// Note that we need to check for Firrtl name uniqueness here! (due to parameterization)
|
||||
val uniqueExtMods = scala.collection.mutable.ArrayBuffer[UsedPadInfo]()
|
||||
usedPads foreach { x =>
|
||||
if (uniqueExtMods.find(_.firrtlBBName == x.firrtlBBName).isEmpty)
|
||||
uniqueExtMods += x
|
||||
}
|
||||
|
||||
// Collecting unique parameterized black boxes
|
||||
// (for io, they're wrapped pads; for supply, they're pad modules directly)
|
||||
val uniqueParameterizedBBs = scala.collection.mutable.ArrayBuffer[UsedPadInfo]()
|
||||
uniqueExtMods foreach { x =>
|
||||
if (uniqueParameterizedBBs.find(_.padArrayName == x.padArrayName).isEmpty)
|
||||
uniqueParameterizedBBs += x
|
||||
}
|
||||
|
||||
// Note: Firrtl is silly and doesn't implement true parameterization -- each module with
|
||||
// parameterization that potentially affects # of IO needs to be uniquely identified
|
||||
// (but only in Firrtl)
|
||||
val bbs = uniqueExtMods.map(x => {
|
||||
// Supply pads don't have ports
|
||||
val ports = x.padType match {
|
||||
case AnalogPad => Seq(Port(NoInfo, AnalogPad.ioName, Input, AnalogType(IntWidth(x.portWidth))))
|
||||
case DigitalPad => Seq(
|
||||
Port(NoInfo, DigitalPad.inName, Input, UIntType(IntWidth(x.portWidth))),
|
||||
Port(NoInfo, DigitalPad.outName, Output, UIntType(IntWidth(x.portWidth)))
|
||||
)
|
||||
case SupplyPad => Seq.empty
|
||||
case _ => throw new Exception("Port pad type invalid!")
|
||||
}
|
||||
// Supply black boxes are not parameterized
|
||||
val params = x.padType match {
|
||||
case AnalogPad | DigitalPad => Seq(IntParam(ioPads.head.widthParamName, x.portWidth))
|
||||
case SupplyPad => Seq()
|
||||
case _ => throw new Exception("Port pad type invalid!")
|
||||
}
|
||||
// Firrtl name is unique
|
||||
ExtModule(NoInfo, x.firrtlBBName, ports, x.padArrayName, params)
|
||||
} ).toSeq
|
||||
|
||||
// Add annotations to black boxes to inline Verilog from template
|
||||
// Again, note the weirdness in parameterization -- just need to hook to one matching Firrtl instance
|
||||
val annos = uniqueParameterizedBBs.map(x =>
|
||||
BlackBoxSourceAnnotation(ModuleName(x.firrtlBBName, CircuitName(c.main)), x.padInline)
|
||||
).toSeq
|
||||
(c.copy(modules = c.modules ++ bbs), annos)
|
||||
}
|
||||
|
||||
}
|
||||
95
tapeout/src/main/scala/transforms/pads/FoundryPadsYaml.scala
Normal file
95
tapeout/src/main/scala/transforms/pads/FoundryPadsYaml.scala
Normal file
@@ -0,0 +1,95 @@
|
||||
package barstools.tapeout.transforms.pads
|
||||
|
||||
import net.jcazevedo.moultingyaml._
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import barstools.tapeout.transforms._
|
||||
|
||||
case class FoundryPad(
|
||||
tpe: String,
|
||||
name: String,
|
||||
width: Int,
|
||||
height: Int,
|
||||
supplySetNum: Option[Int],
|
||||
verilog: String) {
|
||||
|
||||
def padInstName = "PAD"
|
||||
|
||||
require(verilog.contains("{{#if isHorizontal}}"), "All pad templates must contain '{{#if isHorizontal}}'")
|
||||
require(verilog.contains("{{name}}"), "All pad templates must contain module name '{{name}}'")
|
||||
require(verilog.contains(padInstName), s"All pad templates should have instances called ${padInstName}")
|
||||
|
||||
def getSupplySetNum = supplySetNum.getOrElse(1)
|
||||
|
||||
val padType = tpe match {
|
||||
case "digital" =>
|
||||
require(verilog.contains(DigitalPad.inName), "Digital pad template must contain input called 'in'")
|
||||
require(verilog.contains(DigitalPad.outName), "Digital pad template must contain output called 'out'")
|
||||
require(verilog.contains("{{#if isInput}}"), "Digital pad template must contain '{{#if isInput}}'")
|
||||
DigitalPad
|
||||
case "analog" =>
|
||||
require(verilog.contains(AnalogPad.ioName), "Analog pad template must contain inout called 'io'")
|
||||
require(!verilog.contains("{{#if isInput}}"), "Analog pad template must not contain '{{#if isInput}}'")
|
||||
AnalogPad
|
||||
case "supply" =>
|
||||
// Supply pads don't have IO
|
||||
require(!verilog.contains("{{#if isInput}}"), "Supply pad template must not contain '{{#if isInput}}'")
|
||||
require(
|
||||
verilog.contains(s"${padInstName}["), "All supply pad templates should have instance arrays" +
|
||||
" called ${padInstName}[n:0], where n = ${getSupplySetNum-1}")
|
||||
require(supplySetNum.nonEmpty, "# of grouped supply pads 'supplySetNum' should be specified!")
|
||||
SupplyPad
|
||||
case _ => throw new Exception("Illegal pad type in config!")
|
||||
}
|
||||
|
||||
import com.gilt.handlebars.scala.binding.dynamic._
|
||||
import com.gilt.handlebars.scala.Handlebars
|
||||
private val template = Handlebars(verilog)
|
||||
|
||||
// Make sure names don't have spaces in Verilog!
|
||||
private[barstools] val correctedName = name.replace(" ", "_")
|
||||
|
||||
case class TemplateParams(
|
||||
// isInput only used with digital pads
|
||||
isInput: Boolean,
|
||||
isHorizontal: Boolean) {
|
||||
|
||||
private val orient = if (isHorizontal) Horizontal.serialize else Vertical.serialize
|
||||
private val dir = padType match {
|
||||
case AnalogPad => InOut.serialize
|
||||
case SupplyPad => NoDirection.serialize
|
||||
case DigitalPad => if (isInput) Input.serialize else Output.serialize
|
||||
}
|
||||
val name = {
|
||||
val start = Seq("pad", tpe, correctedName, orient)
|
||||
if (padType == DigitalPad) start :+ dir
|
||||
else start
|
||||
}.mkString("_")
|
||||
}
|
||||
|
||||
// Note: Analog + supply don't use direction
|
||||
private def getTemplateParams(dir: Direction, orient: PadOrientation): TemplateParams =
|
||||
TemplateParams(isInput = (dir == Input), isHorizontal = (orient == Horizontal))
|
||||
|
||||
def getVerilog(dir: Direction, orient: PadOrientation): String = {
|
||||
val p = getTemplateParams(dir, orient)
|
||||
template(p).stripMargin
|
||||
}
|
||||
|
||||
def getName(dir: Direction, orient: PadOrientation): String = getTemplateParams(dir, orient).name
|
||||
}
|
||||
|
||||
object FoundryPadsYaml extends DefaultYamlProtocol {
|
||||
val exampleResource = "/FoundryPads.yaml"
|
||||
implicit val _pad = yamlFormat6(FoundryPad)
|
||||
def parse(techDir: String): Seq[FoundryPad] = {
|
||||
val file = techDir + exampleResource
|
||||
if(techDir != "" && !(new java.io.File(file)).exists())
|
||||
throw new Exception("Technology directory must contain FoundryPads.yaml!")
|
||||
val out = (new YamlFileReader(exampleResource)).parse[FoundryPad](if (techDir == "") "" else file)
|
||||
val padNames = out.map(x => x.correctedName)
|
||||
require(padNames.distinct.length == padNames.length, "Pad names must be unique!")
|
||||
out
|
||||
}
|
||||
}
|
||||
133
tapeout/src/main/scala/transforms/pads/PadAnnotations.scala
Normal file
133
tapeout/src/main/scala/transforms/pads/PadAnnotations.scala
Normal file
@@ -0,0 +1,133 @@
|
||||
package barstools.tapeout.transforms.pads
|
||||
|
||||
import firrtl.annotations._
|
||||
import chisel3.experimental._
|
||||
import chisel3._
|
||||
import barstools.tapeout.transforms._
|
||||
import firrtl._
|
||||
|
||||
import net.jcazevedo.moultingyaml._
|
||||
|
||||
object PadAnnotationsYaml extends DefaultYamlProtocol {
|
||||
implicit val _iopad = yamlFormat2(IOPadAnnotation)
|
||||
implicit val _noiopad = yamlFormat1(NoIOPadAnnotation)
|
||||
implicit val _supplyanno = yamlFormat5(SupplyAnnotation)
|
||||
implicit val _modulepadanno = yamlFormat4(ModulePadAnnotation)
|
||||
}
|
||||
|
||||
abstract class FirrtlPadTransformAnnotation {
|
||||
def targetName: String
|
||||
}
|
||||
|
||||
// IO Port can either be annotated with padName + padSide OR noPad (mutually exclusive)
|
||||
abstract class IOAnnotation {
|
||||
def serialize: String
|
||||
}
|
||||
case class IOPadAnnotation(padSide: String, padName: String) extends IOAnnotation {
|
||||
import PadAnnotationsYaml._
|
||||
def serialize: String = this.toYaml.prettyPrint
|
||||
def getPadSide: PadSide = HasPadAnnotation.getSide(padSide)
|
||||
}
|
||||
case class NoIOPadAnnotation(noPad: String = "") extends IOAnnotation {
|
||||
import PadAnnotationsYaml._
|
||||
def serialize: String = this.toYaml.prettyPrint
|
||||
def field = "noPad:"
|
||||
}
|
||||
// Firrtl version
|
||||
case class TargetIOPadAnnoF(target: ComponentName, anno: IOAnnotation) extends FirrtlPadTransformAnnotation {
|
||||
def getAnno = Annotation(target, classOf[AddIOPadsTransform], anno.serialize)
|
||||
def targetName = target.name
|
||||
}
|
||||
// Chisel version
|
||||
case class TargetIOPadAnnoC(target: Element, anno: IOAnnotation) {
|
||||
def getAnno = ChiselAnnotation(target, classOf[AddIOPadsTransform], anno.serialize)
|
||||
}
|
||||
|
||||
// A bunch of supply pads (designated by name, # on each chip side) can be associated with the top module
|
||||
case class SupplyAnnotation(
|
||||
padName: String,
|
||||
leftSide: Int = 0,
|
||||
rightSide: Int = 0,
|
||||
topSide: Int = 0,
|
||||
bottomSide: Int = 0)
|
||||
// The chip top should have a default pad side, a pad template file, and supply annotations
|
||||
case class ModulePadAnnotation(
|
||||
defaultPadSide: String = Top.serialize,
|
||||
coreWidth: Int = 0,
|
||||
coreHeight: Int = 0,
|
||||
supplyAnnos: Seq[SupplyAnnotation] = Seq.empty) {
|
||||
import PadAnnotationsYaml._
|
||||
def serialize: String = this.toYaml.prettyPrint
|
||||
val supplyPadNames = supplyAnnos.map(_.padName)
|
||||
require(supplyPadNames.distinct.length == supplyPadNames.length, "Supply pads should only be specified once!")
|
||||
def getDefaultPadSide: PadSide = HasPadAnnotation.getSide(defaultPadSide)
|
||||
}
|
||||
// Firrtl version
|
||||
case class TargetModulePadAnnoF(target: ModuleName, anno: ModulePadAnnotation) extends FirrtlPadTransformAnnotation {
|
||||
def getAnno = Annotation(target, classOf[AddIOPadsTransform], anno.serialize)
|
||||
def targetName = target.name
|
||||
}
|
||||
// Chisel version
|
||||
case class TargetModulePadAnnoC(target: Module, anno: ModulePadAnnotation) {
|
||||
def getAnno = ChiselAnnotation(target, classOf[AddIOPadsTransform], anno.serialize)
|
||||
}
|
||||
|
||||
case class CollectedAnnos(
|
||||
componentAnnos: Seq[TargetIOPadAnnoF],
|
||||
moduleAnnos: TargetModulePadAnnoF) {
|
||||
def supplyAnnos = moduleAnnos.anno.supplyAnnos
|
||||
def defaultPadSide = moduleAnnos.anno.defaultPadSide
|
||||
def topModName = moduleAnnos.targetName
|
||||
def coreWidth = moduleAnnos.anno.coreWidth
|
||||
def coreHeight = moduleAnnos.anno.coreHeight
|
||||
}
|
||||
|
||||
object HasPadAnnotation {
|
||||
import PadAnnotationsYaml._
|
||||
|
||||
def getSide(a: String): PadSide = a match {
|
||||
case i if i == Left.serialize => Left
|
||||
case i if i == Right.serialize => Right
|
||||
case i if i == Top.serialize => Top
|
||||
case i if i == Bottom.serialize => Bottom
|
||||
case _ => throw new Exception(s" $a not a valid pad side annotation!")
|
||||
}
|
||||
|
||||
def unapply(a: Annotation): Option[FirrtlPadTransformAnnotation] = a match {
|
||||
case Annotation(f, t, s) if t == classOf[AddIOPadsTransform] => f match {
|
||||
case m: ModuleName =>
|
||||
Some(TargetModulePadAnnoF(m, s.parseYaml.convertTo[ModulePadAnnotation]))
|
||||
case c: ComponentName if s.contains(NoIOPadAnnotation().field) =>
|
||||
Some(TargetIOPadAnnoF(c, s.parseYaml.convertTo[NoIOPadAnnotation]))
|
||||
case c: ComponentName =>
|
||||
Some(TargetIOPadAnnoF(c, s.parseYaml.convertTo[IOPadAnnotation]))
|
||||
case _ => throw new Exception("Annotation only valid on module or component")
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
|
||||
def apply(annos: Seq[Annotation]): Option[CollectedAnnos] = {
|
||||
// Get all pad-related annotations (config files, pad sides, pad names, etc.)
|
||||
val padAnnos = annos.map(x => unapply(x)).flatten
|
||||
val targets = padAnnos.map(x => x.targetName)
|
||||
require(targets.distinct.length == targets.length, "Only 1 pad related annotation is allowed per component/module")
|
||||
if (padAnnos.length == 0) None
|
||||
else {
|
||||
val moduleAnnosTemp = padAnnos.filter {
|
||||
case TargetModulePadAnnoF(_, _) => true
|
||||
case _ => false
|
||||
}
|
||||
require(moduleAnnosTemp.length == 1, "Only 1 module may be designated 'Top'")
|
||||
val moduleAnnos = moduleAnnosTemp.head
|
||||
val topModName = moduleAnnos.targetName
|
||||
val componentAnnos = padAnnos.filter {
|
||||
case TargetIOPadAnnoF(ComponentName(_, ModuleName(n, _)), _) if n == topModName =>
|
||||
true
|
||||
case TargetIOPadAnnoF(ComponentName(_, ModuleName(n, _)), _) if n != topModName =>
|
||||
throw new Exception("Pad related component annotations must all be in the same top module")
|
||||
case _ => false
|
||||
}.map(x => x.asInstanceOf[TargetIOPadAnnoF])
|
||||
Some(CollectedAnnos(componentAnnos, moduleAnnos.asInstanceOf[TargetModulePadAnnoF]))
|
||||
}
|
||||
}
|
||||
}
|
||||
56
tapeout/src/main/scala/transforms/pads/PadDescriptors.scala
Normal file
56
tapeout/src/main/scala/transforms/pads/PadDescriptors.scala
Normal file
@@ -0,0 +1,56 @@
|
||||
package barstools.tapeout.transforms.pads
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
|
||||
abstract class PadOrientation extends FirrtlNode
|
||||
case object Horizontal extends PadOrientation {
|
||||
def serialize: String = "horizontal"
|
||||
}
|
||||
case object Vertical extends PadOrientation {
|
||||
def serialize: String = "vertical"
|
||||
}
|
||||
|
||||
abstract class PadType extends FirrtlNode
|
||||
case object DigitalPad extends PadType {
|
||||
def serialize: String = "digital"
|
||||
def inName: String = "in"
|
||||
def outName: String = "out"
|
||||
}
|
||||
case object AnalogPad extends PadType {
|
||||
def serialize: String = "analog"
|
||||
def ioName: String = "io"
|
||||
}
|
||||
case object SupplyPad extends PadType {
|
||||
def serialize: String = "supply"
|
||||
}
|
||||
case object NoPad extends PadType {
|
||||
def serialize: String = "none"
|
||||
}
|
||||
|
||||
case object InOut extends Direction {
|
||||
def serialize: String = "inout"
|
||||
}
|
||||
case object NoDirection extends Direction {
|
||||
def serialize: String = "none"
|
||||
}
|
||||
|
||||
abstract class PadSide extends FirrtlNode {
|
||||
def orientation: PadOrientation
|
||||
}
|
||||
case object Left extends PadSide {
|
||||
def serialize: String = "left"
|
||||
def orientation: PadOrientation = Horizontal
|
||||
}
|
||||
case object Right extends PadSide {
|
||||
def serialize: String = "right"
|
||||
def orientation: PadOrientation = Horizontal
|
||||
}
|
||||
case object Top extends PadSide {
|
||||
def serialize: String = "top"
|
||||
def orientation: PadOrientation = Vertical
|
||||
}
|
||||
case object Bottom extends PadSide {
|
||||
def serialize: String = "bottom"
|
||||
def orientation: PadOrientation = Vertical
|
||||
}
|
||||
116
tapeout/src/main/scala/transforms/pads/PadPlacement.scala
Normal file
116
tapeout/src/main/scala/transforms/pads/PadPlacement.scala
Normal file
@@ -0,0 +1,116 @@
|
||||
package barstools.tapeout.transforms.pads
|
||||
|
||||
import net.jcazevedo.moultingyaml._
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import barstools.tapeout.transforms._
|
||||
|
||||
case class PadPlacement(
|
||||
file: String,
|
||||
left: String,
|
||||
top: String,
|
||||
right: String,
|
||||
bottom: String,
|
||||
instanceArray: String,
|
||||
padLine: String,
|
||||
template: String) {
|
||||
|
||||
require(instanceArray contains "{{signal}}", "Instance Array Template should contain {{signal}}")
|
||||
require(instanceArray contains "{{idx}}", "Instance Array Template should contain {{idx}}")
|
||||
require(padLine contains "{{padInst}}", "Pad line should contain {{padInst}}")
|
||||
require(padLine contains "{{side}}", "Pad line should contain {{side}} (Can be in comments)")
|
||||
require(padLine contains "{{padIdx}}", "Pad line should contain {{padIdx}} (Can be in comments)")
|
||||
require(template contains "{{leftPads}}", "Pad line should contain {{leftPads}}")
|
||||
require(template contains "{{rightPads}}", "Pad line should contain {{rightPads}}")
|
||||
require(template contains "{{topPads}}", "Pad line should contain {{topPads}}")
|
||||
require(template contains "{{bottomPads}}", "Pad line should contain {{bottomPads}}")
|
||||
|
||||
def getSideString(s: PadSide): String = s match {
|
||||
case Left => left
|
||||
case Right => right
|
||||
case Top => top
|
||||
case Bottom => bottom
|
||||
}
|
||||
|
||||
import com.gilt.handlebars.scala.binding.dynamic._
|
||||
import com.gilt.handlebars.scala.Handlebars
|
||||
|
||||
private val instanceArrayTemplate = Handlebars(instanceArray.stripMargin)
|
||||
private val padLineTemplate = Handlebars(padLine.stripMargin)
|
||||
private val padPlacementTemplate = Handlebars(template.stripMargin)
|
||||
|
||||
def getInstanceArray(p: InstanceArrayParams): String = instanceArrayTemplate(p).stripMargin
|
||||
def getPadLine(p: PadLineParams): String = padLineTemplate(p).stripMargin.replace(""", "\"")
|
||||
def getPadPlacement(p: PadPlacementParams): String = padPlacementTemplate(p).stripMargin.replace(""", "\"")
|
||||
|
||||
}
|
||||
|
||||
case class InstanceArrayParams(signal: String, idx: Int)
|
||||
case class PadLineParams(padInst: String, side: String, padIdx: Int)
|
||||
case class PadPlacementParams(leftPads: String, rightPads: String, topPads: String, bottomPads: String)
|
||||
|
||||
object PadPlacementFile extends DefaultYamlProtocol {
|
||||
val exampleResource = "/PadPlacement.yaml"
|
||||
implicit val _pad = yamlFormat8(PadPlacement)
|
||||
def parse(file: String = ""): PadPlacement = {
|
||||
(new YamlFileReader(exampleResource)).parse[PadPlacement](file).head
|
||||
}
|
||||
def generate(
|
||||
techDir: String,
|
||||
targetDir: String,
|
||||
padFrameName: String,
|
||||
portPads: Seq[PortIOPad],
|
||||
supplyPads: Seq[TopSupplyPad]): Unit = {
|
||||
|
||||
val file = techDir + exampleResource
|
||||
if(techDir != "" && !(new java.io.File(file)).exists())
|
||||
throw new Exception("Technology directory must contain PadPlacement.yaml!")
|
||||
val template = parse(if (techDir == "") "" else file)
|
||||
|
||||
val leftPads = scala.collection.mutable.ArrayBuffer[String]()
|
||||
val rightPads = scala.collection.mutable.ArrayBuffer[String]()
|
||||
val topPads = scala.collection.mutable.ArrayBuffer[String]()
|
||||
val bottomPads = scala.collection.mutable.ArrayBuffer[String]()
|
||||
|
||||
def sort(side: PadSide, inst: String): Unit = side match {
|
||||
case Left => leftPads += inst
|
||||
case Right => rightPads += inst
|
||||
case Top => topPads += inst
|
||||
case Bottom => bottomPads += inst
|
||||
}
|
||||
|
||||
// TODO: Be smarter about supply placement (+ grouping?) between signals
|
||||
// Supply pad instance name: padFrameName/firrtlBBName_padSide_#num/PAD[#supplySetNum]
|
||||
supplyPads foreach { p =>
|
||||
val prefixes = p.arrayInstNamePrefix(padFrameName)
|
||||
prefixes foreach { prefix =>
|
||||
(0 until p.supplySetNum) foreach { idx =>
|
||||
sort(p.padSide, template.getInstanceArray(InstanceArrayParams(prefix, idx)))
|
||||
}
|
||||
}
|
||||
}
|
||||
// IO pad instance name: padFrameName/firrtlBBName/getPadName[#portWidth]/PAD
|
||||
portPads.filter(_.pad.nonEmpty) foreach { p =>
|
||||
val prefix = p.arrayInstNamePrefix(padFrameName)
|
||||
(0 until p.portWidth).map(idx =>
|
||||
template.getInstanceArray(InstanceArrayParams(prefix, idx)) + p.arrayInstNameSuffix
|
||||
) foreach { x => sort(p.padSide, x) }
|
||||
}
|
||||
|
||||
def getLines(pads: Seq[String], side: PadSide): String = {
|
||||
val seq = pads.zipWithIndex.map{ case (p, idx) =>
|
||||
template.getPadLine(PadLineParams(p, template.getSideString(side), idx)) }
|
||||
seq.mkString("\n")
|
||||
}
|
||||
|
||||
val fileContents = template.getPadPlacement(PadPlacementParams(
|
||||
leftPads = getLines(leftPads.toSeq, Left),
|
||||
rightPads = getLines(rightPads.toSeq, Right),
|
||||
topPads = getLines(topPads.toSeq, Top),
|
||||
bottomPads = getLines(bottomPads.toSeq, Bottom)
|
||||
))
|
||||
|
||||
WriteConfig(targetDir, template.file, fileContents)
|
||||
}
|
||||
}
|
||||
158
tapeout/src/main/scala/transforms/utils/DiGraph.scala
Normal file
158
tapeout/src/main/scala/transforms/utils/DiGraph.scala
Normal file
@@ -0,0 +1,158 @@
|
||||
package firrtl
|
||||
|
||||
import scala.collection.immutable.{HashSet, HashMap}
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.MultiMap
|
||||
|
||||
class MutableDiGraph[T](
|
||||
val edgeData: MultiMap[T,T] = new mutable.HashMap[T, mutable.Set[T]] with MultiMap[T, T]) {
|
||||
def contains(v: T) = edgeData.contains(v)
|
||||
def getVertices = edgeData.keys
|
||||
def getEdges(v: T) = edgeData(v)
|
||||
def addVertex(v: T): T = {
|
||||
edgeData.getOrElseUpdate(v,new mutable.HashSet[T])
|
||||
v
|
||||
}
|
||||
// Add v to keys to maintain invariant
|
||||
def addEdge(u: T, v: T) = {
|
||||
edgeData.getOrElseUpdate(v, new mutable.HashSet[T])
|
||||
edgeData.addBinding(u,v)
|
||||
}
|
||||
}
|
||||
|
||||
object DiGraph {
|
||||
def apply[T](mdg: MutableDiGraph[T]) = new DiGraph((mdg.edgeData mapValues { _.toSet }).toMap[T, Set[T]])
|
||||
def apply[T](edgeData: MultiMap[T,T]) = new DiGraph((edgeData mapValues { _.toSet }).toMap[T, Set[T]])
|
||||
}
|
||||
|
||||
class DiGraph[T] (val edges: Map[T, Set[T]]) {
|
||||
|
||||
def getVertices = edges.keys
|
||||
def getEdges(v: T) = edges.getOrElse(v, new HashSet[T])
|
||||
|
||||
// Graph must be acyclic for valid linearization
|
||||
def linearize(root: T) = {
|
||||
val order = new mutable.ArrayBuffer[T]
|
||||
val visited = new mutable.HashSet[T]
|
||||
def explore(v: T): Unit = {
|
||||
visited += v
|
||||
for (u <- getEdges(v)) {
|
||||
if (!visited.contains(u)) {
|
||||
explore(u)
|
||||
}
|
||||
}
|
||||
order.append(v)
|
||||
}
|
||||
explore(root)
|
||||
order.reverse.toList
|
||||
}
|
||||
|
||||
def doBFS(root: T) = {
|
||||
val prev = new mutable.HashMap[T,T]
|
||||
val queue = new mutable.Queue[T]
|
||||
queue.enqueue(root)
|
||||
while (!queue.isEmpty) {
|
||||
val u = queue.dequeue
|
||||
for (v <- getEdges(u)) {
|
||||
if (!prev.contains(v)) {
|
||||
prev(v) = u
|
||||
queue.enqueue(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
prev
|
||||
}
|
||||
|
||||
def reachabilityBFS(root: T) = doBFS(root).keys.toSet
|
||||
|
||||
def path(start: T, end: T) = {
|
||||
val nodePath = new mutable.ArrayBuffer[T]
|
||||
val prev = doBFS(start)
|
||||
nodePath += end
|
||||
while (nodePath.last != start) {
|
||||
nodePath += prev(nodePath.last)
|
||||
}
|
||||
nodePath.toList.reverse
|
||||
}
|
||||
|
||||
def findSCCs = {
|
||||
var counter: BigInt = 0
|
||||
val stack = new mutable.Stack[T]
|
||||
val onstack = new mutable.HashSet[T]
|
||||
val indices = new mutable.HashMap[T, BigInt]
|
||||
val lowlinks = new mutable.HashMap[T, BigInt]
|
||||
val sccs = new mutable.ArrayBuffer[List[T]]
|
||||
|
||||
def strongConnect(v: T): Unit = {
|
||||
indices(v) = counter
|
||||
lowlinks(v) = counter
|
||||
counter = counter + 1
|
||||
stack.push(v)
|
||||
onstack += v
|
||||
for (w <- getEdges(v)) {
|
||||
if (!indices.contains(w)) {
|
||||
strongConnect(w)
|
||||
lowlinks(v) = lowlinks(v).min(lowlinks(w))
|
||||
} else if (onstack.contains(w)) {
|
||||
lowlinks(v) = lowlinks(v).min(indices(w))
|
||||
}
|
||||
}
|
||||
if (lowlinks(v) == indices(v)) {
|
||||
val scc = new mutable.ArrayBuffer[T]
|
||||
do {
|
||||
val w = stack.pop
|
||||
onstack -= w
|
||||
scc += w
|
||||
}
|
||||
while (scc.last != v);
|
||||
sccs.append(scc.toList)
|
||||
}
|
||||
}
|
||||
|
||||
for (v <- getVertices) {
|
||||
strongConnect(v)
|
||||
}
|
||||
|
||||
sccs.toList
|
||||
}
|
||||
|
||||
def pathsInDAG(start: T): Map[T,List[List[T]]] = {
|
||||
// paths(v) holds the set of paths from start to v
|
||||
val paths = new mutable.HashMap[T,mutable.Set[List[T]]] with mutable.MultiMap[T,List[T]]
|
||||
val queue = new mutable.Queue[T]
|
||||
val visited = new mutable.HashSet[T]
|
||||
paths.addBinding(start,List(start))
|
||||
queue.enqueue(start)
|
||||
visited += start
|
||||
while (!queue.isEmpty) {
|
||||
val current = queue.dequeue
|
||||
for (v <- getEdges(current)) {
|
||||
if (!visited.contains(v)) {
|
||||
queue.enqueue(v)
|
||||
visited += v
|
||||
}
|
||||
for (p <- paths(current)) {
|
||||
paths.addBinding(v, p :+ v)
|
||||
}
|
||||
}
|
||||
}
|
||||
(paths map { case (k,v) => (k,v.toList) }).toMap
|
||||
}
|
||||
|
||||
def reverse = {
|
||||
val mdg = new MutableDiGraph[T]
|
||||
edges foreach { case (u,edges) => edges.foreach({ v => mdg.addEdge(v,u) }) }
|
||||
DiGraph(mdg)
|
||||
}
|
||||
|
||||
def simplify(vprime: Set[T]) = {
|
||||
val eprime = vprime.map( v => (v,reachabilityBFS(v) & vprime) ).toMap
|
||||
new DiGraph(eprime)
|
||||
}
|
||||
|
||||
def transformNodes[Q](f: (T) => Q): DiGraph[Q] = {
|
||||
val eprime = edges.map({ case (k,v) => (f(k),v.map(f(_))) })
|
||||
new DiGraph(eprime)
|
||||
}
|
||||
|
||||
}
|
||||
65
tapeout/src/main/scala/transforms/utils/FileUtils.scala
Normal file
65
tapeout/src/main/scala/transforms/utils/FileUtils.scala
Normal file
@@ -0,0 +1,65 @@
|
||||
package barstools.tapeout.transforms
|
||||
|
||||
import firrtl._
|
||||
import firrtl.annotations._
|
||||
import firrtl.passes._
|
||||
import firrtl.ir._
|
||||
|
||||
object WriteConfig {
|
||||
def apply(dir: String, file: String, contents: String): Unit = {
|
||||
val writer = new java.io.PrintWriter(new java.io.File(s"$dir/$file"))
|
||||
writer write contents
|
||||
writer.close()
|
||||
}
|
||||
}
|
||||
|
||||
object GetTargetDir {
|
||||
def apply(state: CircuitState): String = {
|
||||
val annos = state.annotations.getOrElse(AnnotationMap(Seq.empty)).annotations
|
||||
val destDir = annos.map {
|
||||
case Annotation(f, t, s) if t == classOf[transforms.BlackBoxSourceHelper] =>
|
||||
transforms.BlackBoxSource.parse(s) match {
|
||||
case Some(transforms.BlackBoxTargetDir(dest)) => Some(dest)
|
||||
case _ => None
|
||||
}
|
||||
case _ => None
|
||||
}.flatten
|
||||
val loc = {
|
||||
if (destDir.isEmpty) "."
|
||||
else destDir.head
|
||||
}
|
||||
val targetDir = new java.io.File(loc)
|
||||
if(!targetDir.exists()) FileUtils.makeDirectory(targetDir.getAbsolutePath)
|
||||
loc
|
||||
}
|
||||
}
|
||||
|
||||
// Fake transform just to track Technology information directory
|
||||
object TechnologyLocation {
|
||||
def apply(dir: String): Annotation = {
|
||||
Annotation(CircuitName("All"), classOf[TechnologyLocation], dir)
|
||||
}
|
||||
}
|
||||
class TechnologyLocation extends Transform {
|
||||
def inputForm: CircuitForm = LowForm
|
||||
def outputForm: CircuitForm = LowForm
|
||||
def execute(state: CircuitState) = throw new Exception("Technology Location transform execution doesn't work!")
|
||||
def get(state: CircuitState): String = {
|
||||
val annos = state.annotations.getOrElse(AnnotationMap(Seq.empty)).annotations
|
||||
val dir = annos.map {
|
||||
case Annotation(f, t, s) if t == classOf[TechnologyLocation] => Some(s)
|
||||
case _ => None
|
||||
}.flatten
|
||||
dir.length match {
|
||||
case 0 => ""
|
||||
case 1 =>
|
||||
val targetDir = new java.io.File(dir.head)
|
||||
if(!targetDir.exists()) throw new Exception("Technology yaml directory doesn't exist!")
|
||||
dir.head
|
||||
case _ => throw new Exception("Only 1 tech directory annotation allowed!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
51
tapeout/src/main/scala/transforms/utils/InstanceGraph.scala
Normal file
51
tapeout/src/main/scala/transforms/utils/InstanceGraph.scala
Normal file
@@ -0,0 +1,51 @@
|
||||
package firrtl.analyses
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.Utils._
|
||||
import firrtl.Mappers._
|
||||
|
||||
class InstanceGraph(c: Circuit) {
|
||||
|
||||
private def collectInstances(insts: mutable.Set[WDefInstance])(s: Statement): Statement = s match {
|
||||
case i: WDefInstance =>
|
||||
insts += i
|
||||
i
|
||||
case _ =>
|
||||
s map collectInstances(insts)
|
||||
s
|
||||
}
|
||||
|
||||
val moduleMap = c.modules.map({m => (m.name,m) }).toMap
|
||||
val childInstances =
|
||||
new mutable.HashMap[String,mutable.Set[WDefInstance]]
|
||||
for (m <- c.modules) {
|
||||
childInstances(m.name) = new mutable.HashSet[WDefInstance]
|
||||
m map collectInstances(childInstances(m.name))
|
||||
}
|
||||
val instanceGraph = new MutableDiGraph[WDefInstance]
|
||||
val instanceQueue = new mutable.Queue[WDefInstance]
|
||||
val topInstance = WDefInstance(c.main,c.main) // top instance
|
||||
instanceQueue.enqueue(topInstance)
|
||||
while (!instanceQueue.isEmpty) {
|
||||
val current = instanceQueue.dequeue
|
||||
for (child <- childInstances(current.module)) {
|
||||
if (!instanceGraph.contains(child)) {
|
||||
instanceQueue.enqueue(child)
|
||||
}
|
||||
instanceGraph.addEdge(current,child)
|
||||
}
|
||||
}
|
||||
|
||||
val graph = DiGraph(instanceGraph)
|
||||
|
||||
lazy val fullHierarchy = graph.pathsInDAG(topInstance)
|
||||
|
||||
def findInstancesInHierarchy(module: String): List[List[WDefInstance]] = {
|
||||
val instances = graph.getVertices.filter(_.module == module).toList
|
||||
instances flatMap { i => fullHierarchy(i) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package barstools.tapeout.transforms
|
||||
|
||||
object LowerName {
|
||||
def apply(s: String): String = s.replace(".", "_").replace("[", "_")replace("]", "")
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package barstools.tapeout.transforms
|
||||
|
||||
import chisel3._
|
||||
import scala.collection.immutable.ListMap
|
||||
|
||||
final class CustomBundle(elts: (String, Data)*) extends Record {
|
||||
val elements = ListMap(elts map { case (field, elt) => field -> elt.chiselCloneType }: _*)
|
||||
def apply(elt: String): Data = elements(elt)
|
||||
override def cloneType = (new CustomBundle(elements.toList: _*)).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
final class CustomIndexedBundle(elts: (Int, Data)*) extends Record {
|
||||
// Must be String, Data
|
||||
val elements = ListMap(elts map { case (field, elt) => field.toString -> elt.chiselCloneType }: _*)
|
||||
def indexedElements = ListMap(elts map { case (field, elt) => field -> elt.chiselCloneType }: _*)
|
||||
def apply(elt: Int): Data = elements(elt.toString)
|
||||
override def cloneType = (new CustomIndexedBundle(indexedElements.toList: _*)).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
object CustomIndexedBundle {
|
||||
def apply(gen: Data, idxs: Seq[Int]) = new CustomIndexedBundle(idxs.map(_ -> gen): _*)
|
||||
// Allows Vecs of elements of different types/widths
|
||||
def apply(gen: Seq[Data]) = new CustomIndexedBundle(gen.zipWithIndex.map{ case (elt, field) => field -> elt }: _*)
|
||||
}
|
||||
21
tapeout/src/main/scala/transforms/utils/YamlHelpers.scala
Normal file
21
tapeout/src/main/scala/transforms/utils/YamlHelpers.scala
Normal file
@@ -0,0 +1,21 @@
|
||||
package barstools.tapeout.transforms
|
||||
|
||||
import net.jcazevedo.moultingyaml._
|
||||
import java.io.File
|
||||
|
||||
class YamlFileReader(resource: String) {
|
||||
def parse[A](file: String = "")(implicit reader: YamlReader[A]) : Seq[A] = {
|
||||
// If the user doesn't provide a Yaml file name, use defaults
|
||||
val yamlString = file match {
|
||||
case f if f.isEmpty =>
|
||||
// Use example config if no file is provided
|
||||
val stream = getClass.getResourceAsStream(resource)
|
||||
io.Source.fromInputStream(stream).mkString
|
||||
case f if new File(f).exists =>
|
||||
scala.io.Source.fromFile(f).getLines.mkString("\n")
|
||||
case _ =>
|
||||
throw new Exception("No valid Yaml file found!")
|
||||
}
|
||||
yamlString.parseYamls.map(x => reader.read(x))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user