diff --git a/src/main/scala/chiseltest/package.scala b/src/main/scala/chiseltest/package.scala index e703c1e54..40b6026f6 100644 --- a/src/main/scala/chiseltest/package.scala +++ b/src/main/scala/chiseltest/package.scala @@ -45,40 +45,64 @@ package object chiseltest { /** allows access to chisel UInt type signals with Scala native values */ implicit class testableUInt(x: UInt) { + private def isZeroWidth = x.widthOption.contains(0) def poke(value: UInt): Unit = poke(value.litValue) def poke(value: BigInt): Unit = { Utils.ensureFits(x, value) - Utils.pokeBits(x, value) + if (!isZeroWidth) { // poking a zero width value is a no-op + Utils.pokeBits(x, value) + } } private[chiseltest] def expectInternal(value: BigInt, message: Option[() => String]): Unit = { Utils.ensureFits(x, value) - Utils.expectBits(x, value, message, None) + if (!isZeroWidth) { // zero width UInts always have the value 0 + Utils.expectBits(x, value, message, None) + } } def expect(value: UInt): Unit = expectInternal(value.litValue, None) def expect(value: UInt, message: => String): Unit = expectInternal(value.litValue, Some(() => message)) def expect(value: BigInt): Unit = expectInternal(value, None) def expect(value: BigInt, message: => String): Unit = expectInternal(value, Some(() => message)) - def peek(): UInt = Context().backend.peekBits(x).asUInt(DataMirror.widthOf(x)) - def peekInt(): BigInt = Context().backend.peekBits(x) + def peekInt(): BigInt = if (!isZeroWidth) { Context().backend.peekBits(x) } + else { + // zero width UInts always have the value 0 + 0 + } + def peek(): UInt = if (!isZeroWidth) { peekInt().asUInt(DataMirror.widthOf(x)) } + else { + 0.U // TODO: change to 0-width constant once supported: https://github.com/chipsalliance/chisel3/pull/2932 + } } /** allows access to chisel SInt type signals with Scala native values */ implicit class testableSInt(x: SInt) { + private def isZeroWidth = x.widthOption.contains(0) def poke(value: SInt): Unit = poke(value.litValue) def poke(value: BigInt): Unit = { Utils.ensureFits(x, value) - Utils.pokeBits(x, value) + if (!isZeroWidth) { // poking a zero width value is a no-op + Utils.pokeBits(x, value) + } } private[chiseltest] def expectInternal(value: BigInt, message: Option[() => String]): Unit = { Utils.ensureFits(x, value) - Utils.expectBits(x, value, message, None) + if (!isZeroWidth) { // zero width UInts always have the value 0 + Utils.expectBits(x, value, message, None) + } } def expect(value: SInt): Unit = expectInternal(value.litValue, None) def expect(value: SInt, message: => String): Unit = expectInternal(value.litValue, Some(() => message)) def expect(value: BigInt): Unit = expectInternal(value, None) def expect(value: BigInt, message: => String): Unit = expectInternal(value, Some(() => message)) - def peek(): SInt = Context().backend.peekBits(x).asSInt(DataMirror.widthOf(x)) - def peekInt(): BigInt = Context().backend.peekBits(x) + def peekInt(): BigInt = if (!isZeroWidth) { Context().backend.peekBits(x) } + else { + // zero width UInts always have the value 0 + 0 + } + def peek(): SInt = if (!isZeroWidth) { peekInt().asSInt(DataMirror.widthOf(x)) } + else { + 0.S // TODO: change to 0-width constant once supported: https://github.com/chipsalliance/chisel3/pull/2932 + } } /** allows access to chisel Interval type signals with Scala native values */ @@ -242,9 +266,14 @@ package object chiseltest { case _: Vec[_] | _: Record => false case _ => true } + val isZeroWidthInt = value match { + case v: Bits if v.widthOption.contains(0) => true + case _ => false + } // if we are dealing with a ground type non-literal, this is only allowed if we are doing a partial poke/expect if (isGroundType && !value.isLit) { - if (allowPartial) { true } + // zero-width integers do not carry a value and thus are allowed to be DontCare + if (allowPartial || isZeroWidthInt) { true } else { throw new NonLiteralValueError(value, x, op) } @@ -376,6 +405,8 @@ package object chiseltest { // Throw an exception if the value cannot fit into the signal. def ensureFits(signal: SInt, value: BigInt): Unit = { signal.widthOption match { + case Some(0) => // special case: 0-width SInts always represent 0 + ensureInRange(signal, value, 0, 0) case Some(w) => val m = BigInt(1) << (w - 1) ensureInRange(signal, value, -m, m - 1) diff --git a/src/test/scala/chiseltest/tests/QueueTest.scala b/src/test/scala/chiseltest/tests/QueueTest.scala index e5adf4c36..765cae08f 100644 --- a/src/test/scala/chiseltest/tests/QueueTest.scala +++ b/src/test/scala/chiseltest/tests/QueueTest.scala @@ -80,4 +80,23 @@ class QueueTest extends AnyFlatSpec with ChiselScalatestTester { } } + it should "enqueue/dequeue zero-width data" in { + test(new QueueModule(UInt(0.W), 2)) { c => + c.in.initSource().setSourceClock(c.clock) + c.out.initSink().setSinkClock(c.clock) + + fork { + c.in.enqueueSeq(Seq(0.U, 0.U, 0.U)) + } + + c.out.expectInvalid() + c.clock.step(1) // wait for first element to enqueue + c.out.expectDequeueNow(0.U) + c.clock.step(1) + c.out.expectDequeueNow(0.U) + c.out.expectDequeueNow(0.U) + c.out.expectInvalid() + } + } + } diff --git a/src/test/scala/chiseltest/tests/ValidQueueTest.scala b/src/test/scala/chiseltest/tests/ValidQueueTest.scala index 13afcf146..8cb6ec9e7 100644 --- a/src/test/scala/chiseltest/tests/ValidQueueTest.scala +++ b/src/test/scala/chiseltest/tests/ValidQueueTest.scala @@ -65,6 +65,19 @@ class ValidQueueTest extends AnyFlatSpec with ChiselScalatestTester { } } + it should "work with a zero-width data queue" in { + test(new ValidQueueModule(UInt(0.W), delay = 3)) { c => + c.in.initSource().setSourceClock(c.clock) + c.out.initSink().setSinkClock(c.clock) + + fork { + c.in.enqueueSeq(Seq(0.U, 0.U, 0.U)) + }.fork { + c.out.expectDequeueSeq(Seq(0.U, 0.U, 0.U)) + }.join() + } + } + class TriBundle extends Bundle { val a = UInt(8.W) val b = UInt(8.W) diff --git a/src/test/scala/chiseltest/tests/ZeroWidthIntsTest.scala b/src/test/scala/chiseltest/tests/ZeroWidthIntsTest.scala new file mode 100644 index 000000000..bee845350 --- /dev/null +++ b/src/test/scala/chiseltest/tests/ZeroWidthIntsTest.scala @@ -0,0 +1,53 @@ +package chiseltest.tests + +import chiseltest._ +import chisel3._ +import chisel3.experimental.BundleLiterals._ +import org.scalatest.freespec.AnyFreeSpec + +class ZeroWidthIntsTest extends AnyFreeSpec with ChiselScalatestTester { + "peek, poke and expect zero width UInt" in { + test(new PassthroughModule(UInt(0.W))) { c => + c.in.poke(0.U) + c.in.expect(0.U) + assert(c.in.peekInt() == 0) + assert(c.in.peek().litValue == 0) + } + } + + "peek, poke and expect zero width SInt" in { + test(new PassthroughModule(SInt(0.W))) { c => + c.in.poke(0.S) + c.in.expect(0.S) + assert(c.in.peekInt() == 0) + assert(c.in.peek().litValue == 0) + } + } + + "in a bundle, zero-width UInt fields do not need to be set" in { + // Since zero width signals carry no info, DontCare is allowed in pokes and expects + test(new PassthroughModule(new ZeroWidthTestBundle)) { c => + c.in.poke((new ZeroWidthTestBundle).Lit(_.a -> 8.U, _.b -> 6.U)) + } + } + + "in a bundle, zero-width SInt fields do not need to be set" in { + // Since zero width signals carry no info, DontCare is allowed in pokes and expects + test(new PassthroughModule(new ZeroWidthTestBundleSigned)) { c => + c.in.poke((new ZeroWidthTestBundleSigned).Lit(_.a -> -8.S, _.b -> 6.S)) + } + } + +} + +class ZeroWidthTestBundle extends Bundle { + val a = UInt(8.W) + val z = UInt(0.W) + val b = UInt(7.W) +} + +class ZeroWidthTestBundleSigned extends Bundle { + val a = SInt(8.W) + val z = SInt(0.W) + val b = SInt(7.W) +}