Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow a counter to be instantiated using a Scala range #1515

Merged
merged 6 commits into from
Jul 30, 2020
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
65 changes: 52 additions & 13 deletions src/main/scala/chisel3/util/Counter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a problem, but just FYI: abs and max are defined on the various integral types, so you can write e.g. r.step.abs or foo max bar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks 😄

I'm still learning how to write idomatic Scala.


/** 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
Expand Down Expand Up @@ -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)
}
}
14 changes: 14 additions & 0 deletions src/test/scala/chiselTests/ChiselSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
19 changes: 19 additions & 0 deletions src/test/scala/chiselTests/Counter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) } }
Expand All @@ -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) }
}
}
}