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

Conversation

nullobject
Copy link
Contributor

@nullobject nullobject commented Jul 15, 2020

This PR adds the ability for a counter to be instantiated using a Scala range.

The original API has been preserved, but the counter now works internally with a range. Using a range to configure the counter allows us to do interesting things.

Counting upwards:

val (value, wrap) = Counter(0 to 3)
// 0, 1, 2, 3, 0, 1...

Counting downwards:

val (value, wrap) = Counter(3 to 0)
// 3, 2, 1, 0, 3, 2...

Counting upwards by twos:

val (value, wrap) = Counter(0 to 9 by 2)
// 0, 2, 4, 6, 8, 0, 2...

It also allows an optional condition attribute (similar to the existing API):

val enable = true.B
val (value, wrap) = Counter(0 to 3, enable)
// 0, 1, 2, 3, 0, 1...

I'm fairly new to Chisel, but I think that having the condition as the second parameter feels more consistent with the rest of the Chisel API.

I'm happy to scale this PR back if you think it's changing too much at once, but I'm keen to get some feedback on the overall goal. Thoughts?

Type of change: feature request

Impact: API addition (no impact on existing code)

Development Phase: proposal

Release Notes
Allow a counter to be instantiated using a Scala range

@CLAassistant
Copy link

CLAassistant commented Jul 15, 2020

CLA assistant check
All committers have signed the CLA.

@nullobject nullobject force-pushed the counter-range branch 2 times, most recently from 86e9a12 to d83f54e Compare July 15, 2020 09:45
@nullobject nullobject marked this pull request as ready for review July 15, 2020 09:50
@nullobject nullobject requested a review from a team as a code owner July 15, 2020 09:50
@nullobject nullobject changed the title Allow the Counter module to be instantiated with a Scala range Allow a counter to be instantiated using a Scala range Jul 15, 2020
Copy link
Contributor

@jackkoenig jackkoenig left a comment

Choose a reason for hiding this comment

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

This is super cool and I love being able to create Counters from Ranges, thanks!

A couple of issues I highlighted in a comment:

  1. I think we should keep the Verilog the same for the simple case 0 until power-of-2
  2. In at least that case, the width of the register seems to be 1 bigger than it needs to be

val wrap = value === r.last.U

when (wrap) {
value := r.start.U
Copy link
Member

Choose a reason for hiding this comment

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

I think the special case can be applied by sticking an if (!(isPow2(r.end) && isPow2(r.step))) here.

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 ended up using something very similar to that conditional, but had to also check that the range starts at zero.

@nullobject
Copy link
Contributor Author

@jackkoenig @aswaterman Please re-review when you get a chance.

I'm happy to keep tweaking this until you guys are happy 😸

Copy link
Contributor

@jackkoenig jackkoenig left a comment

Choose a reason for hiding this comment

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

I can confirm the code gen for 0 until power-of-2 case is resolve, thank you for that! This LGTM, some suggestions for improvement but they're not necessary for this PR. Thanks!

src/main/scala/chisel3/util/Counter.scala Show resolved Hide resolved
src/main/scala/chisel3/util/Counter.scala Show resolved Hide resolved
Comment on lines 102 to 107
def apply(r: Range, cond: Bool = true.B): (UInt, Bool) = {
val c = new Counter(r)
val wrap = WireInit(false.B)
when (cond) { wrap := c.inc() }
(c.value, wrap)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a minor API question, but the range API is a little inconsistent with the old API, if you want an enable its:

Counter(0 until 16, en)
// vs.
Counter(en, 16)

I won't contest that maybe it's the existing API that's awkward, but we probably ought to be consistent here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jackkoenig Thanks for your thoughts 🤔

Unfortunately, flipping the order of the params would make having default values for the enable param more awkward. IMHO, since you can't construct a counter without a range value (i.e. what good is a counter with an enable and no range?), it makes sense that this is the first parameter.

My vote would be to deprecate the old API. That way users could have:

Counter(10)
Counter(10, en)
Counter(0 to 10)
Counter(0 to 10, en)

I'm happy to open a new PR to discuss?

@jackkoenig
Copy link
Contributor

@chick or something with admin rights, can you check why the CI isn't reporting?

@jackkoenig
Copy link
Contributor

@nullobject the test failure may be a real one, the tests that failed use Counter

@nullobject
Copy link
Contributor Author

the test failure may be a real one, the tests that failed use Counter

@jackkoenig Fixed. It was due to a counter being instantiated with Counter(0) in the shift register test. This was creating an empty range.

I have update the legacy constructor to always create a non-empty range, and added an assertion to ensure that counters can never be instantiated with an empty range.

I don't think supporting empty ranges makes much sense, because how can you ever count zero elements?

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.

@aswaterman
Copy link
Member

I think updating the legacy constructor to support Counter(0) is fine. I think it makes sense that the range constructor should require a nonempty range since it represents a half-open interval.

@nullobject nullobject force-pushed the counter-range branch 2 times, most recently from 0fc6c93 to a70d3fa Compare July 29, 2020 10:17
nullobject and others added 3 commits July 30, 2020 09:20
Co-authored-by: Jack Koenig <jack.koenig3@gmail.com>
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.
@jackkoenig
Copy link
Contributor

@nullobject do you care how this is merged? Do you want it with a merge commit or is a squash-and-merge fine?

Also sorry for the delay, somehow I missed the last few emails about this PR.

@nullobject
Copy link
Contributor Author

@nullobject do you care how this is merged? Do you want it with a merge commit or is a squash-and-merge fine?

Also sorry for the delay, somehow I missed the last few emails about this PR.

@jackkoenig Not really, whatever you guys normally do.

Happy to squash it all into one commit if you prefer?

@jackkoenig
Copy link
Contributor

@nullobject since we require PRs to be up-to-date to merge, we usually usually let the trust bot @Mergify.io queue them up, merging master, then squash-and-merging. I'll get that started now. Thanks!

@jackkoenig jackkoenig added Feature New feature, will be included in release notes Please Merge Accepted PRs that are ready to be merged. Useful when waiting on CI. labels Jul 30, 2020
@jackkoenig jackkoenig added this to the 3.4.0 milestone Jul 30, 2020
@nullobject
Copy link
Contributor Author

@jackkoenig Fantastic. Thanks for persisting with my PR.

I'm happy to have made my first contribution to Chisel 🙌

@mergify mergify bot merged commit fca89f6 into chipsalliance:master Jul 30, 2020
@jackkoenig
Copy link
Contributor

@nullobject Thank you so much! It was a pleasure and an excellent contribution!

@nullobject nullobject deleted the counter-range branch July 30, 2020 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature New feature, will be included in release notes Please Merge Accepted PRs that are ready to be merged. Useful when waiting on CI.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants