-
Notifications
You must be signed in to change notification settings - Fork 151
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
Generate bool
accessors for single-bit fields
#84
Generate bool
accessors for single-bit fields
#84
Conversation
Oh, and as a minor but sweet side effect, this change makes documentation much easier to read. |
Thanks for the PR, @whitequark. I don't think I'm going to accept this PR as it is since it seems to throw away enumeratedValues information if the field has size 1 bit. This results in a loss of readability. Something like this: // nrf51.svd
timer0.mode.write(|w| w.mode().counter()); // or mode().timer() would become timer0.mode.write(|w| w.mode(false)); // false mode? if I'm reading this correctly. I'd be more willing if the enumeratedValues information was preserved. Also note that the code generation is designed such that adding enumeratedValues information to a SVD file and then running it again through svd2rust expands the previous API in a backward compatible way and I'd like to preserve that property. |
☔ The latest upstream changes (presumably c758eb7) made this pull request unmergeable. Please resolve the merge conflicts. |
I have completely missed this case, thanks.
I have not previously realized that this is a goal. I agree that it seems clearly desirable, and while it would not be a deal breaker for me personally, I can certainly understand if it is for you. As such, let's change the goals a bit. For this PR, I will, much more modestly, change the After that, I suggest rethinking the approach to nesting in the generated code a bit. The existing code generator makes the documentation very hard to use, particularly when many of your fields are single-bit, many of our other fields have no reserved bit patterns, and many of your registers only have one field (perhaps because all of the registers require 32-bit access even if the peripheral doesn't). In this case, writing code against the generated API becomes an endless maze of indirection that serves no purpose. It is, indeed, nontrivial to rework the code so that later you can add more detail (fields and enumerated values). As a compromise between backwards compatibility and ergonomics, I suggest pulling the I hope that this will cut down the majority of noise in the documentation, and make it much more convenient for casual use. |
@japaric I have now updated this PR. An alternative approach to changing the name depending on the width would have been to call the accessor |
We definitely need something like this in order to improve ergonomics of the common case. |
Thanks for the changes @whitequark. This now looks good me. Could you update the crate level documentation (src/lib.rs - that's what shows up in docs.rs) to reflect the changes? re: documentation being hard to navigate. I agree. When I designed the API I was assuming autocompletion would always be used to write code. So in my mind using the API should have looked like this: peripheral.register.write(|w| w._)
|fieldA - enables / disables the timer
|fieldB - oneshot or contiguous
|fieldC - enables / disables interrupts
|(..)
// then
w.fieldA._
| enabled() - enables the timer
| disabled() - disables the timer In practice, racer doesn't work that well when closures or type inference is involved. |
@japaric done |
@whitequark after looking at this again (after properly sleeping), I'm not fully convinced about all the changes here.
-//! i2c1.cr2.write(|w| unsafe { w.sadd0().bits(1).sadd1().bits(0b0011110) });
+//! i2c1.cr2.write(|w| unsafe { w.sadd0().bit(true).sadd1().bits(0b0011110) });
I do agree that I think this would be even clearer if it used methods:
Would love to hear others' opinions |
"Low" and "high" aren't applicable to bits, only signals, and in fact this would add ambiguity because a logical one could be represented by either low or high signal level. Regarding The change of
Somewhere in the process I've added the equivalent of this change to my SVD file generator and, it seems, then never noticed removing it when refactoring the svd2rust patch. I of course still think it's a major improvement in ergonomics and will add it back. |
|
Updated |
Instead of `bits()`, single-bit fields now have the `bit()` accessor, returning `bool`. Writable fields also have the `set()` and `clear()` mutators. Single-bit fields are also marked as safe even if they are not covered by an explicit writeConstraint. This makes using such fields significantly more ergonomic, as previously they would have been u8s, leading to unnecessary conversion and silent truncation in downstream code.
Thanks for updating the PR @whitequark. I stil don't think that we should (over)use // READ
// this PR: if the bit is ... what?
if peripheral.register.read().bitfield().bit() { .. }
// better: if the bit is one
if peripheral.register.read().bitfield().bit().is_one() { .. }
// or
if peripheral.register.read().bitfield().bit() == Bit::One { .. }
// with enumerated values (bitfield = mode)
if peripheral.register.read().mode().bit().is_one() { .. }
if peripheral.register.read().mode().bit() == Bit::One { .. }
if peripheral.register.read().mode().is_output() { .. }
// or
if peripheral.register.read().mode() == Mode::Output { .. }
// WRITE
// no enumeratedValues
peripheral.register.write(|w| w.bitfiled().set()); // or clear()
peripheral.register.write(|w| w.bitfiled().bit(Bit::One));
// with enumeratedValues
peripheral.register.write(|w| w.mode().set()); // or clear()
peripheral.register.write(|w| w.mode().bit(Bit::One));
peripheral.register.write(|w| w.mode().output()); // or input()
peripheral.register.write(|w| w.mode().variant(Mode::Output)); |
I think almost every example above is non-representative as they are missing the key motivation for having the #[derive(Ord)]
enum Mode { Active, LowPower, Sleep };
fn enable_clocks_for(mode: Mode) {
clocks.uartclk.r0().bit(mode <= Active);
clocks.uartclk.r1().bit(mode <= LowPower);
//etc
} My point is that the fact that it is a bit is an implementation detail, logically, it is a toggle. There's really no reason to newtype bool and then add useless conversion everywhere. Can you show any example where your bool newtype would prevent a bug from being added? That said I think adding |
I would really discourage: I agree that in the case of |
IMO, that code looks like it has been optimized for writing rather than for reading. I would use OK. It seems fine to just use |
Perhaps the choice of the example was not the best. But nevertheless I disagree on the underlying point, having to insert a match/conditional every time a bit could be either set or cleared indicating an on/off state would be completely unnecessary boilerplate. svd2rust is supposed to reduce that not add it. |
The pub fn bit(self, value: bool) -> &'a mut W {
const MASK: bool = true;
const OFFSET: u8 = 19;
self.w.bits &= !((MASK as u32) << OFFSET);
self.w.bits |= ((value & MASK) as u32) << OFFSET;
self.w
} @homunkulus r+ |
📌 Commit 3405452 has been approved by |
Generate `bool` accessors for single-bit fields This makes using such fields significantly more ergonomic, as previously they would have been `u8`s, leading to unnecessary conversion and silent truncation in downstream code. The `bool` writers are never unsafe because it is meaningless to have a writable field that only admits one (or worse, zero) valid values.
☀️ Test successful - status-appveyor, status-travis |
This makes using such fields significantly more ergonomic, as
previously they would have been
u8
s, leading to unnecessaryconversion and silent truncation in downstream code.
The
bool
writers are never unsafe because it is meaninglessto have a writable field that only admits one (or worse, zero)
valid values.