diff options
| -rw-r--r-- | src/main/scala/chisel3/util/Counter.scala | 65 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/ChiselSpec.scala | 14 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/Counter.scala | 19 |
3 files changed, 85 insertions, 13 deletions
diff --git a/src/main/scala/chisel3/util/Counter.scala b/src/main/scala/chisel3/util/Counter.scala index cb503055..11daa515 100644 --- a/src/main/scala/chisel3/util/Counter.scala +++ b/src/main/scala/chisel3/util/Counter.scala @@ -19,27 +19,51 @@ 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.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 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 math.max(1, n)) } - /** Increment the counter + /** The current value of the counter. */ + 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 + + /** 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 + + 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 } else { true.B @@ -67,4 +91,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/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 7980e772..9518fb5c 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -150,6 +150,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) diff --git a/src/test/scala/chiselTests/Counter.scala b/src/test/scala/chiselTests/Counter.scala index 34fdec8e..31bfe7eb 100644 --- a/src/test/scala/chiselTests/Counter.scala +++ b/src/test/scala/chiselTests/Counter.scala @@ -35,6 +35,19 @@ class WrapTester(max: Int) extends BasicTester { } } +class RangeTester(r: Range) extends BasicTester { + val (cnt, wrap) = Counter(r) + val checkWrap = RegInit(false.B) + + when(checkWrap) { + assert(cnt === r.head.U) + stop() + }.elsewhen(wrap) { + assert(cnt === r.last.U) + checkWrap := true.B + } +} + class CounterSpec extends ChiselPropSpec { property("Counter should count up") { forAll(smallPosInts) { (max: Int) => assertTesterPasses{ new CountTester(max) } } @@ -47,4 +60,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) } + } + } } |
