Add Pads + other utilities (#7)
[stevo]: adds a bunch of pad frame commits, as well as beginning work on clocking annotations and constraints * start add io pads pass * save progress adding yaml pad info * saving some semi-presentable work -- parses yaml for pad templates and associates templates with ports * added black boxes to the module; still need to hook up * added supply pad yaml example; added option to not include pad for an IO, blackboxed that cat + bit extraction functions * rewrite createbbs and some other parts of the transform * finally got blackboxhelper to work -- seems there was a typo in the firrtl pass (?) have not connected them up properly in the padframe * finished first version of pad transform; need to add bells and whistles + special case stuff * made a bunch of changes in firrtl to shorthand things * done with padframe for signals * started major refactoring; first of pad yaml stuff * forgot to update verilogTemplate -> verilog * rename ParsePadYaml -> ChipPadsYaml; moved some stuff * separated out stuff that describes pads i.e. direction, type, side * forgot to update import for yamlhelpers * trying to make the process of creating annotations more structured * saving annotation helpers but prob better to switch to yaml * saving changes -- reworking annotations * fixing some bugs; properly annotated ports with pads * annotate supply pads * lesson (re)learned. cleaned up constants * finished adding supply pads to pad frame; still need to generate io file * also committing updated transform; still without io file * big typo was causing pad verilog files not to be generated * verilator passes with transform; had to fix verilog bb typo * added unused pads; added more thorough tests + did visual inspection of output; made some port types more explicit * renamed files/classes to be clearer * started creating pad io template * update spec so that transform order matters * get rid of logger * went around in circles with blackboxhelper + way to annotate * finished adding + testing pad.io creation * starting clkgen pass -- made model for asynchronously reset clk divider + wrappers for programmatic bundling * temporarily locating albert's utility functions here * saving work on clk constraints * redid input config passing -- pass in tech directory instead; seems like getting clk sink, src, and relationship works * not done; need to pause to do tapeout-y things. the clk gen pass gets all the clks and their sources, but i need to build a proper graph to handle clks coming out of muxes
This commit is contained in:
181
tapeout/src/test/scala/transforms/clkgen/ClkGenSpec.scala
Normal file
181
tapeout/src/test/scala/transforms/clkgen/ClkGenSpec.scala
Normal file
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
226
tapeout/src/test/scala/transforms/pads/AddIOPadsSpec.scala
Normal file
226
tapeout/src/test/scala/transforms/pads/AddIOPadsSpec.scala
Normal file
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user