summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSchuyler Eldridge2018-08-07 16:54:34 -0400
committerJack Koenig2018-08-07 13:54:34 -0700
commitb3bd3c430d39e344dc6224717efeca26c9c91378 (patch)
tree31154b186066dca43237bde038db4c2b188b0f88
parent4de6848ef746ca40945dc95a113e820bc7265cea (diff)
BoringUtils / Synthesizable Cross Module References (#718)
This adds an annotator that provides a linkage to the FIRRTL WiringTransform. This enables synthesizable cross module references between one source and multiple sinks without changing IO (the WiringTransform bores through the hierarchy). Per WiringTransform, this will connect sources to their closest sinks (as determined by BFS) or fail if ownership is indeterminate. Make TesterDriver.execute work like Driver.execute: - annotations are included when running FIRRTL - custom transforms are run automatically Also, add a bore method to BoringUtils that allows you to do one source to multi-sink mapping in a single call. This adds a test that this is doing the same thing as the equivalent call via disjoint addSink/addSource. Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
-rw-r--r--src/main/scala/chisel3/testers/TesterDriver.scala18
-rw-r--r--src/main/scala/chisel3/util/Decoupled.scala4
-rw-r--r--src/main/scala/chisel3/util/experimental/BoringUtils.scala198
-rw-r--r--src/test/scala/chiselTests/BoringUtilsSpec.scala83
4 files changed, 299 insertions, 4 deletions
diff --git a/src/main/scala/chisel3/testers/TesterDriver.scala b/src/main/scala/chisel3/testers/TesterDriver.scala
index fc71f2b0..e4bdda0b 100644
--- a/src/main/scala/chisel3/testers/TesterDriver.scala
+++ b/src/main/scala/chisel3/testers/TesterDriver.scala
@@ -5,6 +5,7 @@ package chisel3.testers
import chisel3._
import java.io._
+import chisel3.experimental.RunFirrtlTransform
import firrtl.{Driver => _, _}
object TesterDriver extends BackendCompilationUtilities {
@@ -25,6 +26,7 @@ object TesterDriver extends BackendCompilationUtilities {
// For now, dump the IR out to a file
Driver.dumpFirrtl(circuit, Some(new File(fname.toString + ".fir")))
+ val firrtlCircuit = Driver.toFirrtl(circuit)
// Copy CPP harness and other Verilog sources from resources into files
val cppHarness = new File(path, "top.cpp")
@@ -37,9 +39,21 @@ object TesterDriver extends BackendCompilationUtilities {
})
// Compile firrtl
- if (!compileFirrtlToVerilog(target, path)) {
- return false
+ val transforms = circuit.annotations.collect { case anno: RunFirrtlTransform => anno.transformClass }.distinct
+ .filterNot(_ == classOf[Transform])
+ .map { transformClass: Class[_ <: Transform] => transformClass.newInstance() }
+ val annotations = circuit.annotations.map(_.toFirrtl).toList
+ val optionsManager = new ExecutionOptionsManager("chisel3") with HasChiselExecutionOptions with HasFirrtlOptions {
+ commonOptions = CommonOptions(topName = target, targetDirName = path.getAbsolutePath)
+ firrtlOptions = FirrtlExecutionOptions(compilerName = "verilog", annotations = annotations,
+ customTransforms = transforms,
+ firrtlCircuit = Some(firrtlCircuit))
}
+ firrtl.Driver.execute(optionsManager) match {
+ case _: FirrtlExecutionFailure => return false
+ case _ =>
+ }
+
// Use sys.Process to invoke a bunch of backend stuff, then run the resulting exe
if ((verilogToCpp(target, path, additionalVFiles, cppHarness) #&&
cppToExe(target, path)).! == 0) {
diff --git a/src/main/scala/chisel3/util/Decoupled.scala b/src/main/scala/chisel3/util/Decoupled.scala
index 89ad3115..b90e87ac 100644
--- a/src/main/scala/chisel3/util/Decoupled.scala
+++ b/src/main/scala/chisel3/util/Decoupled.scala
@@ -6,7 +6,7 @@
package chisel3.util
import chisel3._
-import chisel3.experimental.{DataMirror, Direction}
+import chisel3.experimental.{DataMirror, Direction, requireIsChiselType}
import chisel3.internal.naming._ // can't use chisel3_ version because of compile order
/** An I/O Bundle containing 'valid' and 'ready' signals that handshake
@@ -199,7 +199,7 @@ class Queue[T <: Data](gen: T,
}
val genType = if (compileOptions.declaredTypeMustBeUnbound) {
- experimental.requireIsChiselType(gen)
+ requireIsChiselType(gen)
gen
} else {
if (DataMirror.internal.isSynthesizable(gen)) {
diff --git a/src/main/scala/chisel3/util/experimental/BoringUtils.scala b/src/main/scala/chisel3/util/experimental/BoringUtils.scala
new file mode 100644
index 00000000..066e924f
--- /dev/null
+++ b/src/main/scala/chisel3/util/experimental/BoringUtils.scala
@@ -0,0 +1,198 @@
+// See LICENSE for license details.
+
+package chisel3.util.experimental
+
+import chisel3._
+import chisel3.experimental.{ChiselAnnotation, RunFirrtlTransform, annotate}
+import chisel3.internal.{InstanceId, NamedComponent}
+import firrtl.transforms.{DontTouchAnnotation, NoDedupAnnotation}
+import firrtl.passes.wiring.{WiringTransform, SourceAnnotation, SinkAnnotation}
+import firrtl.annotations.{ModuleName, ComponentName}
+
+import scala.concurrent.SyncVar
+import chisel3.internal.Namespace
+
+/** An exception related to BoringUtils
+ * @param message the exception message
+ */
+class BoringUtilsException(message: String) extends Exception(message)
+
+/** Utilities for generating synthesizeable cross module references that
+ * "bore" through the hieararchy. The underlying cross module connects
+ * are handled by FIRRTL's Wiring Transform
+ * ([[firrtl.passes.wiring.WiringTransform]]).
+ *
+ * Consider the following exmple where you want to connect a component in
+ * one module to a component in another. Module `Constant` has a wire
+ * tied to `42` and `Expect` will assert unless connected to `42`:
+ * {{{
+ * class Constant extends Module {
+ * val io = IO(new Bundle{})
+ * val x = Wire(UInt(6.W))
+ * x := 42.U
+ * }
+ * class Expect extends Module {
+ * val io = IO(new Bundle{})
+ * val y = Wire(UInt(6.W))
+ * y := 0.U
+ * // This assertion will fail unless we bore!
+ * chisel3.assert(y === 42.U, "y should be 42 in module Expect")
+ * }
+ * }}}
+ *
+ * We can then connect `x` to `y` using [[BoringUtils]] without
+ * modifiying the Chisel IO of `Constant`, `Expect`, or modules that may
+ * instantiate them. There are two approaches to do this:
+ *
+ * 1. Hierarchical boring using [[BoringUtils.bore]]
+ *
+ * 2. Non-hierarchical boring using [[BoringUtils.addSink]]/[[BoringUtils.addSource]]
+ *
+ * ===Hierarchical Boring===
+ *
+ * Hierarchcical boring involves connecting one sink instance to another
+ * source instance in a parent module. Below, module `Top` contains an
+ * instance of `Cosntant` and `Expect`. Using [[BoringUtils.bore]], we
+ * can connect `constant.x` to `expect.y`.
+ *
+ * {{{
+ * class Top extends Module {
+ * val io = IO(new Bundle{})
+ * val constant = Module(new Constant)
+ * val expect = Module(new Expect)
+ * BoringUtils.bore(constant.x, Seq(expect.y))
+ * }
+ * }}}
+ *
+ * ===Non-hierarchical Boring===
+ *
+ * Non-hierarchical boring involves connections from sources to sinks
+ * that cannot see each other. Here, `x` is described as a source and
+ * given a name, `uniqueId`, and `y` is described as a sink with the same
+ * name. This is equivalent to the hierarchical boring example above, but
+ * requires no modifications to `Top`.
+ *
+ * {{{
+ * class Constant extends Module {
+ * val io = IO(new Bundle{})
+ * val x = Wire(UInt(6.W))
+ * x := 42.U
+ * BoringUtils.addSource(x, "uniqueId")
+ * }
+ * class Expect extends Module {
+ * val io = IO(new Bundle{})
+ * val y = Wire(UInt(6.W))
+ * y := 0.U
+ * // This assertion will fail unless we bore!
+ * chisel3.assert(y === 42.U, "y should be 42 in module Expect")
+ * BoringUtils.addSink(y, "uniqueId")
+ * }
+ * class Top extends Module {
+ * val io = IO(new Bundle{})
+ * val constant = Module(new Constant)
+ * val expect = Module(new Expect)
+ * }
+ * }}}
+ *
+ * ==Comments==
+ *
+ * Both hierarchical and non-hierarchical boring emit FIRRTL annotations
+ * that describe sources and sinks. These are matched by a `name` key
+ * that indicates they should be wired together. Hierarhical boring
+ * safely generates this name automatically. Non-hierarchical boring
+ * unsafely relies on user input to generate this name. Use of
+ * non-hierarchical naming may result in naming conflicts that the user
+ * must handle.
+ *
+ * The automatic generation of hierarchical names relies on a global,
+ * mutable namespace. This is currently persistent across circuit
+ * elaborations.
+ */
+object BoringUtils {
+ /* A global namespace for boring ids */
+ private val namespace: SyncVar[Namespace] = new SyncVar
+ namespace.put(Namespace.empty)
+
+ /* Get a new name (value) from the namespace */
+ private def newName(value: String): String = {
+ val ns = namespace.take()
+ val valuex = ns.name(value)
+ namespace.put(ns)
+ valuex
+ }
+
+ /* True if the requested name (value) exists in the namespace */
+ private def checkName(value: String): Boolean = namespace.get.contains(value)
+
+ /** Add a named source cross module reference
+ *
+ * @param component source circuit component
+ * @param name unique identifier for this source
+ * @param dedup enable dedupblication of modules (necessary if other
+ * instances exist in the design that are not sources)
+ * @param uniqueName if true, this will use a non-conflicting name from
+ * the global namespace
+ * @return the name used
+ * @note if a uniqueName is not specified, the returned name may differ
+ * from the user-provided name
+ */
+ def addSource(component: NamedComponent, name: String, dedup: Boolean = false, uniqueName: Boolean = false): String = {
+ val id = if (uniqueName) { newName(name) } else { name }
+ val maybeDedup =
+ if (dedup) { Seq(new ChiselAnnotation { def toFirrtl = NoDedupAnnotation(component.toNamed.module) }) }
+ else { Seq[ChiselAnnotation]() }
+ val annotations =
+ Seq(new ChiselAnnotation with RunFirrtlTransform {
+ def toFirrtl = SourceAnnotation(component.toNamed, id)
+ def transformClass = classOf[WiringTransform] },
+ new ChiselAnnotation { def toFirrtl = DontTouchAnnotation(component.toNamed) } ) ++ maybeDedup
+
+ annotations.map(annotate(_))
+ id
+ }
+
+ /** Add a named sink cross module reference. Multiple sinks may map to
+ * the same source.
+ *
+ * @param component sink circuit component
+ * @param name unique identifier for this sink that must resolve to
+ * @param dedup enable deduplication on sink component (necessary if
+ * other instances exist in the design that are not sinks) a source
+ * identifier
+ * @param forceExists if true, require that the provided `name` paramater
+ * already exists in the global namespace
+ * @throws BoringUtilsException if name is expected to exist and it doesn't
+ */
+ def addSink(component: InstanceId, name: String, dedup: Boolean = false, forceExists: Boolean = false): Unit = {
+ if (forceExists && !checkName(name)) {
+ throw new BoringUtilsException(s"Sink ID '$name' not found in BoringUtils ID namespace") }
+ def moduleName = component.toNamed match {
+ case c: ModuleName => c
+ case c: ComponentName => c.module
+ case _ => throw new ChiselException("Can only add a Module or Component sink", null)
+ }
+ val maybeDedup =
+ if (dedup) { Seq(new ChiselAnnotation { def toFirrtl = NoDedupAnnotation(moduleName) }) }
+ else { Seq[ChiselAnnotation]() }
+ val annotations =
+ Seq(new ChiselAnnotation with RunFirrtlTransform {
+ def toFirrtl = SinkAnnotation(component.toNamed, name)
+ def transformClass = classOf[WiringTransform] })
+ annotations.map(annotate(_))
+ }
+
+ /** Connect a source to one or more sinks
+ *
+ * @param source a source component
+ * @param sinks one or more sink components
+ * @return the name of the signal used to connect the source to the
+ * sinks
+ * @note the returned name will be based on the name of the source
+ * component
+ */
+ def bore(source: Data, sinks: Seq[Data]): String = {
+ lazy val genName = addSource(source, source.instanceName, true, true)
+ sinks.map(addSink(_, genName, true, true))
+ genName
+ }
+}
diff --git a/src/test/scala/chiselTests/BoringUtilsSpec.scala b/src/test/scala/chiselTests/BoringUtilsSpec.scala
new file mode 100644
index 00000000..3af3b4fa
--- /dev/null
+++ b/src/test/scala/chiselTests/BoringUtilsSpec.scala
@@ -0,0 +1,83 @@
+// See LICENSE for license details.
+
+package chiselTests
+
+import java.io.File
+
+import chisel3._
+import chisel3.util.Counter
+import chisel3.testers.BasicTester
+import chisel3.experimental.{MultiIOModule, RawModule, BaseModule}
+import chisel3.util.experimental.BoringUtils
+import firrtl.{CommonOptions, ExecutionOptionsManager, HasFirrtlOptions, FirrtlExecutionOptions, FirrtlExecutionSuccess,
+ FirrtlExecutionFailure}
+import firrtl.passes.wiring.WiringTransform
+
+abstract class ShouldntAssertTester(cyclesToWait: BigInt = 4) extends BasicTester {
+ val dut: BaseModule
+ val (_, done) = Counter(true.B, 2)
+ when (done) { stop() }
+}
+
+class BoringUtilsSpec extends ChiselFlatSpec with ChiselRunners {
+
+ class BoringInverter extends Module {
+ val io = IO(new Bundle{})
+ val a = Wire(UInt(1.W))
+ val notA = Wire(UInt(1.W))
+ val b = Wire(UInt(1.W))
+ a := 0.U
+ notA := ~a
+ b := a
+ chisel3.assert(b === 1.U)
+ BoringUtils.addSource(notA, "x")
+ BoringUtils.addSink(b, "x")
+ }
+
+ behavior of "BoringUtils.{addSink, addSource}"
+
+ it should "connect two wires within a module" in {
+ runTester(new ShouldntAssertTester { val dut = Module(new BoringInverter) } ) should be (true)
+ }
+
+ trait WireX { this: BaseModule =>
+ val x = Wire(UInt(4.W))
+ }
+
+ class Constant(const: Int) extends MultiIOModule with WireX {
+ x := const.U
+ }
+
+ object Constant { def apply(const: Int): Constant = Module(new Constant(const)) }
+
+ class Expect(const: Int) extends MultiIOModule with WireX {
+ x := 0.U // Default value. Output is zero unless we bore...
+ chisel3.assert(x === const.U, "x (0x%x) was not const.U (0x%x)", x, const.U)
+ }
+
+ object Expect { def apply(const: Int): Expect = Module(new Expect(const)) }
+
+ // After boring, this will have the following connections:
+ // - source(0) -> unconnected
+ // - unconnected -> expect(0)
+ // - source(1) -> expect(1)
+ // - source(2) -> expect(2)
+ class Top(val width: Int) extends MultiIOModule {
+ val source = Seq(0, 1, 2).map(x => x -> Constant(x)).toMap
+ val expect = Map(0 -> Seq.fill(2)(Expect(0)),
+ 1 -> Seq.fill(1)(Expect(1)),
+ 2 -> Seq.fill(3)(Expect(2)))
+ }
+
+ class TopTester extends ShouldntAssertTester {
+ val dut = Module(new Top(4))
+ BoringUtils.bore(dut.source(1).x, dut.expect(1).map(_.x))
+ BoringUtils.bore(dut.source(2).x, dut.expect(2).map(_.x))
+ }
+
+ behavior of "BoringUtils.bore"
+
+ it should "connect across modules using BoringUtils.bore" in {
+ runTester(new TopTester) should be (true)
+ }
+}