From 255f186a9a0f75274d8b80406f761f0b49920a2e Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 10 Feb 2026 16:15:54 -0800 Subject: [PATCH 1/2] Optimize Valid, Decoupled, and Irrevocable by using () => gen This involves a change to the type of the default constructor to pass a function instead of just a Data object. This fixes an O(n^2) issue where gen needed to be cloned twice. Once because the compiler plugin has to be conservative and clone, but then second because gen is an eager argument, it is constructed before the Output(...) invocation so Output also has to clone. By using () => gen, the compiler plugin will no longer clone the Data. The function will be called in a way that Output can detect if the result is fresh or not so it will only be cloned once. --- src/main/scala/chisel3/util/Decoupled.scala | 12 +++++++++--- src/main/scala/chisel3/util/Irrevocable.scala | 13 ++++++++++--- src/main/scala/chisel3/util/Queue.scala | 4 ++-- src/main/scala/chisel3/util/ReadyValidIO.scala | 9 ++++++--- src/main/scala/chisel3/util/Valid.scala | 14 ++++++++++---- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/main/scala/chisel3/util/Decoupled.scala b/src/main/scala/chisel3/util/Decoupled.scala index 6ba1408562a..8accec62769 100644 --- a/src/main/scala/chisel3/util/Decoupled.scala +++ b/src/main/scala/chisel3/util/Decoupled.scala @@ -16,7 +16,13 @@ import chisel3.reflect.DataMirror * of ready or valid. * @param gen the type of data to be wrapped in DecoupledIO */ -class DecoupledIO[+T <: Data](gen: T) extends ReadyValidIO[T](gen) { +class DecoupledIO[+T <: Data](gen: () => T) extends ReadyValidIO[T](gen) { + + @deprecated( + "Use companion object apply to make a Decoupled. Use constructor that takes () => T if extending Decoupled.", + "Chisel 7.9.0" + ) + def this(gen: T) = this(() => gen) /** Applies the supplied functor to the bits of this interface, returning a new * typed DecoupledIO interface. @@ -37,7 +43,7 @@ class DecoupledIO[+T <: Data](gen: T) extends ReadyValidIO[T](gen) { object Decoupled { /** Wraps some Data with a DecoupledIO interface. */ - def apply[T <: Data](gen: T): DecoupledIO[T] = new DecoupledIO(gen) + def apply[T <: Data](gen: T): DecoupledIO[T] = new DecoupledIO(() => gen) // TODO: use a proper empty data type, this is a quick and dirty solution private final class EmptyBundle extends Bundle @@ -58,7 +64,7 @@ object Decoupled { DataMirror.directionOf(irr.bits) == Direction.Output, "Only safe to cast produced Irrevocable bits to Decoupled." ) - val d = Wire(new DecoupledIO(chiselTypeOf(irr.bits))) + val d = Wire(new DecoupledIO(() => chiselTypeOf(irr.bits))) d.bits := irr.bits d.valid := irr.valid irr.ready := d.ready diff --git a/src/main/scala/chisel3/util/Irrevocable.scala b/src/main/scala/chisel3/util/Irrevocable.scala index 098a0bc1c9a..356035d13d4 100644 --- a/src/main/scala/chisel3/util/Irrevocable.scala +++ b/src/main/scala/chisel3/util/Irrevocable.scala @@ -13,11 +13,18 @@ import chisel3.reflect.DataMirror * @param gen the type of data to be wrapped in IrrevocableIO * @groupdesc Signals The actual hardware fields of the Bundle */ -class IrrevocableIO[+T <: Data](gen: T) extends ReadyValidIO[T](gen) +class IrrevocableIO[+T <: Data](gen: () => T) extends ReadyValidIO[T](gen) { + + @deprecated( + "Use companion object apply to make an Irrevocable. Use constructor that takes () => T if extending Irrevocable.", + "Chisel 7.9.0" + ) + def this(gen: T) = this(() => gen) +} /** Factory adds an irrevocable handshaking protocol to a data bundle. */ object Irrevocable { - def apply[T <: Data](gen: T): IrrevocableIO[T] = new IrrevocableIO(gen) + def apply[T <: Data](gen: T): IrrevocableIO[T] = new IrrevocableIO(() => gen) /** Upconverts a DecoupledIO input to an IrrevocableIO, allowing an IrrevocableIO to be used * where a DecoupledIO is expected. @@ -29,7 +36,7 @@ object Irrevocable { DataMirror.directionOf(dec.bits) == Direction.Input, "Only safe to cast consumed Decoupled bits to Irrevocable." ) - val i = Wire(new IrrevocableIO(chiselTypeOf(dec.bits))) + val i = Wire(new IrrevocableIO(() => chiselTypeOf(dec.bits))) dec.bits := i.bits dec.valid := i.valid i.ready := dec.ready diff --git a/src/main/scala/chisel3/util/Queue.scala b/src/main/scala/chisel3/util/Queue.scala index 611a353785d..4b5b11b3e98 100644 --- a/src/main/scala/chisel3/util/Queue.scala +++ b/src/main/scala/chisel3/util/Queue.scala @@ -191,7 +191,7 @@ object Queue { flush: Option[Bool] = None ): DecoupledIO[T] = { if (entries == 0) { - val deq = Wire(new DecoupledIO(chiselTypeOf(enq.bits))) + val deq = Wire(Decoupled(chiselTypeOf(enq.bits))) deq.valid := enq.valid deq.bits := enq.bits enq.ready := deq.ready @@ -325,7 +325,7 @@ object Queue { ): IrrevocableIO[T] = { val deq = apply(enq, entries, pipe, flow, useSyncReadMem, flush) require(entries > 0, "Zero-entry queues don't guarantee Irrevocability") - val irr = Wire(new IrrevocableIO(chiselTypeOf(deq.bits))) + val irr = Wire(Irrevocable(chiselTypeOf(deq.bits))) irr.bits := deq.bits irr.valid := deq.valid deq.ready := irr.ready diff --git a/src/main/scala/chisel3/util/ReadyValidIO.scala b/src/main/scala/chisel3/util/ReadyValidIO.scala index a968ecdb9ed..3561df4b0b6 100644 --- a/src/main/scala/chisel3/util/ReadyValidIO.scala +++ b/src/main/scala/chisel3/util/ReadyValidIO.scala @@ -16,7 +16,10 @@ import chisel3.util.simpleClassName * @param gen the type of data to be wrapped in Ready/Valid * @groupdesc Signals The actual hardware fields of the Bundle */ -abstract class ReadyValidIO[+T <: Data](gen: T) extends Bundle { +abstract class ReadyValidIO[+T <: Data](gen: () => T) extends Bundle { + + @deprecated("Use constructor that takes () => T if extending ReadyValidIO.", "Chisel 7.9.0") + def this(gen: T) = this(() => gen) /** Indicates that the consumer is ready to accept the data this cycle * @group Signals @@ -31,12 +34,12 @@ abstract class ReadyValidIO[+T <: Data](gen: T) extends Bundle { /** The data to be transferred when ready and valid are asserted at the same cycle * @group Signals */ - val bits = Output(gen) + val bits = Output(gen()) /** A stable typeName for this `ReadyValidIO` and any of its implementations * using the supplied `Data` generator's `typeName` */ - override def typeName = s"${simpleClassName(this.getClass)}_${gen.typeName}" + override def typeName = s"${simpleClassName(this.getClass)}_${bits.typeName}" } object ReadyValidIO { diff --git a/src/main/scala/chisel3/util/Valid.scala b/src/main/scala/chisel3/util/Valid.scala index 3a4a2fb182a..1097b92ae1a 100644 --- a/src/main/scala/chisel3/util/Valid.scala +++ b/src/main/scala/chisel3/util/Valid.scala @@ -21,7 +21,13 @@ import chisel3.util.simpleClassName * @see [[Valid$ Valid factory]] for concrete examples * @groupdesc Signals The actual hardware fields of the Bundle */ -class Valid[+T <: Data](gen: T) extends Bundle { +class Valid[+T <: Data](gen: () => T) extends Bundle { + + @deprecated( + "Use companion object apply to make a Valid. Use constructor that takes () => T if extending Valid.", + "Chisel 7.9.0" + ) + def this(gen: T) = this(() => gen) /** A bit that will be asserted when `bits` is valid * @group Signals @@ -31,7 +37,7 @@ class Valid[+T <: Data](gen: T) extends Bundle { /** The data to be transferred, qualified by `valid` * @group Signals */ - val bits = Output(gen) + val bits = Output(gen()) /** True when `valid` is asserted * @return a Chisel [[Bool]] true if `valid` is asserted @@ -41,7 +47,7 @@ class Valid[+T <: Data](gen: T) extends Bundle { /** A non-ambiguous name of this `Valid` instance for use in generated Verilog names * Inserts the parameterized generator's typeName, e.g. Valid_UInt4 */ - override def typeName = s"${simpleClassName(this.getClass)}_${gen.typeName}" + override def typeName = s"${simpleClassName(this.getClass)}_${bits.typeName}" /** Applies the supplied functor to the bits of this interface, returning a new typed Valid interface. * @param f The function to apply to this Valid's 'bits' with return type B @@ -94,5 +100,5 @@ object Valid { * @param gen the data to wrap * @return the wrapped input data */ - def apply[T <: Data](gen: T): Valid[T] = new Valid(gen) + def apply[T <: Data](gen: T): Valid[T] = new Valid(() => gen) } From f32a425c97d00acdc898112f0e4b2b19b245d889 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Tue, 10 Feb 2026 17:05:26 -0800 Subject: [PATCH 2/2] Fix use of DecoupledIO in tests --- src/test/scala-2/chiselTests/Vec.scala | 8 ++++---- .../experimental/hierarchy/InstanceSpec.scala | 7 ++++--- .../chiselTests/reflect/DataMirrorSpec.scala | 14 +++++++------- src/test/scala/chiselTests/ReduceTreeSpec.scala | 8 ++++---- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/test/scala-2/chiselTests/Vec.scala b/src/test/scala-2/chiselTests/Vec.scala index 3bd6f619e69..1bf587124bf 100644 --- a/src/test/scala-2/chiselTests/Vec.scala +++ b/src/test/scala-2/chiselTests/Vec.scala @@ -3,7 +3,7 @@ package chiselTests import chisel3._ -import chisel3.util.{Counter, DecoupledIO} +import chisel3.util.{Counter, Decoupled, DecoupledIO} import circt.stage.ChiselStage import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec @@ -29,8 +29,8 @@ class HugeVecTester(n: Int) extends RawModule { class ReduceTreeTester extends Module { class FooIO[T <: Data](n: Int, private val gen: T) extends Bundle { - val in = Flipped(Vec(n, new DecoupledIO(gen))) - val out = new DecoupledIO(gen) + val in = Flipped(Vec(n, Decoupled(gen))) + val out = Decoupled(gen) } class Foo[T <: Data](n: Int, private val gen: T) extends Module { @@ -39,7 +39,7 @@ class ReduceTreeTester extends Module { def foo(a: DecoupledIO[T], b: DecoupledIO[T]) = { a.ready := true.B b.ready := true.B - val out = Wire(new DecoupledIO(gen)) + val out = Wire(Decoupled(gen)) out.valid := true.B diff --git a/src/test/scala-2/chiselTests/experimental/hierarchy/InstanceSpec.scala b/src/test/scala-2/chiselTests/experimental/hierarchy/InstanceSpec.scala index abcad3391b7..8037837d09b 100644 --- a/src/test/scala-2/chiselTests/experimental/hierarchy/InstanceSpec.scala +++ b/src/test/scala-2/chiselTests/experimental/hierarchy/InstanceSpec.scala @@ -7,7 +7,7 @@ import chisel3._ import chisel3.experimental.BaseModule import chisel3.experimental.hierarchy.{instantiable, public, Definition, Instance} import chisel3.testing.scalatest.FileCheck -import chisel3.util.{DecoupledIO, Valid} +import chisel3.util.{Decoupled, DecoupledIO, Valid} import chisel3.experimental.{attach, Analog} import chisel3.stage.{ChiselGeneratorAnnotation, DesignAnnotation} import circt.stage.ChiselStage @@ -1049,6 +1049,7 @@ class InstanceSpec extends AnyFunSpec with Matchers with Utils with FileCheck { ignore("(7.b): should work on Aggregate Views") { import chiselTests.experimental.FlatDecoupledDataView._ type RegDecoupled = DecoupledIO[FizzBuzz] + val RegDecoupled = Decoupled @instantiable class MyModule extends RawModule { private val a = IO(Flipped(new FlatDecoupled)) @@ -1059,8 +1060,8 @@ class InstanceSpec extends AnyFunSpec with Matchers with Utils with FileCheck { deq <> enq } class Top extends RawModule { - val foo = IO(Flipped(new RegDecoupled(new FizzBuzz))) - val bar = IO(new RegDecoupled(new FizzBuzz)) + val foo = IO(Flipped(RegDecoupled(new FizzBuzz))) + val bar = IO(RegDecoupled(new FizzBuzz)) val i = Instance(Definition(new MyModule)) i.enq <> foo i.enq_valid := foo.valid // Make sure connections also work for @public on elements of a larger Aggregate diff --git a/src/test/scala-2/chiselTests/reflect/DataMirrorSpec.scala b/src/test/scala-2/chiselTests/reflect/DataMirrorSpec.scala index a5e045bebf8..2ab1c4a503e 100644 --- a/src/test/scala-2/chiselTests/reflect/DataMirrorSpec.scala +++ b/src/test/scala-2/chiselTests/reflect/DataMirrorSpec.scala @@ -8,7 +8,7 @@ import chisel3.experimental.hierarchy._ import chisel3.probe.Probe import chisel3.properties.Property import chisel3.reflect.DataMirror -import chisel3.util.DecoupledIO +import chisel3.util.Decoupled import circt.stage.ChiselStage import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers @@ -184,9 +184,9 @@ class DataMirrorSpec extends AnyFlatSpec with Matchers { "isFullyAligned" should "work" in { class InputOutputTest extends Bundle { - val incoming = Input(DecoupledIO(UInt(8.W))) - val outgoing = Output(DecoupledIO(UInt(8.W))) - val mixed = DecoupledIO(UInt(8.W)) + val incoming = Input(Decoupled(UInt(8.W))) + val outgoing = Output(Decoupled(UInt(8.W))) + val mixed = Decoupled(UInt(8.W)) } // Top-level negative test. assert(!DataMirror.isFullyAligned(new InputOutputTest())) @@ -205,15 +205,15 @@ class DataMirrorSpec extends AnyFlatSpec with Matchers { assert(DataMirror.isFullyAligned(Input(new InputOutputTest().mixed))) // Check DecoupledIO directly, as well as coerced. - assert(!DataMirror.isFullyAligned(new DecoupledIO(UInt(8.W)))) - assert(DataMirror.isFullyAligned(Input(new DecoupledIO(UInt(8.W))))) + assert(!DataMirror.isFullyAligned(Decoupled(UInt(8.W)))) + assert(DataMirror.isFullyAligned(Input(Decoupled(UInt(8.W))))) // Positive test, simple vector + flipped vector. assert(DataMirror.isFullyAligned(Vec(2, UInt(1.W)))) assert(DataMirror.isFullyAligned(Flipped(Vec(2, UInt(1.W))))) // Positive test, zero-length vector of non-aligned elements. - assert(DataMirror.isFullyAligned(Vec(0, new DecoupledIO(UInt(8.W))))) + assert(DataMirror.isFullyAligned(Vec(0, Decoupled(UInt(8.W))))) // Negative test: vector of flipped (?). assert(!DataMirror.isFullyAligned(Vec(2, Flipped(UInt(1.W))))) diff --git a/src/test/scala/chiselTests/ReduceTreeSpec.scala b/src/test/scala/chiselTests/ReduceTreeSpec.scala index 729a28f45ce..3e5661f1fd5 100644 --- a/src/test/scala/chiselTests/ReduceTreeSpec.scala +++ b/src/test/scala/chiselTests/ReduceTreeSpec.scala @@ -5,13 +5,13 @@ package chiselTests import chisel3._ import chisel3.simulator.scalatest.ChiselSim import chisel3.simulator.stimulus.RunUntilFinished -import chisel3.util.{is, switch, DecoupledIO, Enum} +import chisel3.util.{is, switch, Decoupled, DecoupledIO, Enum} import org.scalatest.propspec.AnyPropSpec class Arbiter[T <: Data: Manifest](n: Int, private val gen: T) extends Module { val io = IO(new Bundle { - val in = Flipped(Vec(n, new DecoupledIO(gen))) - val out = new DecoupledIO(gen) + val in = Flipped(Vec(n, Decoupled(gen))) + val out = Decoupled(gen) }) def arbitrateTwo(a: DecoupledIO[T], b: DecoupledIO[T]) = { @@ -19,7 +19,7 @@ class Arbiter[T <: Data: Manifest](n: Int, private val gen: T) extends Module { val idleA :: idleB :: hasA :: hasB :: Nil = Enum(4) val regData = Reg(gen) val regState = RegInit(idleA) - val out = Wire(new DecoupledIO(gen)) + val out = Wire(Decoupled(gen)) a.ready := regState === idleA b.ready := regState === idleB