From 5cd8ad008f8ac1796ac47c799e6a4db10f6e87c8 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Tue, 2 Apr 2024 18:58:24 -0600 Subject: [PATCH] Add a new BoringUtils.drive API for boring to drive a sink. (#3960) This API allows users to bore to a sink they plan to drive, which complements the existing API to bore from a source to read. --- core/src/main/scala/chisel3/RawModule.scala | 7 ++- .../util/experimental/BoringUtils.scala | 37 +++++++++-- .../scala/chiselTests/BoringUtilsSpec.scala | 61 +++++++++++++++++++ 3 files changed, 96 insertions(+), 9 deletions(-) diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 4c750138742..7af32391bbf 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -213,10 +213,11 @@ abstract class RawModule extends BaseModule { case (true, false) if left.probeInfo.get.writable => ProbeDefine(si, left.lref, RWProbeExpr(Node(right))) case (true, false) => ProbeDefine(si, left.lref, ProbeExpr(Node(right))) case (false, true) => Connect(si, left.lref, ProbeRead(Node(right))) - case (false, false) => + case (false, false) => + // For non-probe, directly create Nodes for lhs, skipping visibility check to support BoringUtils.drive. (left, right) match { - case (_: Property[_], _: Property[_]) => PropAssign(si, left.lref, Node(right)) - case (_, _) => Connect(si, left.lref, Node(right)) + case (_: Property[_], _: Property[_]) => PropAssign(si, Node(left), Node(right)) + case (_, _) => Connect(si, Node(left), Node(right)) } } val secretCommands = if (_closed) { diff --git a/src/main/scala/chisel3/util/experimental/BoringUtils.scala b/src/main/scala/chisel3/util/experimental/BoringUtils.scala index 18ac0ec66b2..e37c61f926f 100644 --- a/src/main/scala/chisel3/util/experimental/BoringUtils.scala +++ b/src/main/scala/chisel3/util/experimental/BoringUtils.scala @@ -4,6 +4,7 @@ package chisel3.util.experimental import chisel3._ import chisel3.probe.{Probe, RWProbe} +import chisel3.reflect.DataMirror import chisel3.Data.ProbeInfo import chisel3.experimental.{annotate, requireIsHardware, skipPrefix, BaseModule, ChiselAnnotation, SourceInfo} import chisel3.internal.{Builder, BuilderContextCache, NamedComponent, Namespace, PortBinding} @@ -217,8 +218,13 @@ object BoringUtils { genName } - private def boreOrTap[A <: Data](source: A, createProbe: Option[ProbeInfo] = None)(implicit si: SourceInfo): A = { - import reflect.DataMirror + private def boreOrTap[A <: Data]( + source: A, + createProbe: Option[ProbeInfo] = None, + isDrive: Boolean = false + )( + implicit si: SourceInfo + ): A = { def parent(d: Data): BaseModule = d.topBinding.location.get def purePortTypeBase = if (createProbe.nonEmpty) Output(chiselTypeOf(source)) else if (DataMirror.hasOuterFlip(source)) Flipped(chiselTypeOf(source)) @@ -263,7 +269,11 @@ object BoringUtils { // if drilling down, don't drill Probe types val bore = if (up) module.createSecretIO(purePortType) else module.createSecretIO(Flipped(purePortTypeBase)) module.addSecretIO(bore) - conLoc.asInstanceOf[RawModule].secretConnection(bore, rhs) + if (isDrive) { + conLoc.asInstanceOf[RawModule].secretConnection(rhs, bore) + } else { + conLoc.asInstanceOf[RawModule].secretConnection(bore, rhs) + } bore } } @@ -290,8 +300,8 @@ object BoringUtils { Builder.error(s"Cannot bore from $source to ${thisModule.name}, as they do not share a least common ancestor") } val (upPath, downPath) = lcaResult.get - val lcaSource = drill(source, upPath.dropRight(1), upPath.dropRight(1), true) - val sink = drill(lcaSource, downPath.reverse.tail, downPath.reverse, false) + val lcaSource = drill(source, upPath.dropRight(1), upPath.dropRight(1), up = !isDrive) + val sink = drill(lcaSource, downPath.reverse.tail, downPath.reverse, up = isDrive) if ( createProbe.nonEmpty || DataMirror.hasProbeTypeModifier(purePortTypeBase) || @@ -301,7 +311,11 @@ object BoringUtils { } else { // Creating a wire to assign the result to. We will return this. val bore = Wire(purePortTypeBase) - thisModule.asInstanceOf[RawModule].secretConnection(bore, sink) + if (isDrive) { + thisModule.asInstanceOf[RawModule].secretConnection(sink, bore) + } else { + thisModule.asInstanceOf[RawModule].secretConnection(bore, sink) + } bore } } @@ -314,6 +328,17 @@ object BoringUtils { boreOrTap(source, createProbe = None) } + /** Access a sink [[Data]] for driving that may or may not be in the current module. + * + * If the sink is in a child module, than create input ports to allow driving the requested sink. + * + * Note that the sink may not be a probe, and [[rwTap]] should be used instead. + */ + def drive[A <: Data](sink: A)(implicit si: SourceInfo): A = { + require(!DataMirror.hasProbeTypeModifier(sink), "cannot drive a probe from BoringUtils.drive") + boreOrTap(sink, createProbe = None, isDrive = true) + } + /** Access a source [[Data]] that may or may not be in the current module. If * this is in a child module, then create read-only probe ports to allow * access to the requested source. diff --git a/src/test/scala/chiselTests/BoringUtilsSpec.scala b/src/test/scala/chiselTests/BoringUtilsSpec.scala index 5236e361ed4..11b74b8b73d 100644 --- a/src/test/scala/chiselTests/BoringUtilsSpec.scala +++ b/src/test/scala/chiselTests/BoringUtilsSpec.scala @@ -376,4 +376,65 @@ class BoringUtilsSpec extends ChiselFlatSpec with ChiselRunners with Utils with "propassign a, bar.a_bore" )() } + + behavior.of("BoringUtils.drive") + + it should "fail on probes" in { + class Foo extends RawModule { + val a = Wire(Bool()) + val p = ProbeValue(a) + } + + class Bar extends RawModule { + val foo = Module(new Foo) + + BoringUtils.drive(foo.p) := 1.B + } + + val e = the[Exception] thrownBy circt.stage.ChiselStage.emitCHIRRTL(new Bar) + + e.getMessage should include("requirement failed: cannot drive a probe from BoringUtils.drive") + } + + it should "bore ports for driving hardware" in { + class Foo extends RawModule { + val a = Wire(Bool()) + } + + class Bar extends RawModule { + val foo = Module(new Foo) + + BoringUtils.drive(foo.a) := 1.B + } + + val chirrtl = circt.stage.ChiselStage.emitCHIRRTL(new Bar) + + matchesAndOmits(chirrtl)( + "input bore", + "connect a, bore", + "wire bore", + "connect bore, UInt<1>(0h1)", + "connect foo.bore, bore" + )() + } + + it should "bore ports for driving properties" in { + class Foo extends RawModule { + val a = Wire(Property[Int]()) + } + + class Bar extends RawModule { + val foo = Module(new Foo) + + BoringUtils.drive(foo.a) := Property(1) + } + + val chirrtl = circt.stage.ChiselStage.emitCHIRRTL(new Bar) + + matchesAndOmits(chirrtl)( + "input bore", + "propassign a, bore", + "propassign foo.bore, Integer(1)" + )() + } }