Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Support for Decoupled/Valid with zero-width data #552

Merged
merged 1 commit into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 40 additions & 9 deletions src/main/scala/chiseltest/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions src/test/scala/chiseltest/tests/QueueTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

}
13 changes: 13 additions & 0 deletions src/test/scala/chiseltest/tests/ValidQueueTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
53 changes: 53 additions & 0 deletions src/test/scala/chiseltest/tests/ZeroWidthIntsTest.scala
Original file line number Diff line number Diff line change
@@ -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)
}