diff options
| author | Jack Koenig | 2016-12-21 14:33:07 -0800 |
|---|---|---|
| committer | Jack Koenig | 2017-02-08 18:00:32 -0800 |
| commit | 66a72ff64c46d8a9fdade77223de62b4dcfe2825 (patch) | |
| tree | 8ff97057072ed7ec1e1c64b3f1db774e2c09f99e | |
| parent | 132b80edee2fb8e730d3b6f5eb5f36051a819525 (diff) | |
Add Analog type
Used for stitching Verilog inout through Chisel Modules (from BlackBox
to BlackBox)
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/Attach.scala | 45 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/BiConnect.scala | 32 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/core/Bits.scala | 49 | ||||
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala | 1 | ||||
| -rw-r--r-- | src/main/scala/chisel3/internal/firrtl/Emitter.scala | 1 | ||||
| -rw-r--r-- | src/main/scala/chisel3/package.scala | 4 | ||||
| -rw-r--r-- | src/test/resources/AnalogBlackBox.v | 27 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/AnalogIntegrationSpec.scala | 128 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/AnalogSpec.scala | 211 |
9 files changed, 497 insertions, 1 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/core/Attach.scala b/chiselFrontend/src/main/scala/chisel3/core/Attach.scala new file mode 100644 index 00000000..5e767b84 --- /dev/null +++ b/chiselFrontend/src/main/scala/chisel3/core/Attach.scala @@ -0,0 +1,45 @@ +// See LICENSE for license details. + +package chisel3.core + +import chisel3.internal._ +import chisel3.internal.Builder.pushCommand +import chisel3.internal.firrtl._ +import chisel3.internal.sourceinfo.{SourceInfo} + +object attach { // scalastyle:ignore object.name + // Exceptions that can be generated by attach + case class AttachException(message: String) extends Exception(message) + def ConditionalAttachException = + AttachException(": Conditional attach is not allowed!") + + // Actual implementation + private[core] def impl(elts: Seq[Analog], contextModule: Module)(implicit sourceInfo: SourceInfo): Unit = { + if (Builder.whenDepth != 0) throw ConditionalAttachException + + // TODO Check that references are valid and can be attached + + pushCommand(Attach(sourceInfo, elts.map(_.lref))) + } + + /** Create an electrical connection between [[Analog]] components + * + * @param elts The components to attach + * + * @example + * {{{ + * val a1 = Wire(Analog(32.W)) + * val a2 = Wire(Analog(32.W)) + * attach(a1, a2) + * }}} + */ + def apply(elts: Analog*)(implicit sourceInfo: SourceInfo): Unit = { + try { + impl(elts, Builder.forcedModule) + } catch { + case AttachException(message) => + throwException(elts.mkString("Attaching (", ", ", s") failed @$message")) + } + } +} + diff --git a/chiselFrontend/src/main/scala/chisel3/core/BiConnect.scala b/chiselFrontend/src/main/scala/chisel3/core/BiConnect.scala index b17239e7..825dbad7 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/BiConnect.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/BiConnect.scala @@ -2,8 +2,10 @@ package chisel3.core +import chisel3.internal.Builder import chisel3.internal.Builder.pushCommand -import chisel3.internal.firrtl.Connect +import chisel3.internal.firrtl.{Attach, Connect} +import chisel3.internal.throwException import scala.language.experimental.macros import chisel3.internal.sourceinfo._ @@ -42,6 +44,9 @@ object BiConnect { BiConnectException(s": Right Record missing field ($field).") def MismatchedException(left: String, right: String) = BiConnectException(s": Left ($left) and Right ($right) have different types.") + def AttachAlreadyBulkConnectedException(sourceInfo: SourceInfo) = + BiConnectException(sourceInfo.makeMessage(": Analog previously bulk connected at " + _)) + /** This function is what recursively tries to connect a left and right together * @@ -52,6 +57,13 @@ object BiConnect { def connect(sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Data, right: Data, context_mod: Module): Unit = (left, right) match { // Handle element case (root case) + case (left_a: Analog, right_a: Analog) => + try { + analogAttach(sourceInfo, left_a, right_a, context_mod) + } catch { + // If attach fails, convert to BiConnectException + case attach.AttachException(message) => throw BiConnectException(message) + } case (left_e: Element, right_e: Element) => { elemConnect(sourceInfo, connectCompileOptions, left_e, right_e, context_mod) // TODO(twigg): Verify the element-level classes are connectable @@ -235,4 +247,22 @@ object BiConnect { // so just error out else throw UnknownRelationException } + + // This function checks if analog element-level attaching is allowed + // Then it either issues it or throws the appropriate exception. + def analogAttach(implicit sourceInfo: SourceInfo, left: Analog, right: Analog, contextModule: Module): Unit = { + // Error if left or right is BICONNECTED in the current module already + for (elt <- left :: right :: Nil) { + elt.biConnectLocs.get(contextModule) match { + case Some(sl) => throw AttachAlreadyBulkConnectedException(sl) + case None => // Do nothing + } + } + + // Do the attachment + attach.impl(Seq(left, right), contextModule) + // Mark bulk connected + left.biConnectLocs(contextModule) = sourceInfo + right.biConnectLocs(contextModule) = sourceInfo + } } diff --git a/chiselFrontend/src/main/scala/chisel3/core/Bits.scala b/chiselFrontend/src/main/scala/chisel3/core/Bits.scala index 96ea137f..bf134771 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Bits.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Bits.scala @@ -3,6 +3,7 @@ package chisel3.core import scala.language.experimental.macros +import collection.mutable import chisel3.internal._ import chisel3.internal.Builder.{pushCommand, pushOp} @@ -1020,3 +1021,51 @@ object FixedPoint { } } + +/** Data type for representing bidirectional bitvectors of a given width + * + * Analog support is limited to allowing wiring up of Verilog BlackBoxes with bidirectional (inout) + * pins. There is currently no support for reading or writing of Analog types within Chisel code. + * + * Given that Analog is bidirectional, it is illegal to assign a direction to any Analog type. It + * is legal to "flip" the direction (since Analog can be a member of aggregate types) which has no + * effect. + * + * Analog types are generally connected using the bidirectional [[attach]] mechanism, but also + * support limited bulkconnect `<>`. Analog types are only allowed to be bulk connected *once* in a + * given module. This is to prevent any surprising consequences of last connect semantics. + * + * @note This API is experimental and subject to change + */ +final class Analog private (width: Width) extends Element(width) { + require(width.known, "Since Analog is only for use in BlackBoxes, width must be known") + + // Used to enforce single bulk connect of Analog types, multi-attach is still okay + // Note that this really means 1 bulk connect per Module because a port can + // be connected in the parent module as well + private[core] val biConnectLocs = mutable.Map.empty[Module, SourceInfo] + + // Define setter/getter pairing + // Analog can only be bound to Ports and Wires (and Unbound) + private[core] override def binding_=(target: Binding): Unit = target match { + case (_: UnboundBinding | _: WireBinding | PortBinding(_, None)) => super.binding_=(target) + case _ => throwException("Only Wires and Ports can be of type Analog") + } + private[core] override def cloneTypeWidth(w: Width): this.type = + new Analog(w).asInstanceOf[this.type] + private[chisel3] def toType = s"Analog$width" + def cloneType: this.type = cloneTypeWidth(width) + // What do flatten and fromBits mean? + private[chisel3] def flatten: IndexedSeq[Bits] = + throwException("Chisel Internal Error: Analog cannot be flattened into Bits") + def do_fromBits(that: Bits)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): this.type = + throwException("Analog does not support fromBits") + final def toPrintable: Printable = PString("Analog") +} +/** Object that provides factory methods for [[Analog]] objects + * + * @note This API is experimental and subject to change + */ +object Analog { + def apply(width: Width): Analog = new Analog(width) +} diff --git a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala index 50400034..bee72817 100644 --- a/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/chiselFrontend/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -263,6 +263,7 @@ case class WhenBegin(sourceInfo: SourceInfo, pred: Arg) extends Command case class WhenEnd(sourceInfo: SourceInfo) extends Command case class Connect(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command case class BulkConnect(sourceInfo: SourceInfo, loc1: Node, loc2: Node) extends Command +case class Attach(sourceInfo: SourceInfo, locs: Seq[Node]) extends Command case class ConnectInit(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command case class Stop(sourceInfo: SourceInfo, clock: Arg, ret: Int) extends Command case class Port(id: Data, dir: Direction) diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 42bc6c30..eb00e333 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -25,6 +25,7 @@ private class Emitter(circuit: Circuit) { case e: DefMemPort[_] => s"${e.dir} mport ${e.name} = ${e.source.fullName(ctx)}[${e.index.fullName(ctx)}], ${e.clock.fullName(ctx)}" case e: Connect => s"${e.loc.fullName(ctx)} <= ${e.exp.fullName(ctx)}" case e: BulkConnect => s"${e.loc1.fullName(ctx)} <- ${e.loc2.fullName(ctx)}" + case e: Attach => e.locs.map(_.fullName(ctx)).mkString("attach (", ", ", ")") case e: Stop => s"stop(${e.clock.fullName(ctx)}, UInt<1>(1), ${e.ret})" case e: Printf => val (fmt, args) = e.pable.unpack(ctx) diff --git a/src/main/scala/chisel3/package.scala b/src/main/scala/chisel3/package.scala index a5d782ea..a236d3da 100644 --- a/src/main/scala/chisel3/package.scala +++ b/src/main/scala/chisel3/package.scala @@ -264,6 +264,10 @@ package object chisel3 { // scalastyle:ignore package.object.name type RawParam = chisel3.core.RawParam val RawParam = chisel3.core.RawParam + type Analog = chisel3.core.Analog + val Analog = chisel3.core.Analog + val attach = chisel3.core.attach + // Implicit conversions for BlackBox Parameters implicit def fromIntToIntParam(x: Int): IntParam = IntParam(BigInt(x)) implicit def fromLongToIntParam(x: Long): IntParam = IntParam(BigInt(x)) diff --git a/src/test/resources/AnalogBlackBox.v b/src/test/resources/AnalogBlackBox.v new file mode 100644 index 00000000..79e74a13 --- /dev/null +++ b/src/test/resources/AnalogBlackBox.v @@ -0,0 +1,27 @@ + +module AnalogReaderBlackBox( + inout [31:0] bus, + output [31:0] out +); + assign bus = 32'dz; + assign out = bus; +endmodule + +module AnalogWriterBlackBox( + inout [31:0] bus, + input [31:0] in +); + assign bus = in; +endmodule + +module AnalogBlackBox #( + parameter index=0 +) ( + inout [31:0] bus, + input port_0_in_valid, + input [31:0] port_0_in_bits, + output [31:0] port_0_out +); + assign port_0_out = bus; + assign bus = (port_0_in_valid)? port_0_in_bits + index : 32'dZ; +endmodule diff --git a/src/test/scala/chiselTests/AnalogIntegrationSpec.scala b/src/test/scala/chiselTests/AnalogIntegrationSpec.scala new file mode 100644 index 00000000..92f89e06 --- /dev/null +++ b/src/test/scala/chiselTests/AnalogIntegrationSpec.scala @@ -0,0 +1,128 @@ +// See LICENSE for license details. + +package chiselTests + +import chisel3._ +import chisel3.util._ +import chisel3.testers.BasicTester +import chisel3.experimental._ + +/* This test is different from AnalogSpec in that it uses more complicated black boxes that can each + * drive the bidirectional bus. It was created to evaluate Analog with synthesis tools since the + * simple tests in AnalogSpec don't anything interesting in them to synthesize. + */ + +class AnalogBlackBoxPort extends Bundle { + val in = Input(Valid(UInt(32.W))) + val out = Output(UInt(32.W)) +} + +// This IO can be used for a single BlackBox or to group multiple +// Has multiple ports for driving and checking but only one shared bus +class AnalogBlackBoxIO(n: Int) extends Bundle { + require(n > 0) + val bus = Analog(32.W) + val port = Vec(n, new AnalogBlackBoxPort) +} + +// Assigns bus to out +// Assigns in.bits + index to bus when in.valid +class AnalogBlackBox(index: Int) extends BlackBox(Map("index" -> index)) { + val io = IO(new AnalogBlackBoxIO(1)) +} + +// Wraps up n blackboxes, connecing their buses and simply forwarding their ports up +class AnalogBlackBoxWrapper(n: Int, idxs: Seq[Int]) extends Module { + require(n > 0) + val io = IO(new AnalogBlackBoxIO(n)) + val bbs = idxs.map(i => Module(new AnalogBlackBox(i))) + io.bus <> bbs.head.io.bus // Always bulk connect io.bus to first bus + io.port <> bbs.flatMap(_.io.port) // Connect ports + attach(bbs.map(_.io.bus):_*) // Attach all the buses +} + +// Common superclass for AnalogDUT and AnalogSmallDUT +abstract class AnalogDUTModule(numBlackBoxes: Int) extends Module { + require(numBlackBoxes > 0) + val io = IO(new Bundle { + val ports = Vec(numBlackBoxes, new AnalogBlackBoxPort) + }) +} + +/** Single test case for lots of things + * + * $ - Wire at top connecting child inouts (Done in AnalogDUT) + * $ - Port inout connected to 1 or more children inouts (AnalogBackBoxWrapper) + * $ - Multiple port inouts connected (AnalogConnector) + */ +class AnalogDUT extends AnalogDUTModule(5) { // 5 BlackBoxes + val mods = Seq( + Module(new AnalogBlackBoxWrapper(1, Seq(0))), + Module(new AnalogBlackBox(1)), + Module(new AnalogBlackBoxWrapper(2, Seq(2, 3))), // 2 blackboxes + Module(new AnalogBlackBox(4)) + ) + // Connect all ports to top + io.ports <> mods.flatMap(_.io.port) + // Attach first 3 Modules + attach(mods.take(3).map(_.io.bus):_*) + // Attach last module to 1st through AnalogConnector + val con = Module(new AnalogConnector) + attach(con.io.bus1, mods.head.io.bus) + attach(con.io.bus2, mods.last.io.bus) +} + +/** Same as [[AnalogDUT]] except it omits [[AnalogConnector]] because that is currently not + * supported by Verilator + * @todo Delete once Verilator can handle [[AnalogDUT]] + */ +class AnalogSmallDUT extends AnalogDUTModule(4) { // 4 BlackBoxes + val mods = Seq( + Module(new AnalogBlackBoxWrapper(1, Seq(0))), + Module(new AnalogBlackBox(1)), + Module(new AnalogBlackBoxWrapper(2, Seq(2, 3))) // 2 BlackBoxes + ) + // Connect all ports to top + io.ports <> mods.flatMap(_.io.port) + // Attach first 3 Modules + attach(mods.take(3).map(_.io.bus):_*) +} + + +// This tester is primarily intended to be able to pass the dut to synthesis +class AnalogIntegrationTester(mod: => AnalogDUTModule) extends BasicTester { + val BusValue = 2.U(32.W) // arbitrary + + val dut = Module(mod) + + val expectedValue = Wire(UInt(32.W)) + expectedValue := BusValue // Overridden each cycle + + val (cycle, done) = Counter(true.B, dut.io.ports.size) + for ((dut, idx) <- dut.io.ports.zipWithIndex) { + printf(p"@$cycle: BlackBox #$idx: $dut\n") + // Defaults + dut.in.valid := false.B + dut.in.bits := BusValue + // Error checking + assert(dut.out === expectedValue) + + when (cycle === idx.U) { + expectedValue := BusValue + idx.U + dut.in.valid := true.B + + } + } + when (done) { stop() } +} + +class AnalogIntegrationSpec extends ChiselFlatSpec { + behavior of "Verilator" + it should "support simple bidirectional wires" in { + assertTesterPasses(new AnalogIntegrationTester(new AnalogSmallDUT), Seq("/AnalogBlackBox.v")) + } + // Use this test once Verilator supports alias + ignore should "support arbitrary bidirectional wires" in { + assertTesterPasses(new AnalogIntegrationTester(new AnalogDUT), Seq("/AnalogBlackBox.v")) + } +} diff --git a/src/test/scala/chiselTests/AnalogSpec.scala b/src/test/scala/chiselTests/AnalogSpec.scala new file mode 100644 index 00000000..576a5a1f --- /dev/null +++ b/src/test/scala/chiselTests/AnalogSpec.scala @@ -0,0 +1,211 @@ +// See LICENSE for license details. + +package chiselTests + +import chisel3._ +import chisel3.util._ +import chisel3.testers.BasicTester +import chisel3.experimental.{Analog, attach} + +// IO for Modules that just connect bus to out +class AnalogReaderIO extends Bundle { + val bus = Analog(32.W) + val out = Output(UInt(32.W)) +} +// IO for Modules that drive bus from in (there should be only 1) +class AnalogWriterIO extends Bundle { + val bus = Analog(32.W) + val in = Input(UInt(32.W)) +} + +trait AnalogReader { + self: Module => + final val io = self.IO(new AnalogReaderIO) +} + +class AnalogReaderBlackBox extends BlackBox with AnalogReader + +class AnalogReaderWrapper extends Module with AnalogReader { + val mod = Module(new AnalogReaderBlackBox) + io <> mod.io +} +class AnalogWriterBlackBox extends BlackBox { + val io = IO(new AnalogWriterIO) +} +// Connects two Analog ports +class AnalogConnector extends Module { + val io = IO(new Bundle { + val bus1 = Analog(32.W) + val bus2 = Analog(32.W) + }) + io.bus1 <> io.bus2 +} + +// Parent class for tests connecing up AnalogReaders and AnalogWriters +abstract class AnalogTester extends BasicTester { + final val BusValue = "hdeadbeef".U + + final val (cycle, done) = Counter(true.B, 2) + when (done) { stop() } + + final val writer = Module(new AnalogWriterBlackBox) + writer.io.in := BusValue + + final def check(reader: Module with AnalogReader): Unit = + assert(reader.io.out === BusValue) +} + +class AnalogSpec extends ChiselFlatSpec { + behavior of "Analog" + + it should "NOT be bindable to registers" in { + a [ChiselException] should be thrownBy { + elaborate { new Module { + val io = IO(new Bundle {}) + val reg = Reg(Analog(32.W)) + }} + } + } + + it should "NOT be bindable to a direction" in { + a [ChiselException] should be thrownBy { + elaborate { new Module { + val io = IO(new Bundle { + val a = Input(Analog(32.W)) + }) + }} + } + a [ChiselException] should be thrownBy { + elaborate { new Module { + val io = IO(new Bundle { + val a = Output(Analog(32.W)) + }) + }} + } + } + + it should "be flippable" in { + elaborate { new Module { + val io = IO(new Bundle { + val a = Flipped(Analog(32.W)) + }) + }} + } + + // There is no binding on the type of a memory + // Should this be an error? + ignore should "NOT be a legal type for Mem" in { + a [ChiselException] should be thrownBy { + elaborate { new Module { + val io = IO(new Bundle {}) + val mem = Mem(16, Analog(32.W)) + }} + } + } + + it should "NOT be bindable to Mem ports" in { + a [ChiselException] should be thrownBy { + elaborate { new Module { + val io = IO(new Bundle {}) + val mem = Mem(16, Analog(32.W)) + val port = mem(5.U) + }} + } + } + + // TODO This should probably be caught in Chisel + // Also note this relies on executing Firrtl from Chisel directly + it should "NOT be connectable to UInts" in { + a [Exception] should be thrownBy { + runTester { new BasicTester { + val uint = Wire(init = 0.U(32.W)) + val sint = Wire(Analog(32.W)) + sint := uint + }} + } + } + + it should "work with 2 blackboxes bulk connected" in { + assertTesterPasses(new AnalogTester { + val mod = Module(new AnalogReaderBlackBox) + mod.io.bus <> writer.io.bus + check(mod) + }, Seq("/AnalogBlackBox.v")) + } + + it should "error if any bulk connected more than once" in { + a [ChiselException] should be thrownBy { + elaborate(new Module { + val io = IO(new Bundle {}) + val wires = List.fill(3)(Wire(Analog(32.W))) + wires(0) <> wires(1) + wires(0) <> wires(2) + }) + } + } + + it should "work with 3 blackboxes attached" in { + assertTesterPasses(new AnalogTester { + val mods = Seq.fill(2)(Module(new AnalogReaderBlackBox)) + attach(writer.io.bus, mods(0).io.bus, mods(1).io.bus) + mods.foreach(check(_)) + }, Seq("/AnalogBlackBox.v")) + } + + it should "work with 3 blackboxes separately attached via a wire" in { + assertTesterPasses(new AnalogTester { + val mods = Seq.fill(2)(Module(new AnalogReaderBlackBox)) + val busWire = Wire(Analog(32.W)) + attach(busWire, writer.io.bus) + attach(busWire, mods(0).io.bus) + attach(mods(1).io.bus, busWire) + mods.foreach(check(_)) + }, Seq("/AnalogBlackBox.v")) + } + + // This does not currently work in Verilator unless Firrtl does constant prop and dead code + // elimination on these wires + ignore should "work with intermediate wires attached to each other" in { + assertTesterPasses(new AnalogTester { + val mod = Module(new AnalogReaderBlackBox) + val busWire = Seq.fill(2)(Wire(Analog(32.W))) + attach(busWire(0), writer.io.bus) + attach(busWire(1), mod.io.bus) + attach(busWire(0), busWire(1)) + check(mod) + }, Seq("/AnalogBlackBox.v")) + } + + it should "work with blackboxes at different levels of the module hierarchy" in { + assertTesterPasses(new AnalogTester { + val mods = Seq(Module(new AnalogReaderBlackBox), Module(new AnalogReaderWrapper)) + val busWire = Wire(writer.io.bus) + attach(writer.io.bus, mods(0).io.bus, mods(1).io.bus) + mods.foreach(check(_)) + }, Seq("/AnalogBlackBox.v")) + } + + // This does not currently work in Verilator, but does work in VCS + ignore should "support two analog ports in the same module" in { + assertTesterPasses(new AnalogTester { + val reader = Module(new AnalogReaderBlackBox) + val connector = Module(new AnalogConnector) + connector.io.bus1 <> writer.io.bus + reader.io.bus <> connector.io.bus2 + check(reader) + }, Seq("/AnalogBlackBox.v")) + } + + it should "NOT support conditional connection of analog types" in { + a [ChiselException] should be thrownBy { + assertTesterPasses(new AnalogTester { + val mod = Module(new AnalogReaderBlackBox) + when (cycle > 3.U) { + mod.io.bus <> writer.io.bus + } + check(mod) + }, Seq("/AnalogBlackBox.v")) + } + } +} + |
