diff --git a/.gitignore b/.gitignore index 2179f6e0..f4406576 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ -### local stuff +### Local stuff +proprietary/ src/main/scala/dsptools/sandbox.sc test_run_dir/ *.fir +*.f *.anno + ### XilinxISE template # intermediate build files *.bgn @@ -342,4 +345,4 @@ project/plugins/project/ hs_err_pid* # ignore lib from rocket build -lib +lib \ No newline at end of file diff --git a/build.sbt b/build.sbt index 7374d991..6f61668a 100644 --- a/build.sbt +++ b/build.sbt @@ -6,7 +6,7 @@ lazy val commonSettings = Seq( organization := "edu.berkeley.cs", version := "0.1-SNAPSHOT", scalaVersion := "2.11.8", - scalacOptions := Seq("-deprecation", "-feature"), + scalacOptions := Seq("-deprecation", "-feature", "-language:reflectiveCalls"), libraryDependencies ++= commonDependencies ) @@ -21,4 +21,5 @@ lazy val tapeout = (project in file("tapeout")) libraryDependencies ++= Seq("chisel3","chisel-iotesters").map { dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) } - ) \ No newline at end of file + ) + .settings(scalacOptions in Test ++= Seq("-language:reflectiveCalls")) \ No newline at end of file diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 6395b980..f4423b4d 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -7,9 +7,14 @@ object Dependencies { val scalacheckVersion = "1.12.4" val scalacheck = "org.scalacheck" %% "scalacheck" % scalacheckVersion % "test" + // Templating! + val handlebarsVersion = "2.1.1" + val handlebars = "com.gilt" %% "handlebars-scala" % handlebarsVersion + val commonDependencies: Seq[ModuleID] = Seq( scalatest, - scalacheck + scalacheck, + handlebars ) } \ No newline at end of file diff --git a/tapeout/src/main/resources/FoundryPads.yaml b/tapeout/src/main/resources/FoundryPads.yaml new file mode 100644 index 00000000..a6133be0 --- /dev/null +++ b/tapeout/src/main/resources/FoundryPads.yaml @@ -0,0 +1,113 @@ +# Pad types must be one of digital, analog, or supply; pad names must be unique! +# This just shows you how you can template things with {{}}, if/else, and the following parameters: +# isInput: Boolean (each digital pad entry should be configurable between both input and output) +# isHorizontal: Boolean (each pad entry should be configurable between both horizontal and vertical) +# NOTE: Expects 1-bit in/out to be named in/out for digital; and 1-bit io for analog (supplies don't have ports) +# Expects module name to be obtained from {{name}} which is derived from yaml name, tpe in the Firrtl pass +# Pipe is used for stripping margins, but indentation is required before the pipe for the yaml reader to work +--- +tpe: analog +name: slow_foundry +width: 0 +height: 0 +verilog: | + |// Foundry Analog Pad Example + |// Pad Orientation: {{#if isHorizontal}}Horizontal{{else}}Vertical{{/if}} + |// Call your instance PAD + |module {{name}}( + | inout io + |); + |endmodule +--- +tpe: analog +name: fast_custom +width: 0 +height: 0 +verilog: | + |// Custom Analog Pad Example + |// Pad Orientation: {{#if isHorizontal}}Horizontal{{else}}Vertical{{/if}} + |// Call your instance PAD + |module {{name}}( + | inout io + |); + |endmodule +--- +tpe: digital +name: from_tristate_foundry +width: 0 +height: 0 +verilog: | + |// Digital Pad Example + |// Signal Direction: {{#if isInput}}Input{{else}}Output{{/if}} + |// Pad Orientation: {{#if isHorizontal}}Horizontal{{else}}Vertical{{/if}} + |// Call your instance PAD + |module {{name}}( + | input in, + | output reg out + |); + | // Where you would normally dump your pad instance + | always @* begin + | out = in; + | end + |endmodule +--- +tpe: digital +name: fake_digital +width: 0 +height: 0 +verilog: | + |// (Fake/Unused) Digital Pad Example + |// Signal Direction: {{#if isInput}}Input{{else}}Output{{/if}} + |// Pad Orientation: {{#if isHorizontal}}Horizontal{{else}}Vertical{{/if}} + |// Call your instance PAD + |module {{name}}( + | input in, + | output reg out + |); + | // Where you would normally dump your pad instance + | always @* begin + | out = in; + | end + |endmodule +--- +tpe: supply +name: vdd +width: 0 +height: 0 +supplySetNum: 1 +verilog: | + |// VDD Pad Example (No IO) + |// Can group some number together as required by the foundry + |// Pad Orientation: {{#if isHorizontal}}Horizontal{{else}}Vertical{{/if}} + |// Call your instance array PAD[0:0], PAD[2:0], etc. + |module {{name}}( + |); + |endmodule +--- +tpe: supply +name: vss +width: 0 +height: 0 +supplySetNum: 2 +verilog: | + |// VSS Pad Example (No IO) + |// Can group some number together as required by the foundry + |// Pad Orientation: {{#if isHorizontal}}Horizontal{{else}}Vertical{{/if}} + |// Call your instance array PAD[0:0], PAD[2:0], etc. + |module {{name}}( + |); + |endmodule +--- +tpe: supply +name: avss +width: 0 +height: 0 +supplySetNum: 1 +verilog: | + |// Analog VSS Pad Example (No IO) + |// Can group some number together as required by the foundry + |// Pad Orientation: {{#if isHorizontal}}Horizontal{{else}}Vertical{{/if}} + |// Call your instance array PAD[0:0], PAD[2:0], etc. + |module {{name}}( + |); + |endmodule diff --git a/tapeout/src/main/resources/PadPlacement.yaml b/tapeout/src/main/resources/PadPlacement.yaml new file mode 100644 index 00000000..a8a94f1c --- /dev/null +++ b/tapeout/src/main/resources/PadPlacement.yaml @@ -0,0 +1,43 @@ +# Example for Innovus: https://legacy.iis.ee.ethz.ch/~vlsi2/ex05/ex05.pdf +--- +file: pads.io +left: "1" # Bottom to top +top: "2" # Left to right +right: "3" # Bottom to top +bottom: "4" # Left to right +# Note: In your scripts, you should specify instance array styles +# i.e. hdl_instance_array_naming_style string (For Genus) +instanceArray: "{{signal}}[{{idx}}]" +padLine: | + | (inst name = "{{padInst}}") # Side: {{side}}, Order: {{padIdx}} +template: | + |(globals + | version = 3 + | io_order = default + |) + |(iopad + | (bottomleft + | (inst name="corner_ll" cell="CORNER_EXAMPLE" ) + | ) + | (bottomright + | (inst name="corner_lr" orientation=MY cell="CORNER_EXAMPLE" ) + | ) + | (topleft + | (inst name="corner_ul" orientation=MX cell="CORNER_EXAMPLE" ) + | ) + | (topright + | (inst name="corner_ur" cell="CORNER_EXAMPLE" ) + | ) + | (left + |{{leftPads}} + | ) + | (right + |{{rightPads}} + | ) + | (top + |{{topPads}} + | ) + | (bottom + |{{bottomPads}} + | ) + |) \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/clkgen/ClkAnnotations.scala b/tapeout/src/main/scala/transforms/clkgen/ClkAnnotations.scala new file mode 100644 index 00000000..832cfbb1 --- /dev/null +++ b/tapeout/src/main/scala/transforms/clkgen/ClkAnnotations.scala @@ -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) + } +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/clkgen/ClkDivider.scala b/tapeout/src/main/scala/transforms/clkgen/ClkDivider.scala new file mode 100644 index 00000000..755a66aa --- /dev/null +++ b/tapeout/src/main/scala/transforms/clkgen/ClkDivider.scala @@ -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!") + } +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/clkgen/ClkSrcTransform.scala b/tapeout/src/main/scala/transforms/clkgen/ClkSrcTransform.scala new file mode 100644 index 00000000..a003abf8 --- /dev/null +++ b/tapeout/src/main/scala/transforms/clkgen/ClkSrcTransform.scala @@ -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) + } + } +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/clkgen/CreateClkConstraints.scala b/tapeout/src/main/scala/transforms/clkgen/CreateClkConstraints.scala new file mode 100644 index 00000000..2e5021de --- /dev/null +++ b/tapeout/src/main/scala/transforms/clkgen/CreateClkConstraints.scala @@ -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 + } +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/pads/AddIOPadsTransform.scala b/tapeout/src/main/scala/transforms/pads/AddIOPadsTransform.scala new file mode 100644 index 00000000..2531a632 --- /dev/null +++ b/tapeout/src/main/scala/transforms/pads/AddIOPadsTransform.scala @@ -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) + } + } +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/pads/AddPadFrame.scala b/tapeout/src/main/scala/transforms/pads/AddPadFrame.scala new file mode 100644 index 00000000..17b22fbb --- /dev/null +++ b/tapeout/src/main/scala/transforms/pads/AddPadFrame.scala @@ -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)) + } + +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/pads/AnnotatePortPads.scala b/tapeout/src/main/scala/transforms/pads/AnnotatePortPads.scala new file mode 100644 index 00000000..1f6911a0 --- /dev/null +++ b/tapeout/src/main/scala/transforms/pads/AnnotatePortPads.scala @@ -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)) + } +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/pads/AnnotateSupplyPads.scala b/tapeout/src/main/scala/transforms/pads/AnnotateSupplyPads.scala new file mode 100644 index 00000000..27b93b46 --- /dev/null +++ b/tapeout/src/main/scala/transforms/pads/AnnotateSupplyPads.scala @@ -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) + } +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/pads/ChiselTopModule.scala b/tapeout/src/main/scala/transforms/pads/ChiselTopModule.scala new file mode 100644 index 00000000..8f891e62 --- /dev/null +++ b/tapeout/src/main/scala/transforms/pads/ChiselTopModule.scala @@ -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() +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/pads/CreatePadBBs.scala b/tapeout/src/main/scala/transforms/pads/CreatePadBBs.scala new file mode 100644 index 00000000..1a7f2aa9 --- /dev/null +++ b/tapeout/src/main/scala/transforms/pads/CreatePadBBs.scala @@ -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) + } + +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/pads/FoundryPadsYaml.scala b/tapeout/src/main/scala/transforms/pads/FoundryPadsYaml.scala new file mode 100644 index 00000000..ff1b92f0 --- /dev/null +++ b/tapeout/src/main/scala/transforms/pads/FoundryPadsYaml.scala @@ -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 + } +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/pads/PadAnnotations.scala b/tapeout/src/main/scala/transforms/pads/PadAnnotations.scala new file mode 100644 index 00000000..66b7f184 --- /dev/null +++ b/tapeout/src/main/scala/transforms/pads/PadAnnotations.scala @@ -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])) + } + } +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/pads/PadDescriptors.scala b/tapeout/src/main/scala/transforms/pads/PadDescriptors.scala new file mode 100644 index 00000000..95a2cb1b --- /dev/null +++ b/tapeout/src/main/scala/transforms/pads/PadDescriptors.scala @@ -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 +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/pads/PadPlacement.scala b/tapeout/src/main/scala/transforms/pads/PadPlacement.scala new file mode 100644 index 00000000..2d774f01 --- /dev/null +++ b/tapeout/src/main/scala/transforms/pads/PadPlacement.scala @@ -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) + } +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/utils/DiGraph.scala b/tapeout/src/main/scala/transforms/utils/DiGraph.scala new file mode 100644 index 00000000..8e0db078 --- /dev/null +++ b/tapeout/src/main/scala/transforms/utils/DiGraph.scala @@ -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) + } + +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/utils/FileUtils.scala b/tapeout/src/main/scala/transforms/utils/FileUtils.scala new file mode 100644 index 00000000..5fc35842 --- /dev/null +++ b/tapeout/src/main/scala/transforms/utils/FileUtils.scala @@ -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!") + } + } +} + + + diff --git a/tapeout/src/main/scala/transforms/utils/InstanceGraph.scala b/tapeout/src/main/scala/transforms/utils/InstanceGraph.scala new file mode 100644 index 00000000..10b37ea8 --- /dev/null +++ b/tapeout/src/main/scala/transforms/utils/InstanceGraph.scala @@ -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) } + } + +} diff --git a/tapeout/src/main/scala/transforms/utils/LowerAnnotations.scala b/tapeout/src/main/scala/transforms/utils/LowerAnnotations.scala new file mode 100644 index 00000000..a11bfa19 --- /dev/null +++ b/tapeout/src/main/scala/transforms/utils/LowerAnnotations.scala @@ -0,0 +1,5 @@ +package barstools.tapeout.transforms + +object LowerName { + def apply(s: String): String = s.replace(".", "_").replace("[", "_")replace("]", "") +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/utils/ProgrammaticBundle.scala b/tapeout/src/main/scala/transforms/utils/ProgrammaticBundle.scala new file mode 100644 index 00000000..7df199ba --- /dev/null +++ b/tapeout/src/main/scala/transforms/utils/ProgrammaticBundle.scala @@ -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 }: _*) +} \ No newline at end of file diff --git a/tapeout/src/main/scala/transforms/utils/YamlHelpers.scala b/tapeout/src/main/scala/transforms/utils/YamlHelpers.scala new file mode 100644 index 00000000..6754136d --- /dev/null +++ b/tapeout/src/main/scala/transforms/utils/YamlHelpers.scala @@ -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)) + } +} \ No newline at end of file diff --git a/tapeout/src/test/resources/PadAnnotationVerilogPart.v b/tapeout/src/test/resources/PadAnnotationVerilogPart.v new file mode 100644 index 00000000..9e4b257f --- /dev/null +++ b/tapeout/src/test/resources/PadAnnotationVerilogPart.v @@ -0,0 +1,231 @@ +module ExampleTopModuleWithBB_PadFrame( + output clock_Int, + output reset_Int, + output [14:0] io_a_Int, + output [14:0] io_b_Int, + output [13:0] io_c_Int, + input [15:0] io_x_Int, + input [15:0] io_y_Int, + input [15:0] io_z_Int, + input [4:0] io_v_0_Int, + input [4:0] io_v_1_Int, + input [4:0] io_v_2_Int, + input clock_Ext, + input reset_Ext, + input [14:0] io_a_Ext, + input [14:0] io_b_Ext, + input [13:0] io_c_Ext, + output [15:0] io_x_Ext, + output [15:0] io_y_Ext, + output [15:0] io_z_Ext, + inout [2:0] io_analog1_Ext, + inout [2:0] io_analog2_Ext, + output [4:0] io_v_0_Ext, + output [4:0] io_v_1_Ext, + output [4:0] io_v_2_Ext +); + wire pad_digital_from_tristate_foundry_vertical_input_array_reset_in; + wire pad_digital_from_tristate_foundry_vertical_input_array_reset_out; + wire [14:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_a_in; + wire [14:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_a_out; + wire [14:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_b_in; + wire [14:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_b_out; + wire [13:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_c_in; + wire [13:0] pad_digital_from_tristate_foundry_horizontal_input_array_io_c_out; + wire [15:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_x_in; + wire [15:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_x_out; + wire [15:0] pad_digital_from_tristate_foundry_vertical_output_array_io_z_in; + wire [15:0] pad_digital_from_tristate_foundry_vertical_output_array_io_z_out; + wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_in; + wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_out; + wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_in; + wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_out; + wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_in; + wire [4:0] pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_out; + pad_digital_from_tristate_foundry_vertical_input_array #(.WIDTH(1)) pad_digital_from_tristate_foundry_vertical_input_array_reset ( + .in(pad_digital_from_tristate_foundry_vertical_input_array_reset_in), + .out(pad_digital_from_tristate_foundry_vertical_input_array_reset_out) + ); + pad_digital_from_tristate_foundry_horizontal_input_array #(.WIDTH(15)) pad_digital_from_tristate_foundry_horizontal_input_array_io_a ( + .in(pad_digital_from_tristate_foundry_horizontal_input_array_io_a_in), + .out(pad_digital_from_tristate_foundry_horizontal_input_array_io_a_out) + ); + pad_digital_from_tristate_foundry_horizontal_input_array #(.WIDTH(15)) pad_digital_from_tristate_foundry_horizontal_input_array_io_b ( + .in(pad_digital_from_tristate_foundry_horizontal_input_array_io_b_in), + .out(pad_digital_from_tristate_foundry_horizontal_input_array_io_b_out) + ); + pad_digital_from_tristate_foundry_horizontal_input_array #(.WIDTH(14)) pad_digital_from_tristate_foundry_horizontal_input_array_io_c ( + .in(pad_digital_from_tristate_foundry_horizontal_input_array_io_c_in), + .out(pad_digital_from_tristate_foundry_horizontal_input_array_io_c_out) + ); + pad_digital_from_tristate_foundry_horizontal_output_array #(.WIDTH(16)) pad_digital_from_tristate_foundry_horizontal_output_array_io_x ( + .in(pad_digital_from_tristate_foundry_horizontal_output_array_io_x_in), + .out(pad_digital_from_tristate_foundry_horizontal_output_array_io_x_out) + ); + pad_digital_from_tristate_foundry_vertical_output_array #(.WIDTH(16)) pad_digital_from_tristate_foundry_vertical_output_array_io_z ( + .in(pad_digital_from_tristate_foundry_vertical_output_array_io_z_in), + .out(pad_digital_from_tristate_foundry_vertical_output_array_io_z_out) + ); + pad_analog_fast_custom_horizontal_array #(.WIDTH(3)) pad_analog_fast_custom_horizontal_array_io_analog1 ( + .io(io_analog1_Ext) + ); + pad_analog_slow_foundry_vertical_array #(.WIDTH(3)) pad_analog_slow_foundry_vertical_array_io_analog2 ( + .io(io_analog2_Ext) + ); + pad_digital_from_tristate_foundry_horizontal_output_array #(.WIDTH(5)) pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0 ( + .in(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_in), + .out(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_out) + ); + pad_digital_from_tristate_foundry_horizontal_output_array #(.WIDTH(5)) pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1 ( + .in(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_in), + .out(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_out) + ); + pad_digital_from_tristate_foundry_horizontal_output_array #(.WIDTH(5)) pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2 ( + .in(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_in), + .out(pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_out) + ); + pad_supply_vdd_horizontal pad_supply_vdd_horizontal_left_0 ( + ); + pad_supply_vdd_horizontal pad_supply_vdd_horizontal_left_1 ( + ); + pad_supply_vdd_horizontal pad_supply_vdd_horizontal_left_2 ( + ); + pad_supply_vdd_vertical pad_supply_vdd_vertical_bottom_0 ( + ); + pad_supply_vdd_vertical pad_supply_vdd_vertical_bottom_1 ( + ); + pad_supply_vss_horizontal pad_supply_vss_horizontal_right_0 ( + ); + assign clock_Int = clock_Ext; + assign reset_Int = pad_digital_from_tristate_foundry_vertical_input_array_reset_out; + assign io_a_Int = pad_digital_from_tristate_foundry_horizontal_input_array_io_a_out; + assign io_b_Int = pad_digital_from_tristate_foundry_horizontal_input_array_io_b_out; + assign io_c_Int = $signed(pad_digital_from_tristate_foundry_horizontal_input_array_io_c_out); + assign io_x_Ext = pad_digital_from_tristate_foundry_horizontal_output_array_io_x_out; + assign io_y_Ext = io_y_Int; + assign io_z_Ext = $signed(pad_digital_from_tristate_foundry_vertical_output_array_io_z_out); + assign io_v_0_Ext = pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_out; + assign io_v_1_Ext = pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_out; + assign io_v_2_Ext = pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_out; + assign pad_digital_from_tristate_foundry_vertical_input_array_reset_in = reset_Ext; + assign pad_digital_from_tristate_foundry_horizontal_input_array_io_a_in = io_a_Ext; + assign pad_digital_from_tristate_foundry_horizontal_input_array_io_b_in = io_b_Ext; + assign pad_digital_from_tristate_foundry_horizontal_input_array_io_c_in = $unsigned(io_c_Ext); + assign pad_digital_from_tristate_foundry_horizontal_output_array_io_x_in = io_x_Int; + assign pad_digital_from_tristate_foundry_vertical_output_array_io_z_in = $unsigned(io_z_Int); + assign pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0_in = io_v_0_Int; + assign pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1_in = io_v_1_Int; + assign pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2_in = io_v_2_Int; +endmodule +module ExampleTopModuleWithBB( + input clock, + input reset, + input [14:0] io_a, + input [14:0] io_b, + input [13:0] io_c, + output [15:0] io_x, + output [15:0] io_y, + output [15:0] io_z, + inout [2:0] io_analog1, + inout [2:0] io_analog2, + output [4:0] io_v_0, + output [4:0] io_v_1, + output [4:0] io_v_2 +); + wire ExampleTopModuleWithBB_PadFrame_clock_Int; + wire ExampleTopModuleWithBB_PadFrame_reset_Int; + wire [14:0] ExampleTopModuleWithBB_PadFrame_io_a_Int; + wire [14:0] ExampleTopModuleWithBB_PadFrame_io_b_Int; + wire [13:0] ExampleTopModuleWithBB_PadFrame_io_c_Int; + wire [15:0] ExampleTopModuleWithBB_PadFrame_io_x_Int; + wire [15:0] ExampleTopModuleWithBB_PadFrame_io_y_Int; + wire [15:0] ExampleTopModuleWithBB_PadFrame_io_z_Int; + wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_0_Int; + wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_1_Int; + wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_2_Int; + wire ExampleTopModuleWithBB_PadFrame_clock_Ext; + wire ExampleTopModuleWithBB_PadFrame_reset_Ext; + wire [14:0] ExampleTopModuleWithBB_PadFrame_io_a_Ext; + wire [14:0] ExampleTopModuleWithBB_PadFrame_io_b_Ext; + wire [13:0] ExampleTopModuleWithBB_PadFrame_io_c_Ext; + wire [15:0] ExampleTopModuleWithBB_PadFrame_io_x_Ext; + wire [15:0] ExampleTopModuleWithBB_PadFrame_io_y_Ext; + wire [15:0] ExampleTopModuleWithBB_PadFrame_io_z_Ext; + wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_0_Ext; + wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_1_Ext; + wire [4:0] ExampleTopModuleWithBB_PadFrame_io_v_2_Ext; + wire ExampleTopModuleWithBB_Internal_clock; + wire ExampleTopModuleWithBB_Internal_reset; + wire [14:0] ExampleTopModuleWithBB_Internal_io_a; + wire [14:0] ExampleTopModuleWithBB_Internal_io_b; + wire [13:0] ExampleTopModuleWithBB_Internal_io_c; + wire [15:0] ExampleTopModuleWithBB_Internal_io_x; + wire [15:0] ExampleTopModuleWithBB_Internal_io_y; + wire [15:0] ExampleTopModuleWithBB_Internal_io_z; + wire [4:0] ExampleTopModuleWithBB_Internal_io_v_0; + wire [4:0] ExampleTopModuleWithBB_Internal_io_v_1; + wire [4:0] ExampleTopModuleWithBB_Internal_io_v_2; + ExampleTopModuleWithBB_PadFrame ExampleTopModuleWithBB_PadFrame ( + .clock_Int(ExampleTopModuleWithBB_PadFrame_clock_Int), + .reset_Int(ExampleTopModuleWithBB_PadFrame_reset_Int), + .io_a_Int(ExampleTopModuleWithBB_PadFrame_io_a_Int), + .io_b_Int(ExampleTopModuleWithBB_PadFrame_io_b_Int), + .io_c_Int(ExampleTopModuleWithBB_PadFrame_io_c_Int), + .io_x_Int(ExampleTopModuleWithBB_PadFrame_io_x_Int), + .io_y_Int(ExampleTopModuleWithBB_PadFrame_io_y_Int), + .io_z_Int(ExampleTopModuleWithBB_PadFrame_io_z_Int), + .io_v_0_Int(ExampleTopModuleWithBB_PadFrame_io_v_0_Int), + .io_v_1_Int(ExampleTopModuleWithBB_PadFrame_io_v_1_Int), + .io_v_2_Int(ExampleTopModuleWithBB_PadFrame_io_v_2_Int), + .clock_Ext(ExampleTopModuleWithBB_PadFrame_clock_Ext), + .reset_Ext(ExampleTopModuleWithBB_PadFrame_reset_Ext), + .io_a_Ext(ExampleTopModuleWithBB_PadFrame_io_a_Ext), + .io_b_Ext(ExampleTopModuleWithBB_PadFrame_io_b_Ext), + .io_c_Ext(ExampleTopModuleWithBB_PadFrame_io_c_Ext), + .io_x_Ext(ExampleTopModuleWithBB_PadFrame_io_x_Ext), + .io_y_Ext(ExampleTopModuleWithBB_PadFrame_io_y_Ext), + .io_z_Ext(ExampleTopModuleWithBB_PadFrame_io_z_Ext), + .io_analog1_Ext(io_analog1), + .io_analog2_Ext(io_analog2), + .io_v_0_Ext(ExampleTopModuleWithBB_PadFrame_io_v_0_Ext), + .io_v_1_Ext(ExampleTopModuleWithBB_PadFrame_io_v_1_Ext), + .io_v_2_Ext(ExampleTopModuleWithBB_PadFrame_io_v_2_Ext) + ); + ExampleTopModuleWithBB_Internal ExampleTopModuleWithBB_Internal ( + .clock(ExampleTopModuleWithBB_Internal_clock), + .reset(ExampleTopModuleWithBB_Internal_reset), + .io_a(ExampleTopModuleWithBB_Internal_io_a), + .io_b(ExampleTopModuleWithBB_Internal_io_b), + .io_c(ExampleTopModuleWithBB_Internal_io_c), + .io_x(ExampleTopModuleWithBB_Internal_io_x), + .io_y(ExampleTopModuleWithBB_Internal_io_y), + .io_z(ExampleTopModuleWithBB_Internal_io_z), + .io_analog1(io_analog1), + .io_analog2(io_analog2), + .io_v_0(ExampleTopModuleWithBB_Internal_io_v_0), + .io_v_1(ExampleTopModuleWithBB_Internal_io_v_1), + .io_v_2(ExampleTopModuleWithBB_Internal_io_v_2) + ); + assign io_x = ExampleTopModuleWithBB_PadFrame_io_x_Ext; + assign io_y = ExampleTopModuleWithBB_PadFrame_io_y_Ext; + assign io_z = ExampleTopModuleWithBB_PadFrame_io_z_Ext; + assign io_v_0 = ExampleTopModuleWithBB_PadFrame_io_v_0_Ext; + assign io_v_1 = ExampleTopModuleWithBB_PadFrame_io_v_1_Ext; + assign io_v_2 = ExampleTopModuleWithBB_PadFrame_io_v_2_Ext; + assign ExampleTopModuleWithBB_PadFrame_io_x_Int = ExampleTopModuleWithBB_Internal_io_x; + assign ExampleTopModuleWithBB_PadFrame_io_y_Int = ExampleTopModuleWithBB_Internal_io_y; + assign ExampleTopModuleWithBB_PadFrame_io_z_Int = ExampleTopModuleWithBB_Internal_io_z; + assign ExampleTopModuleWithBB_PadFrame_io_v_0_Int = ExampleTopModuleWithBB_Internal_io_v_0; + assign ExampleTopModuleWithBB_PadFrame_io_v_1_Int = ExampleTopModuleWithBB_Internal_io_v_1; + assign ExampleTopModuleWithBB_PadFrame_io_v_2_Int = ExampleTopModuleWithBB_Internal_io_v_2; + assign ExampleTopModuleWithBB_PadFrame_clock_Ext = clock; + assign ExampleTopModuleWithBB_PadFrame_reset_Ext = reset; + assign ExampleTopModuleWithBB_PadFrame_io_a_Ext = io_a; + assign ExampleTopModuleWithBB_PadFrame_io_b_Ext = io_b; + assign ExampleTopModuleWithBB_PadFrame_io_c_Ext = io_c; + assign ExampleTopModuleWithBB_Internal_clock = ExampleTopModuleWithBB_PadFrame_clock_Int; + assign ExampleTopModuleWithBB_Internal_reset = ExampleTopModuleWithBB_PadFrame_reset_Int; + assign ExampleTopModuleWithBB_Internal_io_a = ExampleTopModuleWithBB_PadFrame_io_a_Int; + assign ExampleTopModuleWithBB_Internal_io_b = ExampleTopModuleWithBB_PadFrame_io_b_Int; + assign ExampleTopModuleWithBB_Internal_io_c = ExampleTopModuleWithBB_PadFrame_io_c_Int; +endmodule \ No newline at end of file diff --git a/tapeout/src/test/resources/PadPlacement.io b/tapeout/src/test/resources/PadPlacement.io new file mode 100644 index 00000000..435ce274 --- /dev/null +++ b/tapeout/src/test/resources/PadPlacement.io @@ -0,0 +1,236 @@ +(globals + version = 3 + io_order = default +) +(iopad + (bottomleft + (inst name="corner_ll" cell="CORNER_EXAMPLE" ) + ) + (bottomright + (inst name="corner_lr" orientation=MY cell="CORNER_EXAMPLE" ) + ) + (topleft + (inst name="corner_ul" orientation=MX cell="CORNER_EXAMPLE" ) + ) + (topright + (inst name="corner_ur" cell="CORNER_EXAMPLE" ) + ) + (left + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_supply_vdd_horizontal_left_0/PAD[0]") # Side: 1, Order: 0 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_supply_vdd_horizontal_left_1/PAD[0]") # Side: 1, Order: 1 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_supply_vdd_horizontal_left_2/PAD[0]") # Side: 1, Order: 2 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[0]/PAD") # Side: 1, Order: 3 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[1]/PAD") # Side: 1, Order: 4 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[2]/PAD") # Side: 1, Order: 5 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[3]/PAD") # Side: 1, Order: 6 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[4]/PAD") # Side: 1, Order: 7 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[5]/PAD") # Side: 1, Order: 8 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[6]/PAD") # Side: 1, Order: 9 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[7]/PAD") # Side: 1, Order: 10 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[8]/PAD") # Side: 1, Order: 11 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[9]/PAD") # Side: 1, Order: 12 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[10]/PAD") # Side: 1, Order: 13 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[11]/PAD") # Side: 1, Order: 14 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[12]/PAD") # Side: 1, Order: 15 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[13]/PAD") # Side: 1, Order: 16 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_a/pad_digital_from_tristate_foundry_horizontal_input[14]/PAD") # Side: 1, Order: 17 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[0]/PAD") # Side: 1, Order: 18 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[1]/PAD") # Side: 1, Order: 19 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[2]/PAD") # Side: 1, Order: 20 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[3]/PAD") # Side: 1, Order: 21 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[4]/PAD") # Side: 1, Order: 22 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[5]/PAD") # Side: 1, Order: 23 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[6]/PAD") # Side: 1, Order: 24 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[7]/PAD") # Side: 1, Order: 25 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[8]/PAD") # Side: 1, Order: 26 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[9]/PAD") # Side: 1, Order: 27 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[10]/PAD") # Side: 1, Order: 28 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[11]/PAD") # Side: 1, Order: 29 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[12]/PAD") # Side: 1, Order: 30 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[13]/PAD") # Side: 1, Order: 31 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_b/pad_digital_from_tristate_foundry_horizontal_input[14]/PAD") # Side: 1, Order: 32 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[0]/PAD") # Side: 1, Order: 33 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[1]/PAD") # Side: 1, Order: 34 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[2]/PAD") # Side: 1, Order: 35 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[3]/PAD") # Side: 1, Order: 36 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[4]/PAD") # Side: 1, Order: 37 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[5]/PAD") # Side: 1, Order: 38 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[6]/PAD") # Side: 1, Order: 39 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[7]/PAD") # Side: 1, Order: 40 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[8]/PAD") # Side: 1, Order: 41 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[9]/PAD") # Side: 1, Order: 42 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[10]/PAD") # Side: 1, Order: 43 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[11]/PAD") # Side: 1, Order: 44 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[12]/PAD") # Side: 1, Order: 45 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_input_array_io_c/pad_digital_from_tristate_foundry_horizontal_input[13]/PAD") # Side: 1, Order: 46 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[0]/PAD") # Side: 1, Order: 47 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[1]/PAD") # Side: 1, Order: 48 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[2]/PAD") # Side: 1, Order: 49 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[3]/PAD") # Side: 1, Order: 50 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[4]/PAD") # Side: 1, Order: 51 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[5]/PAD") # Side: 1, Order: 52 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[6]/PAD") # Side: 1, Order: 53 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[7]/PAD") # Side: 1, Order: 54 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[8]/PAD") # Side: 1, Order: 55 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[9]/PAD") # Side: 1, Order: 56 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[10]/PAD") # Side: 1, Order: 57 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[11]/PAD") # Side: 1, Order: 58 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[12]/PAD") # Side: 1, Order: 59 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[13]/PAD") # Side: 1, Order: 60 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[14]/PAD") # Side: 1, Order: 61 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_x/pad_digital_from_tristate_foundry_horizontal_output[15]/PAD") # Side: 1, Order: 62 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_analog_fast_custom_horizontal_array_io_analog1/pad_analog_fast_custom_horizontal[0]/PAD") # Side: 1, Order: 63 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_analog_fast_custom_horizontal_array_io_analog1/pad_analog_fast_custom_horizontal[1]/PAD") # Side: 1, Order: 64 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_analog_fast_custom_horizontal_array_io_analog1/pad_analog_fast_custom_horizontal[2]/PAD") # Side: 1, Order: 65 + + ) + (right + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_supply_vss_horizontal_right_0/PAD[0]") # Side: 3, Order: 0 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_supply_vss_horizontal_right_0/PAD[1]") # Side: 3, Order: 1 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0/pad_digital_from_tristate_foundry_horizontal_output[0]/PAD") # Side: 3, Order: 2 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0/pad_digital_from_tristate_foundry_horizontal_output[1]/PAD") # Side: 3, Order: 3 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0/pad_digital_from_tristate_foundry_horizontal_output[2]/PAD") # Side: 3, Order: 4 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0/pad_digital_from_tristate_foundry_horizontal_output[3]/PAD") # Side: 3, Order: 5 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_0/pad_digital_from_tristate_foundry_horizontal_output[4]/PAD") # Side: 3, Order: 6 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1/pad_digital_from_tristate_foundry_horizontal_output[0]/PAD") # Side: 3, Order: 7 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1/pad_digital_from_tristate_foundry_horizontal_output[1]/PAD") # Side: 3, Order: 8 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1/pad_digital_from_tristate_foundry_horizontal_output[2]/PAD") # Side: 3, Order: 9 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1/pad_digital_from_tristate_foundry_horizontal_output[3]/PAD") # Side: 3, Order: 10 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_1/pad_digital_from_tristate_foundry_horizontal_output[4]/PAD") # Side: 3, Order: 11 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2/pad_digital_from_tristate_foundry_horizontal_output[0]/PAD") # Side: 3, Order: 12 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2/pad_digital_from_tristate_foundry_horizontal_output[1]/PAD") # Side: 3, Order: 13 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2/pad_digital_from_tristate_foundry_horizontal_output[2]/PAD") # Side: 3, Order: 14 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2/pad_digital_from_tristate_foundry_horizontal_output[3]/PAD") # Side: 3, Order: 15 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_horizontal_output_array_io_v_2/pad_digital_from_tristate_foundry_horizontal_output[4]/PAD") # Side: 3, Order: 16 + + ) + (top + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_input_array_reset/pad_digital_from_tristate_foundry_vertical_input[0]/PAD") # Side: 2, Order: 0 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[0]/PAD") # Side: 2, Order: 1 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[1]/PAD") # Side: 2, Order: 2 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[2]/PAD") # Side: 2, Order: 3 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[3]/PAD") # Side: 2, Order: 4 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[4]/PAD") # Side: 2, Order: 5 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[5]/PAD") # Side: 2, Order: 6 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[6]/PAD") # Side: 2, Order: 7 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[7]/PAD") # Side: 2, Order: 8 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[8]/PAD") # Side: 2, Order: 9 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[9]/PAD") # Side: 2, Order: 10 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[10]/PAD") # Side: 2, Order: 11 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[11]/PAD") # Side: 2, Order: 12 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[12]/PAD") # Side: 2, Order: 13 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[13]/PAD") # Side: 2, Order: 14 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[14]/PAD") # Side: 2, Order: 15 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_digital_from_tristate_foundry_vertical_output_array_io_z/pad_digital_from_tristate_foundry_vertical_output[15]/PAD") # Side: 2, Order: 16 + + ) + (bottom + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_supply_vdd_vertical_bottom_0/PAD[0]") # Side: 4, Order: 0 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_supply_vdd_vertical_bottom_1/PAD[0]") # Side: 4, Order: 1 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_analog_slow_foundry_vertical_array_io_analog2/pad_analog_slow_foundry_vertical[0]/PAD") # Side: 4, Order: 2 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_analog_slow_foundry_vertical_array_io_analog2/pad_analog_slow_foundry_vertical[1]/PAD") # Side: 4, Order: 3 + + (inst name = "ExampleTopModuleWithBB_PadFrame/pad_analog_slow_foundry_vertical_array_io_analog2/pad_analog_slow_foundry_vertical[2]/PAD") # Side: 4, Order: 4 + + ) +) \ No newline at end of file diff --git a/tapeout/src/test/scala/transforms/clkgen/ClkGenSpec.scala b/tapeout/src/test/scala/transforms/clkgen/ClkGenSpec.scala new file mode 100644 index 00000000..17ae1c76 --- /dev/null +++ b/tapeout/src/test/scala/transforms/clkgen/ClkGenSpec.scala @@ -0,0 +1,181 @@ +// See LICENSE for license details. + +package barstools.tapeout.transforms.clkgen + +import chisel3._ +import firrtl._ +import org.scalatest.{FlatSpec, Matchers} +import chisel3.experimental._ +import chisel3.iotesters._ +import chisel3.util.HasBlackBoxInline +import barstools.tapeout.transforms.pads.TopModule + +// Purely to see that clk src tagging works with BBs +class FakeBBClk extends BlackBox with HasBlackBoxInline with IsClkModule { + val io = IO(new Bundle { + val inClk = Input(Clock()) + val outClk = Output(Vec(3, Clock())) + }) + + annotateClkPort(io.inClk, Sink()) + val generatedClks = io.outClk.map { case elt => + val id = getIOName(elt) + val srcId = getIOName(io.inClk) + annotateClkPort(elt.asInstanceOf[Element]) + GeneratedClk(id, Seq(srcId), Seq(0, 1, 2)) + }.toSeq + + annotateDerivedClks(ClkDiv, generatedClks) + + // Generates a "FakeBB.v" file with the following Verilog module + setInline("FakeBBClk.v", + s""" + |module FakeBBClk( + | input inClk, + | output outClk_0, + | output outClk_1, + | output outClk_2 + |); + | always @* begin + | outClk_0 = inClk; + | outClk_1 = inClk; + | outClk_2 = inClk; + | end + |endmodule + """.stripMargin) +} + +class ModWithNestedClkIO(numPhases: Int) extends Bundle { + val inClk = Input(Clock()) + val bbOutClk = Output(Vec(3, Clock())) + val clkDivOut = Output(Vec(numPhases, Clock())) +} + +class TestModWithNestedClkIO(numPhases: Int) extends Bundle { + val bbOutClk = Output(Vec(3, Bool())) + val clkDivOut = Output(Vec(numPhases, Bool())) +} + +class ModWithNestedClk(divBy: Int, phases: Seq[Int], syncReset: Boolean) extends Module { + + val io = IO(new ModWithNestedClkIO(phases.length)) + + val bb = Module(new FakeBBClk) + bb.io.inClk := io.inClk + io.bbOutClk := bb.io.outClk + val clkDiv = Module(new SEClkDivider(divBy, phases, syncReset = syncReset)) + clkDiv.io.reset := reset + clkDiv.io.inClk := io.inClk + phases.zipWithIndex.foreach { case (phase, idx) => io.clkDivOut(idx) := clkDiv.io.outClks(phase) } + +} + +class TopModuleWithClks(val divBy: Int, val phases: Seq[Int]) extends TopModule(usePads = false) { + val io = IO(new Bundle { + val gen1 = new TestModWithNestedClkIO(phases.length) + val gen2 = new TestModWithNestedClkIO(phases.length) + val gen3 = new TestModWithNestedClkIO(phases.length) + val fakeClk1 = Input(Clock()) + val fakeClk2 = Input(Clock()) + }) + + // TODO: Don't have to type Some + annotateClkPort(clock, + id = "clock", // not in io bundle + sink = Sink(Some(ClkSrc(period = 5.0, async = Seq(getIOName(io.fakeClk1))))) + ) + annotateClkPort(io.fakeClk1, Sink(Some(ClkSrc(period = 4.0)))) + annotateClkPort(io.fakeClk2, Sink(Some(ClkSrc(period = 3.0)))) + + // Most complicated: test chain of clock generators + val gen1 = Module(new ModWithNestedClk(divBy, phases, syncReset = true)) + io.gen1.bbOutClk := Vec(gen1.io.bbOutClk.map(x => x.asUInt)) + io.gen1.clkDivOut := Vec(gen1.io.clkDivOut.map(x => x.asUInt)) + gen1.io.inClk := clock + // ClkDiv on generated clk -> reset occurs before first input clk edge + val gen2 = Module(new ModWithNestedClk(divBy, phases, syncReset = false)) + io.gen2.bbOutClk := Vec(gen2.io.bbOutClk.map(x => x.asUInt)) + io.gen2.clkDivOut := Vec(gen2.io.clkDivOut.map(x => x.asUInt)) + gen2.io.inClk := gen1.io.clkDivOut.last + val gen3 = Module(new ModWithNestedClk(divBy, phases, syncReset = false)) + io.gen3.bbOutClk := Vec(gen3.io.bbOutClk.map(x => x.asUInt)) + io.gen3.clkDivOut := Vec(gen3.io.clkDivOut.map(x => x.asUInt)) + gen3.io.inClk := gen1.io.clkDivOut.last +} + +class TopModuleWithClksTester(c: TopModuleWithClks) extends PeekPokeTester(c) { + val maxT = c.divBy * c.divBy * 4 + val numSubClkOutputs = c.io.gen1.clkDivOut.length + val gen1Out = Seq.fill(numSubClkOutputs)(scala.collection.mutable.ArrayBuffer[Int]()) + val gen2Out = Seq.fill(numSubClkOutputs)(scala.collection.mutable.ArrayBuffer[Int]()) + val gen3Out = Seq.fill(numSubClkOutputs)(scala.collection.mutable.ArrayBuffer[Int]()) + reset(10) + for (t <- 0 until maxT) { + for (k <- 0 until numSubClkOutputs) { + gen1Out(k) += peek(c.io.gen1.clkDivOut(k)).intValue + gen2Out(k) += peek(c.io.gen2.clkDivOut(k)).intValue + gen3Out(k) += peek(c.io.gen3.clkDivOut(k)).intValue + } + step(1) + } + + val clkCounts = (0 until maxT) + val clkCountsModDiv = clkCounts.map(_ % c.divBy) + for (k <- 0 until numSubClkOutputs) { + val expected = clkCountsModDiv.map(x => if (x == c.phases(k)) 1 else 0) + expect(gen1Out(k) == expected, s"gen1Out($k) incorrect!") + println(s"gen1Out($k): \t${gen1Out(k).mkString("")}") + } + + val gen1ClkCounts = (0 until maxT/c.divBy).map(i => Seq.fill(c.divBy)(i)).flatten + val gen1ClkCountsModDiv = gen1ClkCounts.map(_ % c.divBy) + + for (k <- 0 until numSubClkOutputs) { + // Handle initial transient + val fillVal = if (c.phases.last == c.divBy - 1 && k == numSubClkOutputs - 1) 1 else 0 + val expected = Seq.fill(c.phases.last)(fillVal) ++ + gen1ClkCountsModDiv.map(x => if (x == c.phases(k)) 1 else 0).dropRight(c.phases.last) + expect(gen2Out(k) == expected, s"gen1Out($k) incorrect!") + println(s"gen2Out($k): \t${gen2Out(k).mkString("")}") + println(s"expected: \t${expected.mkString("")}") + } + + expect(gen2Out == gen3Out, "gen2Out should equal gen3Out") + +} + +class ClkGenSpec extends FlatSpec with Matchers { + + def readOutputFile(dir: String, f: String): String = + scala.io.Source.fromFile(Seq(dir, f).mkString("/")).getLines.mkString("\n") + def readResource(resource: String): String = { + val stream = getClass.getResourceAsStream(resource) + scala.io.Source.fromInputStream(stream).mkString + } + + def checkOutputs(dir: String) = { + } + + behavior of "top module with clk gens" + + it should "pass simple testbench" in { + val optionsManager = new TesterOptionsManager { + firrtlOptions = firrtlOptions.copy( + compilerName = "verilog" + /*annotations = List(passes.clocklist.ClockListAnnotation( + s"-c:TopModuleWithClks:-m:TopModuleWithClks:-o:test.clk" + )), + customTransforms = Seq(new passes.clocklist.ClockListTransform())*/ + ) + testerOptions = testerOptions.copy(isVerbose = false, backendName = "verilator", displayBase = 10) + commonOptions = commonOptions.copy(targetDirName = "test_run_dir/ClkTB") + } + // WARNING: TB requires that phase divBy - 1 should be at the end of the Seq to be OK during initial transient + iotesters.Driver.execute(() => new TopModuleWithClks(4, Seq(0, 1, 3)), optionsManager) { c => + val dir = optionsManager.commonOptions.targetDirName + checkOutputs(dir) + new TopModuleWithClksTester(c) + } should be (true) + } + +} \ No newline at end of file diff --git a/tapeout/src/test/scala/transforms/pads/AddIOPadsSpec.scala b/tapeout/src/test/scala/transforms/pads/AddIOPadsSpec.scala new file mode 100644 index 00000000..b578be97 --- /dev/null +++ b/tapeout/src/test/scala/transforms/pads/AddIOPadsSpec.scala @@ -0,0 +1,226 @@ +// See LICENSE for license details. + +package barstools.tapeout.transforms.pads + +import chisel3._ +import firrtl._ +import org.scalatest.{FlatSpec, Matchers} +import chisel3.experimental._ +import chisel3.util.HasBlackBoxInline +import chisel3.iotesters._ + +class BB extends BlackBox with HasBlackBoxInline { + val io = IO(new Bundle { + val c = Input(SInt(14.W)) + val z = Output(SInt(16.W)) + val analog1 = Analog(3.W) + val analog2 = analog1.chiselCloneType + }) + // Generates a "FakeBB.v" file with the following Verilog module + setInline("FakeBB.v", + s""" + |module BB( + | input [15:0] c, + | output [15:0] z, + | inout [2:0] analog1, + | inout [2:0] analog2 + |); + | always @* begin + | z = 2 * c; + | analog2 = analog1 + 1; + | end + |endmodule + """.stripMargin) +} + +// If no template file is provided, it'll use the default one (example) in the resource folder +// Default pad side is Top if no side is specified for a given IO +// You can designate the number of different supply pads on each chip side +class ExampleTopModuleWithBB extends TopModule( + supplyAnnos = Seq( + SupplyAnnotation(padName = "vdd", leftSide = 3, bottomSide = 2), + SupplyAnnotation(padName = "vss", rightSide = 1) + )) { + val io = IO(new Bundle { + val a = Input(UInt(15.W)) + val b = a.chiselCloneType + val c = Input(SInt(14.W)) + val x = Output(UInt(16.W)) + val y = x.chiselCloneType + val z = Output(SInt(16.W)) + val analog1 = Analog(3.W) + val analog2 = analog1.chiselCloneType + val v = Output(Vec(3, UInt(5.W))) + }) + + // Can annotate aggregates with pad side location + pad name (should be a name in the yaml template) + annotatePad(io.v, Right, "from_tristate_foundry") + // Can annotate individual elements + annotatePad(io.analog1, Left, "fast_custom") + annotatePad(io.analog2, Bottom, "slow_foundry") + // Looks for a pad that matches the IO type (digital in, digital out, analog) if no name is specified + Seq(io.a, io.b, io.c, io.x) foreach { x => annotatePad(x, Left) } + // Some signals might not want pads associated with them + noPad(io.y) + // Clk might come directly from bump + noPad(clock) + + val bb = Module(new BB()) + bb.io.c := io.c + io.z := bb.io.z + bb.io.analog1 <> io.analog1 + bb.io.analog2 <> io.analog2 + + io.x := io.a + 1.U + io.y := io.b - 1.U + + io.v foreach { lhs => lhs := io.a } + +} + +class SimpleTopModuleTester(c: ExampleTopModuleWithBB) extends PeekPokeTester(c) { + val ax = Seq(5, 3) + val bx = Seq(8, 2) + val cx = Seq(-11, -9) + for (i <- 0 until ax.length) { + poke(c.io.a, ax(i)) + poke(c.io.b, bx(i)) + poke(c.io.c, cx(i)) + expect(c.io.x, ax(i) + 1) + expect(c.io.y, bx(i) - 1) + expect(c.io.z, 2 * cx(i)) + c.io.v foreach { out => expect(out, ax(i)) } + } + // Analog can't be peeked + poked +} + +// Notes: Annotations +// a in 15: left, default digital +// b in 15: left, default digital +// c in 14: left, default digital ; signed +// x out 16: left, default digital +// y out: NOPAD +// clk in: NOPAD +// analog1 3: left, fast_custom +// analog2 3: bottom, slow_foundry +// v (vec of 3 with 5, out): right, from_tristate_foundry +// reset in: UNSPECIFIED: top, default digital +// z out 16: UNSPECIFIED: top, default digital ; signed +// vdd, left: 3, group of 1 +// vdd, bottom: 2, group of 1 +// vss, right: 1, group of 2 +// Notes: Used pads +// digital horizontal (from_tristate_foundry) +// in + out +// analog fast_custom horizontal +// analog slow_foundry vertical +// digital vertical (from_tristate_foundry) +// in + out +// vdd horizontal +// vdd vertical +// vss horizontal + +class IOPadSpec extends FlatSpec with Matchers { + + def readOutputFile(dir: String, f: String): String = + scala.io.Source.fromFile(Seq(dir, f).mkString("/")).getLines.mkString("\n") + def readResource(resource: String): String = { + val stream = getClass.getResourceAsStream(resource) + scala.io.Source.fromInputStream(stream).mkString + } + + def checkOutputs(dir: String) = { + // Show that black box source helper is run + //readOutputFile(dir, "black_box_verilog_files.f") should include ("pad_supply_vdd_horizontal.v") + + val padBBEx = s"""// Digital Pad Example + |// Signal Direction: Input + |// Pad Orientation: Horizontal + |// Call your instance PAD + |module pad_digital_from_tristate_foundry_horizontal_input( + | input in, + | output reg out + |); + | // Where you would normally dump your pad instance + | always @* begin + | out = in; + | end + |endmodule + | + |module pad_digital_from_tristate_foundry_horizontal_input_array #( + | parameter int WIDTH=1 + |)( + | input [WIDTH-1:0] in, + | output reg [WIDTH-1:0] out + |); + | pad_digital_from_tristate_foundry_horizontal_input pad_digital_from_tristate_foundry_horizontal_input[WIDTH-1:0]( + | .in(in), + | .out(out) + | );""".stripMargin + // Make sure black box templating is OK + readOutputFile(dir, "pad_digital_from_tristate_foundry_horizontal_input_array.v") should include (padBBEx) + + val verilog = readOutputFile(dir, "ExampleTopModuleWithBB.v") + // Pad frame + top should be exact + verilog should include (readResource("/PadAnnotationVerilogPart.v")) + // Pad Placement IO file should be exact + val padIO = readOutputFile(dir, "pads.io") + padIO should include(readResource("/PadPlacement.io")) + } + + behavior of "top module with blackbox" + + import barstools.tapeout.transforms._ + + it should "pass simple testbench" in { + val optionsManager = new TesterOptionsManager { + firrtlOptions = firrtlOptions.copy( + compilerName = "verilog" + // annotations = List(TechnologyLocation("./RealTech")) + ) + testerOptions = testerOptions.copy(isVerbose = true, backendName = "verilator", displayBase = 10) + commonOptions = commonOptions.copy(targetDirName = "test_run_dir/PadsTB") + } + iotesters.Driver.execute(() => new ExampleTopModuleWithBB, optionsManager) { c => + val dir = optionsManager.commonOptions.targetDirName + checkOutputs(dir) + new SimpleTopModuleTester(c) + } should be (true) + } +/* + it should "create proper IO pads + black box in low firrtl" in { + val optionsManager = new ExecutionOptionsManager("barstools") with HasChiselExecutionOptions with HasFirrtlOptions { + firrtlOptions = firrtlOptions.copy(compilerName = "low") + commonOptions = commonOptions.copy(targetDirName = "test_run_dir/LoFirrtl") + //commonOptions = commonOptions.copy(globalLogLevel = logger.LogLevel.Info) + } + val success = chisel3.Driver.execute(optionsManager, () => new ExampleTopModuleWithBB) match { + case ChiselExecutionSuccess(_, chirrtl, Some(FirrtlExecutionSuccess(_, firrtl))) => + firrtl should include ("ExampleTopModuleWithBB_PadFrame") + firrtl should include ("ExampleTopModuleWithBB_Internal") + firrtl should not include ("FakeBBPlaceholder") + true + case _ => false + } + success should be (true) + } +*/ + it should "create proper IO pads + black box in verilog" in { + val optionsManager = new ExecutionOptionsManager("barstools") with HasChiselExecutionOptions with HasFirrtlOptions { + firrtlOptions = firrtlOptions.copy( + compilerName = "verilog" + ) + commonOptions = commonOptions.copy(targetDirName = "test_run_dir/PadsVerilog") + //commonOptions = commonOptions.copy(globalLogLevel = logger.LogLevel.Info) + } + val success = chisel3.Driver.execute(optionsManager, () => new ExampleTopModuleWithBB) match { + case ChiselExecutionSuccess(_, chirrtl, Some(FirrtlExecutionSuccess(_, verilog))) => + true + case _ => false + } + success should be (true) + val dir = optionsManager.commonOptions.targetDirName + checkOutputs(dir) + } + +} \ No newline at end of file