Skip to content

Commit

Permalink
support for Decoupled/Valid with zero-width data
Browse files Browse the repository at this point in the history
Addresses a similar to ucb-bar#35, specifically for Decoupled and Valid
interfaces. Decoupled interfaces with zero-width data can also be used
as a control/synchronization handshake where no data payload exists.
Here we take an explicit approach: the designer knows the
with of data at instatiation and can accordingly utilize appropriate
Option[Data] values.
  • Loading branch information
kammoh authored and ekiwi committed Jan 11, 2023
1 parent 005899f commit d8d04a9
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 15 deletions.
37 changes: 28 additions & 9 deletions src/main/scala/chiseltest/DecoupledDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ class DecoupledDriver[T <: Data](x: ReadyValidIO[T]) {
) // TODO: validate against bits/valid sink clocks
}

def enqueueNow(data: T): Unit = timescope {
def enqueueNow(data: T): Unit = enqueueNow(Some(data))

def enqueueNow(maybeData: Option[T]): Unit = timescope {
// TODO: check for init
x.bits.poke(data)
maybeData.foreach(data => x.bits.poke(data))
x.valid.poke(true.B)
fork
.withRegion(Monitor) {
Expand All @@ -38,9 +40,10 @@ class DecoupledDriver[T <: Data](x: ReadyValidIO[T]) {
.joinAndStep(getSourceClock)
}

def enqueue(data: T): Unit = timescope {
// TODO: check for init
x.bits.poke(data)
def enqueue(data: T): Unit = enqueue(Some(data))

def enqueue(maybeData: Option[T]): Unit = timescope {
maybeData.foreach(data => x.bits.poke(data))
x.valid.poke(true.B)
fork
.withRegion(Monitor) {
Expand All @@ -57,6 +60,12 @@ class DecoupledDriver[T <: Data](x: ReadyValidIO[T]) {
}
}

def enqueueSeq(data: Seq[Option[T]])(implicit _dummy: DummyImplicit): Unit = timescope {
for (elt <- data) {
enqueue(elt)
}
}

// Sink (dequeue) functions
//
def initSink(): this.type = {
Expand Down Expand Up @@ -84,25 +93,29 @@ class DecoupledDriver[T <: Data](x: ReadyValidIO[T]) {
}
}

def expectDequeue(data: T): Unit = timescope {
def expectDequeue(data: T): Unit = expectDequeue(Some(data))

def expectDequeue(maybeData: Option[T]): Unit = timescope {
// TODO: check for init
x.ready.poke(true.B)
fork
.withRegion(Monitor) {
waitForValid()
x.valid.expect(true.B)
x.bits.expect(data)
maybeData.foreach(data => x.bits.expect(data))
}
.joinAndStep(getSinkClock)
}

def expectDequeueNow(data: T): Unit = timescope {
def expectDequeueNow(data: T): Unit = expectDequeueNow(Some(data))

def expectDequeueNow(maybeData: Option[T]): Unit = timescope {
// TODO: check for init
x.ready.poke(true.B)
fork
.withRegion(Monitor) {
x.valid.expect(true.B)
x.bits.expect(data)
maybeData.foreach(data => x.bits.expect(data))
}
.joinAndStep(getSinkClock)
}
Expand All @@ -113,6 +126,12 @@ class DecoupledDriver[T <: Data](x: ReadyValidIO[T]) {
}
}

def expectDequeueSeq(data: Seq[Option[T]])(implicit _dummy: DummyImplicit): Unit = timescope {
for (elt <- data) {
expectDequeue(elt)
}
}

def expectPeek(data: T): Unit = {
fork.withRegion(Monitor) {
x.valid.expect(true.B)
Expand Down
29 changes: 23 additions & 6 deletions src/main/scala/chiseltest/ValidDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ class ValidDriver[T <: Data](x: ValidIO[T]) {
) // TODO: validate against bits/valid sink clocks
}

def enqueueNow(data: T): Unit = timescope {
def enqueueNow(data: T): Unit = enqueueNow(Some(data))
def enqueueNow(maybeData: Option[T]): Unit = timescope {
// TODO: check for init
x.bits.poke(data)
maybeData.foreach(data => x.bits.poke(data))
x.valid.poke(true.B)
getSourceClock.step(1)
}
Expand All @@ -40,6 +41,12 @@ class ValidDriver[T <: Data](x: ValidIO[T]) {
}
}

def enqueueSeq(data: Seq[Option[T]])(implicit _dummy: DummyImplicit): Unit = timescope {
for (elt <- data) {
enqueueNow(elt)
}
}

// Sink (dequeue) functions
//
def initSink(): this.type = {
Expand All @@ -66,23 +73,27 @@ class ValidDriver[T <: Data](x: ValidIO[T]) {
}
}

def expectDequeue(data: T): Unit = timescope {
def expectDequeue(data: T): Unit = expectDequeue(Some(data))

def expectDequeue(maybeData: Option[T]): Unit = timescope {
// TODO: check for init
fork
.withRegion(Monitor) {
waitForValid()
x.valid.expect(true.B)
x.bits.expect(data)
maybeData.foreach(data => x.bits.expect(data))
}
.joinAndStep(getSinkClock)
}

def expectDequeueNow(data: T): Unit = timescope {
def expectDequeueNow(data: T): Unit = expectDequeueNow(Some(data))

def expectDequeueNow(maybeData: Option[T]): Unit = timescope {
// TODO: check for init
fork
.withRegion(Monitor) {
x.valid.expect(true.B)
x.bits.expect(data)
maybeData.foreach(data => x.bits.expect(data))
}
.joinAndStep(getSinkClock)
}
Expand All @@ -93,6 +104,12 @@ class ValidDriver[T <: Data](x: ValidIO[T]) {
}
}

def expectDequeueSeq(data: Seq[Option[T]])(implicit _dummy: DummyImplicit): Unit = timescope {
for (elt <- data) {
expectDequeue(elt)
}
}

/** This does not advance time, unlike expectDequeue
* this method will throw an error if valid has not been asserted
*/
Expand Down
41 changes: 41 additions & 0 deletions src/test/scala/chiseltest/tests/QueueTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,28 @@ class QueueTest extends AnyFlatSpec with ChiselScalatestTester {
}
}

it should "pass through elements, using enqueueSeq of Seq[Option[UInt]]" in {
test(new QueueModule(UInt(8.W), 2)) { c =>
c.in.initSource().setSourceClock(c.clock)
c.out.initSink().setSinkClock(c.clock)

fork {
c.in.enqueueSeq(Seq(42.U, 43.U))
c.in.enqueueSeq(Seq(Some(44.U), Some(45.U), Some(46.U)))
}

c.out.expectInvalid()
c.clock.step(1) // wait for first element to enqueue
c.out.expectDequeueNow(42.U)
c.out.expectPeek(43.U) // check that queue stalls
c.clock.step(1)
c.out.expectDequeueNow(43.U)
c.out.expectDequeueSeq(Seq(Some(44.U), Some(45.U)))
c.out.expectDequeueSeq(Seq(46.U))
c.out.expectInvalid()
}
}

it should "work with a combinational queue" in {
test(new PassthroughQueue(UInt(8.W))) { c =>
c.in.initSource()
Expand Down Expand Up @@ -80,4 +102,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(None, None, None))
}

c.out.expectInvalid()
c.clock.step(1) // wait for first element to enqueue
c.out.expectDequeueNow(None)
c.clock.step(1)
c.out.expectDequeueNow(None)
c.out.expectDequeueNow(None)
c.out.expectInvalid()
}
}

}
31 changes: 31 additions & 0 deletions src/test/scala/chiseltest/tests/ValidQueueTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ class ValidQueueTest extends AnyFlatSpec with ChiselScalatestTester {
}
}

it should "pass through elements, using enqueueSeq/expectDequeueSeq of Seq[Option[UInt]]" in {
test(new ValidQueueModule(UInt(8.W), delay = 3)).withAnnotations(Seq()) { c =>
c.in.initSource().setSourceClock(c.clock)
c.out.initSink().setSinkClock(c.clock)

c.out.expectInvalid()

fork {
c.in.enqueueSeq(Seq(Some(42.U), Some(43.U)))
c.in.enqueueSeq(Seq(44.U))
}.fork {
c.out.expectDequeueSeq(Seq(42.U))
c.out.expectDequeueSeq(Seq(Some(43.U), Some(44.U)))
c.out.expectInvalid()
}.join()
}
}

it should "work with a combinational queue" in {
test(new ValidQueueModule(UInt(8.W), delay = 3)) { c =>
c.in.initSource().setSourceClock(c.clock)
Expand All @@ -65,6 +83,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(None, None, None))
}.fork {
c.out.expectDequeueSeq(Seq(None, None, None))
}.join()
}
}

class TriBundle extends Bundle {
val a = UInt(8.W)
val b = UInt(8.W)
Expand Down

0 comments on commit d8d04a9

Please sign in to comment.