Skip to content

Commit

Permalink
zero width integer support for peek, poke and expect (#552)
Browse files Browse the repository at this point in the history
Co-authored-by: Kamyar Mohajerani <kammoh@gmail.com>

Co-authored-by: Kevin Läufer <laeufer@cs.berkeley.edu>
  • Loading branch information
kammoh and ekiwi committed Jan 11, 2023
1 parent 91b5501 commit 7d98ab5
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 9 deletions.
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)
}

0 comments on commit 7d98ab5

Please sign in to comment.