From 7940c53f227ee65040bf12dc0cc8df0e51a33971 Mon Sep 17 00:00:00 2001 From: Joshua Bassett Date: Wed, 15 Jul 2020 09:22:10 +1000 Subject: [PATCH 1/5] Add positive range generator --- src/test/scala/chiselTests/ChiselSpec.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 2ee6fbcf7eb..64a1ae64d86 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -147,6 +147,20 @@ class ChiselPropSpec extends PropSpec with ChiselRunners with ScalaCheckProperty // Generator for small positive integers. val smallPosInts = Gen.choose(1, 4) + // Generator for positive (ascending or descending) ranges. + def posRange: Gen[Range] = for { + dir <- Gen.oneOf(true, false) + step <- Gen.choose(1, 3) + m <- Gen.choose(1, 10) + n <- Gen.choose(1, 10) + } yield { + if (dir) { + Range(m, (m+n)*step, step) + } else { + Range((m+n)*step, m, -step) + } + } + // Generator for widths considered "safe". val safeUIntWidth = Gen.choose(1, 30) From 8fef104a989828f41ea29a5b5e51c2c6aead0af5 Mon Sep 17 00:00:00 2001 From: Joshua Bassett Date: Wed, 15 Jul 2020 19:45:19 +1000 Subject: [PATCH 2/5] Allow the Counter module to be instantiated with a Scala range --- src/main/scala/chisel3/util/Counter.scala | 61 ++++++++++++++++++----- src/test/scala/chiselTests/Counter.scala | 14 ++++++ 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/main/scala/chisel3/util/Counter.scala b/src/main/scala/chisel3/util/Counter.scala index cb503055115..5135ffc9627 100644 --- a/src/main/scala/chisel3/util/Counter.scala +++ b/src/main/scala/chisel3/util/Counter.scala @@ -19,27 +19,47 @@ import chisel3.internal.naming.chiselName // can't use chisel3_ version because * ... * } * }}} - * - * @param n number of counts before the counter resets (or one more than the - * maximum output value of the counter), need not be a power of two */ @chiselName -class Counter(val n: Int) { - require(n >= 0, s"Counter value must be nonnegative, got: $n") - val value = if (n > 1) RegInit(0.U(log2Ceil(n).W)) else 0.U +class Counter private (r: Range) { + require(r.start >= 0 && r.end >= 0, s"Counter range must be positive, got: $r") + + private val delta = math.abs(r.step).U + private val width = math.max(log2Up(r.start), log2Up(r.end)) + 1 - /** Increment the counter + /** Creates a counter with the specified number of steps. + * + * @param n number of steps before the counter resets + */ + def this(n: Int) { this(0 until n) } + + /** The current value of the counter. */ + val value = if (r.length > 1) RegInit(r.start.U(width.W)) else r.start.U + + /** The range of the counter values. */ + def range: Range = r + + /** Increments the counter by a step. * * @note The incremented value is registered and will be visible on the next clock cycle - * @return whether the counter will wrap to zero on the next cycle + * @return whether the counter will wrap on the next clock cycle */ def inc(): Bool = { - if (n > 1) { - val wrap = value === (n-1).U - value := value + 1.U - if (!isPow2(n)) { - when (wrap) { value := 0.U } + if (r.length > 1) { + val wrap = value === r.last.U + + when (wrap) { + value := r.start.U + } otherwise { + if (r.step > 0) { + // Increasing range + value := value + delta + } else { + // Decreasing range + value := value - delta + } } + wrap } else { true.B @@ -67,4 +87,19 @@ object Counter when (cond) { wrap := c.inc() } (c.value, wrap) } + + /** Creates a counter that steps through a specified range of values. + * + * @param r the range of counter values + * @param enable controls whether the counter increments this cycle + * @return tuple of the counter value and whether the counter will wrap (the value is at + * maximum and the condition is true). + */ + @chiselName + def apply(r: Range, enable: Bool = true.B): (UInt, Bool) = { + val c = new Counter(r) + val wrap = WireInit(false.B) + when (enable) { wrap := c.inc() } + (c.value, wrap) + } } diff --git a/src/test/scala/chiselTests/Counter.scala b/src/test/scala/chiselTests/Counter.scala index 34fdec8e4d2..97cd6ff552d 100644 --- a/src/test/scala/chiselTests/Counter.scala +++ b/src/test/scala/chiselTests/Counter.scala @@ -35,6 +35,14 @@ class WrapTester(max: Int) extends BasicTester { } } +class RangeTester(r: Range) extends BasicTester { + val (cnt, wrap) = Counter(r) + when(wrap) { + assert(cnt === r.last.U) + stop() + } +} + class CounterSpec extends ChiselPropSpec { property("Counter should count up") { forAll(smallPosInts) { (max: Int) => assertTesterPasses{ new CountTester(max) } } @@ -47,4 +55,10 @@ class CounterSpec extends ChiselPropSpec { property("Counter should wrap") { forAll(smallPosInts) { (max: Int) => assertTesterPasses{ new WrapTester(max) } } } + + property("Counter should handle a range") { + forAll(posRange) { (r: Range) => + assertTesterPasses{ new RangeTester(r) } + } + } } From fbc7caa7d91cd06d7ec6ce54e4fd892629551580 Mon Sep 17 00:00:00 2001 From: Josh Bassett Date: Sat, 18 Jul 2020 10:01:24 +1000 Subject: [PATCH 3/5] Use head/last to determine counter width Co-authored-by: Jack Koenig --- src/main/scala/chisel3/util/Counter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/chisel3/util/Counter.scala b/src/main/scala/chisel3/util/Counter.scala index 5135ffc9627..d7f9370d126 100644 --- a/src/main/scala/chisel3/util/Counter.scala +++ b/src/main/scala/chisel3/util/Counter.scala @@ -25,7 +25,7 @@ class Counter private (r: Range) { require(r.start >= 0 && r.end >= 0, s"Counter range must be positive, got: $r") private val delta = math.abs(r.step).U - private val width = math.max(log2Up(r.start), log2Up(r.end)) + 1 + private val width = math.max(log2Up(r.head), log2Up(r.last)) + 1 /** Creates a counter with the specified number of steps. * From 25066eebe3763b4a9cf705710934176eb57a4e80 Mon Sep 17 00:00:00 2001 From: Joshua Bassett Date: Sat, 18 Jul 2020 12:53:14 +1000 Subject: [PATCH 4/5] Let counter overflow naturally when appropriate We only need to explicitly wrap counters that don't start at zero, or end on a power of two. Otherwise we just let the counter overflow naturally to avoid wasting an extra mux. --- src/main/scala/chisel3/util/Counter.scala | 29 +++++++++++++---------- src/test/scala/chiselTests/Counter.scala | 9 +++++-- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/main/scala/chisel3/util/Counter.scala b/src/main/scala/chisel3/util/Counter.scala index d7f9370d126..1c2e777aa5e 100644 --- a/src/main/scala/chisel3/util/Counter.scala +++ b/src/main/scala/chisel3/util/Counter.scala @@ -24,8 +24,8 @@ import chisel3.internal.naming.chiselName // can't use chisel3_ version because class Counter private (r: Range) { require(r.start >= 0 && r.end >= 0, s"Counter range must be positive, got: $r") - private val delta = math.abs(r.step).U - private val width = math.max(log2Up(r.head), log2Up(r.last)) + 1 + private val delta = math.abs(r.step) + private val width = math.max(log2Up(r.last + 1), log2Up(r.head + 1)) /** Creates a counter with the specified number of steps. * @@ -34,7 +34,7 @@ class Counter private (r: Range) { def this(n: Int) { this(0 until n) } /** The current value of the counter. */ - val value = if (r.length > 1) RegInit(r.start.U(width.W)) else r.start.U + val value = if (r.length > 1) RegInit(r.head.U(width.W)) else r.head.U /** The range of the counter values. */ def range: Range = r @@ -48,16 +48,19 @@ class Counter private (r: Range) { if (r.length > 1) { val wrap = value === r.last.U - when (wrap) { - value := r.start.U - } otherwise { - if (r.step > 0) { - // Increasing range - value := value + delta - } else { - // Decreasing range - value := value - delta - } + if (r.step > 0) { + // Increasing range + value := value + delta.U + } else { + // Decreasing range + value := value - delta.U + } + + // We only need to explicitly wrap counters that don't start at zero, or + // end on a power of two. Otherwise we just let the counter overflow + // naturally to avoid wasting an extra mux. + if (!(r.head == 0 && isPow2(r.last + delta))) { + when(wrap) { value := r.head.U } } wrap diff --git a/src/test/scala/chiselTests/Counter.scala b/src/test/scala/chiselTests/Counter.scala index 97cd6ff552d..31bfe7eb615 100644 --- a/src/test/scala/chiselTests/Counter.scala +++ b/src/test/scala/chiselTests/Counter.scala @@ -37,9 +37,14 @@ class WrapTester(max: Int) extends BasicTester { class RangeTester(r: Range) extends BasicTester { val (cnt, wrap) = Counter(r) - when(wrap) { - assert(cnt === r.last.U) + val checkWrap = RegInit(false.B) + + when(checkWrap) { + assert(cnt === r.head.U) stop() + }.elsewhen(wrap) { + assert(cnt === r.last.U) + checkWrap := true.B } } From bded72aedfb78560af09c8c01e7f7b385e6eb345 Mon Sep 17 00:00:00 2001 From: Joshua Bassett Date: Mon, 27 Jul 2020 21:13:50 +1000 Subject: [PATCH 5/5] Require counter range to be non-empty --- src/main/scala/chisel3/util/Counter.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/scala/chisel3/util/Counter.scala b/src/main/scala/chisel3/util/Counter.scala index 1c2e777aa5e..11daa515289 100644 --- a/src/main/scala/chisel3/util/Counter.scala +++ b/src/main/scala/chisel3/util/Counter.scala @@ -22,16 +22,17 @@ import chisel3.internal.naming.chiselName // can't use chisel3_ version because */ @chiselName class Counter private (r: Range) { + require(r.length > 0, s"Counter range cannot be empty, got: $r") require(r.start >= 0 && r.end >= 0, s"Counter range must be positive, got: $r") - private val delta = math.abs(r.step) - private val width = math.max(log2Up(r.last + 1), log2Up(r.head + 1)) + private lazy val delta = math.abs(r.step) + private lazy val width = math.max(log2Up(r.last + 1), log2Up(r.head + 1)) /** Creates a counter with the specified number of steps. * * @param n number of steps before the counter resets */ - def this(n: Int) { this(0 until n) } + def this(n: Int) { this(0 until math.max(1, n)) } /** The current value of the counter. */ val value = if (r.length > 1) RegInit(r.head.U(width.W)) else r.head.U