[del] Add local copy of tock-registers with u128 impl of IntLike

This commit is contained in:
Berkus Decker 2020-11-24 23:03:10 +02:00
parent b025fb6dd3
commit b2c99f52c7
9 changed files with 1054 additions and 128 deletions

2
Cargo.lock generated
View File

@ -113,8 +113,6 @@ dependencies = [
[[package]]
name = "tock-registers"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f521a79accce68c417c9c77ce22108056b626126da1932f7e2e9b5bbffee0cea"
[[package]]
name = "unicode-xid"

View File

@ -1,6 +1,7 @@
[workspace]
members = [
"nucleus"
"nucleus",
"crates/tock-registers"
]
[profile.dev]
@ -18,3 +19,6 @@ lto = true
[profile.test]
opt-level = 's'
debug = true
[patch.crates-io]
tock-registers = { path = 'crates/tock-registers' }

View File

@ -1,5 +1,48 @@
# Changelog
## master
## v0.6
- #2095: Fix syntax errors and inconsistencies in documentation
- #2071: Clarify bit widths in documentation examples
- #2015: Use UnsafeCell in registers (see issue #2005)
- #1939: Make the Field::mask and FieldValue::mask fields private
- #1823: Allow large unsigned values as bitmasks + add bitmask! helper macro
- #1554: Allow lifetime parameters for `register_structs! { Foo<'a> { ..`
- #1661: Add `Aliased` register type for MMIO with differing R/W behavior
## v0.5
- #1510
- Register visibility granularity: don't automatically make everything
`pub`, rather give creation macro callers visbility control.
- #1489
- Make `register_structs!` unit test generation opt-out, so that
`custom-test-frameworks` environments can disable them.
- #1481
- Add `#[derive(Copy, Clone)]` to InMemoryRegister.
- #1428
- Implement `mask()` for `FieldValue<u16>` which seems to have been
skipped at some point.
- Implement `read()` for `FieldValue` so that individual fields
can be extracted from a register `FieldValue` representation.
- #1461: Update `register_structs` macro to support flexible visibility of each
struct and each field. Also revert to private structs by default.
## v0.4.1
- #1458: Update struct macro to create `pub` structs
## v0.4
- #1368: Remove `new()` and add `InMemoryRegister`
- #1410: Add new macro for generating structs
## v0.3
- #1243: Update to Rust 2018 (nightly)

View File

@ -1,6 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "tock-registers"
version = "0.3.0"

View File

@ -1,6 +1,6 @@
[package]
name = "tock-registers"
version = "0.3.0"
version = "0.6.0"
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
description = "Memory-Mapped I/O and register interface developed for Tock."
homepage = "https://www.tockos.org/"
@ -13,3 +13,6 @@ edition = "2018"
[badges]
travis-ci = { repository = "tock/tock", branch = "master" }
[features]
no_std_unit_tests = []

View File

@ -9,12 +9,58 @@ The crate provides three types for working with memory mapped registers:
`ReadWrite`, `ReadOnly`, and `WriteOnly`, providing read-write, read-only, and
write-only functionality, respectively.
Defining the registers is similar to the C-style approach, where each register
is a field in a packed struct:
Defining the registers is done with the `register_structs` macro, which expects
for each register an offset, a field name, and a type. Registers must be
declared in increasing order of offsets and contiguously. Gaps when defining the
registers must be explicitly annotated with an offset and gap identifier (by
convention using a field named `_reservedN`), but without a type. The macro will
then automatically take care of calculating the gap size and inserting a
suitable filler struct. The end of the struct is marked with its size and the
`@END` keyword, effectively pointing to the offset immediately past the list of
registers.
```rust
use tock_registers::registers::{ReadOnly, ReadWrite, WriteOnly};
register_structs! {
Registers {
// Control register: read-write
// The 'Control' parameter constrains this register to only use fields from
// a certain group (defined below in the bitfields section).
(0x000 => cr: ReadWrite<u8, Control::Register>),
// Status register: read-only
(0x001 => s: ReadOnly<u8, Status::Register>),
// Registers can be bytes, halfwords, or words:
// Note that the second type parameter can be omitted, meaning that there
// are no bitfields defined for these registers.
(0x002 => byte0: ReadWrite<u8>),
(0x003 => byte1: ReadWrite<u8>),
(0x004 => short: ReadWrite<u16>),
// Empty space between registers must be marked with a padding field,
// declared as follows. The length of this padding is automatically
// computed by the macro.
(0x006 => _reserved),
(0x008 => word: ReadWrite<u32>),
// The type for a register can be anything. Conveniently, you can use an
// array when there are a bunch of similar registers.
(0x00C => array: [ReadWrite<u32>; 4]),
(0x01C => ... ),
// Etc.
// The end of the struct is marked as follows.
(0x100 => @END),
}
}
```
This generates a C-style struct of the following form.
```rust
#[repr(C)]
struct Registers {
// Control register: read-write
@ -31,21 +77,97 @@ struct Registers {
byte0: ReadWrite<u8>,
byte1: ReadWrite<u8>,
short: ReadWrite<u16>,
word: ReadWrite<u32>
// The padding length was automatically computed as 0x008 - 0x006.
_reserved: [u8; 2],
word: ReadWrite<u32>,
// Arrays are expanded as-is, like any other type.
array: [ReadWrite<u32>; 4],
// Etc.
}
```
By default, `std` unit tests for the struct are generated as well (that is,
tests attributed with `#[test]`). The unit tests make sure that the offsets and
padding are consistent with the actual fields in the struct, and that alignment
is correct.
Since those tests would break compilation in `custom-test-frameworks`
environments, it is possible to opt out of the test generation. To do so, add
the following cargo feature:
```toml
[dependencies.tock-registers]
version = "0.4.x"
features = ["no_std_unit_tests"]
```
WARNING: For now, the **unit tests checking offsets and alignments are not yet
run** on `make ci-travis`. This means that leaving an unintentional gap between
registers will **not** be caught. Instead, the `register_structs` macro will
generate a struct with invalid offsets without warning. Please follow the
discussion on https://github.com/tock/tock/pull/1393.
For example, the following call to the macro:
```rust
register_structs! {
Registers {
(0x000 => foo: ReadOnly<u8>),
(0x008 => bar: ReadOnly<u8>),
(0x009 => @END),
}
}
```
will generate the following struct, even though there is an unintentional gap of
4 bytes between addresses `0x004` (the end of register `foo`) and `0x008` (the
intended beginning of register `bar`).
```rust
#[repr(C)]
struct Registers {
foo: ReadOnly<u32>,
bar: ReadOnly<u32>,
}
```
By default, the visibility of the generated structs and fields is private. You
can make them public using the `pub` keyword, just before the struct name or the
field identifier.
For example, the following call to the macro:
```rust
register_structs! {
pub Registers {
(0x000 => foo: ReadOnly<u32>),
(0x004 => pub bar: ReadOnly<u32>),
(0x008 => @END),
}
}
```
will generate the following struct.
```rust
#[repr(C)]
pub struct Registers {
foo: ReadOnly<u32>,
pub bar: ReadOnly<u32>,
}
```
## Defining bitfields
Bitfields are defined through the `register_bitfields!` macro:
```rust
register_bitfields! [
// First parameter is the register width for the bitfields. Can be u8, u16,
// u32, or u64.
u8,
// First parameter is the register width. Can be u8, u16, u32, or u64.
u32,
// Each subsequent parameter is a register abbreviation, its descriptive
// name, and its associated bitfields.
@ -56,7 +178,7 @@ register_bitfields! [
// name OFFSET(shift) NUMBITS(num) [ /* optional values */ ]
// This is a two-bit field which includes bits 4 and 5
RANGE OFFSET(4) NUMBITS(3) [
RANGE OFFSET(4) NUMBITS(2) [
// Each of these defines a name for a value that the bitfield can be
// written with or matched against. Note that this set is not exclusive--
// the field can still be written with arbitrary constants.
@ -103,8 +225,8 @@ register_bitfields! [
## Register Interface Summary
There are three types provided by the register interface: `ReadOnly`,
`WriteOnly`, and `ReadWrite`. They provide the following functions:
There are four types provided by the register interface: `ReadOnly`,
`WriteOnly`, `ReadWrite`, and `Aliased`. They provide the following functions:
```rust
ReadOnly<T: IntLike, R: RegisterLongName = ()>
@ -120,8 +242,6 @@ WriteOnly<T: IntLike, R: RegisterLongName = ()>
.set(value: T) // Set the raw register value
.write(value: FieldValue<T, R>) // Write the value of one or more fields,
// overwriting other fields to zero
ReadWrite<T: IntLike, R: RegisterLongName = ()>
.get() -> T // Get the raw register value
.set(value: T) // Set the raw register value
@ -139,8 +259,22 @@ ReadWrite<T: IntLike, R: RegisterLongName = ()>
.matches_all(value: FieldValue<T, R>) -> bool // Check if all specified parts of a field match
.extract() -> LocalRegisterCopy<T, R> // Make local copy of register
Aliased<T: IntLike, R: RegisterLongName = (), W: RegisterLongName = ()>
.get() -> T // Get the raw register value
.set(value: T) // Set the raw register value
.read(field: Field<T, R>) -> T // Read the value of the given field
.read_as_enum<E>(field: Field<T, R>) -> Option<E> // Read value of the given field as a enum member
.write(value: FieldValue<T, W>) // Write the value of one or more fields,
// overwriting other fields to zero
.is_set(field: Field<T, R>) -> bool // Check if one or more bits in a field are set
.matches_any(value: FieldValue<T, R>) -> bool // Check if any specified parts of a field match
.matches_all(value: FieldValue<T, R>) -> bool // Check if all specified parts of a field match
.extract() -> LocalRegisterCopy<T, R> // Make local copy of register
```
The `Aliased` type represents cases where read-only and write-only registers,
with different meanings, are aliased to the same memory location.
The first type parameter (the `IntLike` type) is `u8`, `u16`, `u32`, or `u64`.
## Example: Using registers and bitfields
@ -265,6 +399,23 @@ let local = registers.cr.extract();
// Now all the functions for a ReadOnly register work.
let txcomplete: bool = local.is_set(Status::TXCOMPLETE);
// -----------------------------------------------------------------------------
// In-Memory Registers
// -----------------------------------------------------------------------------
// In some cases, code may want to edit a memory location with all of the
// register features described above, but the actual memory location is not a
// fixed MMIO register but instead an arbitrary location in memory. If this
// location is then shared with the hardware (i.e. via DMA) then the code
// must do volatile reads and writes since the value may change without the
// software knowing. To support this, the library includes an `InMemoryRegister`
// type.
let control: InMemoryRegister<u32, Control::Register> = InMemoryRegister::new(0)
control.write(Contol::BYTE_COUNT.val(0) +
Contol::ENABLE::Yes +
Contol::LENGTH.val(10));
```
Note that `modify` performs exactly one volatile load and one volatile store,
@ -288,13 +439,13 @@ with a register of the type `ReadWrite<_, Control>` (or `ReadOnly/WriteOnly`,
etc). For instance, if we have the bitfields and registers as defined above,
```rust
// This line compiles, because CR and registers.cr are both associated with the
// Control group of bitfields.
// This line compiles, because registers.cr is associated with the Control group
// of bitfields.
registers.cr.modify(Control::RANGE.val(1));
// This line will not compile, because CR is associated with the Control group,
// while registers.s is associated with the Status group.
registers.s.modify(Control::RANGE.val(1));
// This line will not compile, because registers.s is associated with the Status
// group, not the Control group.
let range = registers.s.read(Control::RANGE);
```
## Naming conventions

View File

@ -5,7 +5,5 @@
#![feature(const_fn)]
#![no_std]
#[macro_use]
pub mod macros;
pub mod registers;

View File

@ -1,13 +1,21 @@
//! Macros for cleanly defining peripheral registers.
/// Helper macro for computing bitmask of variable number of bits
#[macro_export]
macro_rules! bitmask {
($numbits:expr) => {
(1 << ($numbits - 1)) + ((1 << ($numbits - 1)) - 1)
};
}
/// Helper macro for defining register fields.
#[macro_export]
macro_rules! register_bitmasks {
{
// BITFIELD_NAME OFFSET(x)
$(#[$outer:meta])*
$valtype:ty, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr)),+
$valtype:ident, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr)),+ $(,)?
]
} => {
$(#[$outer])*
@ -17,8 +25,8 @@ macro_rules! register_bitmasks {
// BITFIELD_NAME OFFSET
// All fields are 1 bit
$(#[$outer:meta])*
$valtype:ty, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident $offset:expr ),+
$valtype:ident, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident $offset:expr ),+ $(,)?
]
} => {
$(#[$outer])*
@ -28,8 +36,8 @@ macro_rules! register_bitmasks {
{
// BITFIELD_NAME OFFSET(x) NUMBITS(y)
$(#[$outer:meta])*
$valtype:ty, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr) NUMBITS($numbits:expr) ),+
$valtype:ident, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr) NUMBITS($numbits:expr) ),+ $(,)?
]
} => {
$(#[$outer])*
@ -39,9 +47,9 @@ macro_rules! register_bitmasks {
{
// BITFIELD_NAME OFFSET(x) NUMBITS(y) []
$(#[$outer:meta])*
$valtype:ty, $reg_desc:ident, [
$valtype:ident, $reg_desc:ident, [
$( $(#[$inner:meta])* $field:ident OFFSET($offset:expr) NUMBITS($numbits:expr)
$values:tt ),+
$values:tt ),+ $(,)?
]
} => {
$(#[$outer])*
@ -49,14 +57,16 @@ macro_rules! register_bitmasks {
$values); )*
};
{
$valtype:ty, $reg_desc:ident, $(#[$outer:meta])* $field:ident,
$valtype:ident, $reg_desc:ident, $(#[$outer:meta])* $field:ident,
$offset:expr, $numbits:expr,
[$( $(#[$inner:meta])* $valname:ident = $value:expr ),*]
} => {
[$( $(#[$inner:meta])* $valname:ident = $value:expr ),+ $(,)?]
} => { // this match arm is duplicated below with an allowance for 0 elements in the valname -> value array,
// to seperately support the case of zero-variant enums not supporting non-default
// representations.
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const $field: Field<$valtype, $reg_desc> =
Field::<$valtype, $reg_desc>::new((1<<($numbits-1))+((1<<($numbits-1))-1), $offset);
Field::<$valtype, $reg_desc>::new($crate::bitmask!($numbits), $offset);
#[allow(non_snake_case)]
#[allow(unused)]
@ -71,24 +81,25 @@ macro_rules! register_bitmasks {
#[allow(unused)]
$(#[$inner])*
pub const $valname: FieldValue<$valtype, $reg_desc> =
FieldValue::<$valtype, $reg_desc>::new((1<<($numbits-1))+((1<<($numbits-1))-1),
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
$offset, $value);
)*
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const SET: FieldValue<$valtype, $reg_desc> =
FieldValue::<$valtype, $reg_desc>::new((1<<($numbits-1))+((1<<($numbits-1))-1),
$offset, (1<<($numbits-1))+((1<<($numbits-1))-1));
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
$offset, $crate::bitmask!($numbits));
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const CLEAR: FieldValue<$valtype, $reg_desc> =
FieldValue::<$valtype, $reg_desc>::new((1<<($numbits-1))+((1<<($numbits-1))-1),
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
$offset, 0);
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[repr($valtype)] // so that values larger than isize::MAX can be stored
$(#[$outer])*
pub enum Value {
$(
@ -113,18 +124,67 @@ macro_rules! register_bitmasks {
}
}
};
{
$valtype:ident, $reg_desc:ident, $(#[$outer:meta])* $field:ident,
$offset:expr, $numbits:expr,
[]
} => { //same pattern as previous match arm, for 0 elements in array. Removes code associated with array.
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const $field: Field<$valtype, $reg_desc> =
Field::<$valtype, $reg_desc>::new($crate::bitmask!($numbits), $offset);
#[allow(non_snake_case)]
#[allow(unused)]
$(#[$outer])*
pub mod $field {
#[allow(unused_imports)]
use $crate::registers::{FieldValue, TryFromValue};
use super::$reg_desc;
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const SET: FieldValue<$valtype, $reg_desc> =
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
$offset, $crate::bitmask!($numbits));
#[allow(non_upper_case_globals)]
#[allow(unused)]
pub const CLEAR: FieldValue<$valtype, $reg_desc> =
FieldValue::<$valtype, $reg_desc>::new($crate::bitmask!($numbits),
$offset, 0);
#[allow(dead_code)]
#[allow(non_camel_case_types)]
$(#[$outer])*
pub enum Value {}
impl TryFromValue<$valtype> for Value {
type EnumType = Value;
fn try_from(_v: $valtype) -> Option<Self::EnumType> {
Option::None
}
}
}
};
}
/// Define register types and fields.
#[macro_export]
macro_rules! register_bitfields {
{
$valtype:ty, $( $(#[$inner:meta])* $reg:ident $fields:tt ),*
$valtype:ident, $( $(#[$inner:meta])* $vis:vis $reg:ident $fields:tt ),* $(,)?
} => {
$(
#[allow(non_snake_case)]
$(#[$inner])*
pub mod $reg {
$vis mod $reg {
// Visibility note: This is left always `pub` as it is not
// meaningful to restrict access to the `Register` element of
// the register module if the module itself is in scope
//
// (if you can access $reg, you can access $reg::Register)
#[derive(Clone, Copy)]
pub struct Register;
impl $crate::registers::RegisterLongName for Register {}
@ -136,3 +196,242 @@ macro_rules! register_bitfields {
)*
}
}
#[macro_export]
macro_rules! register_fields {
// Macro entry point.
(@root $(#[$attr_struct:meta])* $vis_struct:vis $name:ident $(<$life:lifetime>)? { $($input:tt)* } ) => {
$crate::register_fields!(
@munch (
$($input)*
) -> {
$vis_struct struct $(#[$attr_struct])* $name $(<$life>)?
}
);
};
// Print the struct once all fields have been munched.
(@munch
(
$(#[$attr_end:meta])*
($offset:expr => @END),
)
-> {$vis_struct:vis struct $(#[$attr_struct:meta])* $name:ident $(<$life:lifetime>)? $(
$(#[$attr:meta])*
($vis:vis $id:ident: $ty:ty)
)*}
) => {
$(#[$attr_struct])*
#[repr(C)]
$vis_struct struct $name $(<$life>)? {
$(
$(#[$attr])*
$vis $id: $ty
),*
}
};
// Munch field.
(@munch
(
$(#[$attr:meta])*
($offset_start:expr => $vis:vis $field:ident: $ty:ty),
$($after:tt)*
)
-> {$($output:tt)*}
) => {
$crate::register_fields!(
@munch (
$($after)*
) -> {
$($output)*
$(#[$attr])*
($vis $field: $ty)
}
);
};
// Munch padding.
(@munch
(
$(#[$attr:meta])*
($offset_start:expr => $padding:ident),
$(#[$attr_next:meta])*
($offset_end:expr => $($next:tt)*),
$($after:tt)*
)
-> {$($output:tt)*}
) => {
$crate::register_fields!(
@munch (
$(#[$attr_next])*
($offset_end => $($next)*),
$($after)*
) -> {
$($output)*
$(#[$attr])*
($padding: [u8; $offset_end - $offset_start])
}
);
};
}
#[macro_export]
macro_rules! test_fields {
// Macro entry point.
(@root $struct:ident $(<$life:lifetime>)? { $($input:tt)* } ) => {
$crate::test_fields!(@munch $struct $(<$life>)? sum ($($input)*) -> {});
};
// Print the tests once all fields have been munched.
// We wrap the tests in a "detail" function that potentially takes a lifetime parameter, so that
// the lifetime is declared inside it - therefore all types using the lifetime are well-defined.
(@munch $struct:ident $(<$life:lifetime>)? $sum:ident
(
$(#[$attr_end:meta])*
($size:expr => @END),
)
-> {$($stmts:block)*}
) => {
{
fn detail $(<$life>)? ()
{
let mut $sum: usize = 0;
$($stmts)*
let size = core::mem::size_of::<$struct $(<$life>)?>();
assert!(
size == $size,
"Invalid size for struct {} (expected {:#X} but was {:#X})",
stringify!($struct),
$size,
size
);
}
detail();
}
};
// Munch field.
(@munch $struct:ident $(<$life:lifetime>)? $sum:ident
(
$(#[$attr:meta])*
($offset_start:expr => $vis:vis $field:ident: $ty:ty),
$(#[$attr_next:meta])*
($offset_end:expr => $($next:tt)*),
$($after:tt)*
)
-> {$($output:block)*}
) => {
$crate::test_fields!(
@munch $struct $(<$life>)? $sum (
$(#[$attr_next])*
($offset_end => $($next)*),
$($after)*
) -> {
$($output)*
{
assert!(
$sum == $offset_start,
"Invalid start offset for field {} (expected {:#X} but was {:#X})",
stringify!($field),
$offset_start,
$sum
);
let align = core::mem::align_of::<$ty>();
assert!(
$sum & (align - 1) == 0,
"Invalid alignment for field {} (expected alignment of {:#X} but offset was {:#X})",
stringify!($field),
align,
$sum
);
$sum += core::mem::size_of::<$ty>();
assert!(
$sum == $offset_end,
"Invalid end offset for field {} (expected {:#X} but was {:#X})",
stringify!($field),
$offset_end,
$sum
);
}
}
);
};
// Munch padding.
(@munch $struct:ident $(<$life:lifetime>)? $sum:ident
(
$(#[$attr:meta])*
($offset_start:expr => $padding:ident),
$(#[$attr_next:meta])*
($offset_end:expr => $($next:tt)*),
$($after:tt)*
)
-> {$($output:block)*}
) => {
$crate::test_fields!(
@munch $struct $(<$life>)? $sum (
$(#[$attr_next])*
($offset_end => $($next)*),
$($after)*
) -> {
$($output)*
{
assert!(
$sum == $offset_start,
"Invalid start offset for padding {} (expected {:#X} but was {:#X})",
stringify!($padding),
$offset_start,
$sum
);
$sum = $offset_end;
}
}
);
};
}
#[cfg(not(feature = "no_std_unit_tests"))]
#[macro_export]
macro_rules! register_structs {
{
$(
$(#[$attr:meta])*
$vis_struct:vis $name:ident $(<$life:lifetime>)? {
$( $fields:tt )*
}
),*
} => {
$( $crate::register_fields!(@root $(#[$attr])* $vis_struct $name $(<$life>)? { $($fields)* } ); )*
#[cfg(test)]
mod test_register_structs {
$(
#[allow(non_snake_case)]
mod $name {
use super::super::*;
#[test]
fn test_offsets() {
$crate::test_fields!(@root $name $(<$life>)? { $($fields)* } )
}
}
)*
}
};
}
#[cfg(feature = "no_std_unit_tests")]
#[macro_export]
macro_rules! register_structs {
{
$(
$(#[$attr:meta])*
$vis_struct:vis $name:ident $(<$life:lifetime>)? {
$( $fields:tt )*
}
),*
} => {
$( $crate::register_fields!(@root $(#[$attr])* $vis_struct $name $(<$life>)? { $($fields)* } ); )*
};
}

View File

@ -4,11 +4,10 @@
//! registers and bitfields.
//!
//! ```rust
//! # #[macro_use]
//! # extern crate tock_registers;
//! # fn main() {}
//!
//! use tock_registers::registers::{ReadOnly, ReadWrite};
//! use tock_registers::register_bitfields;
//!
//! // Register maps are specified like this:
//! #[repr(C)]
@ -60,14 +59,16 @@
#![allow(clippy::suspicious_op_assign_impl)]
#![allow(clippy::suspicious_arithmetic_impl)]
use core::cell::UnsafeCell;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Add, AddAssign, BitAnd, BitOr, Not, Shl, Shr};
use core::ops::{Add, AddAssign, BitAnd, BitOr, BitOrAssign, Not, Shl, Shr};
/// IntLike properties needed to read/write/modify a register.
pub trait IntLike:
BitAnd<Output = Self>
+ BitOr<Output = Self>
+ BitOrAssign
+ Not<Output = Self>
+ Eq
+ Shr<usize, Output = Self>
@ -93,12 +94,16 @@ impl IntLike for u32 {
0
}
}
impl IntLike for u64 {
fn zero() -> Self {
0
}
}
impl IntLike for u128 {
fn zero() -> Self {
0
}
}
/// Descriptive name for each register.
pub trait RegisterLongName {}
@ -114,155 +119,230 @@ pub trait TryFromValue<V> {
}
/// Read/Write registers.
// To successfully alias this structure onto hardware registers in memory, this
// struct must be exactly the size of the `T`.
#[repr(transparent)]
pub struct ReadWrite<T: IntLike, R: RegisterLongName = ()> {
value: T,
value: UnsafeCell<T>,
associated_register: PhantomData<R>,
}
/// Read-only registers.
// To successfully alias this structure onto hardware registers in memory, this
// struct must be exactly the size of the `T`.
#[repr(transparent)]
pub struct ReadOnly<T: IntLike, R: RegisterLongName = ()> {
value: T,
associated_register: PhantomData<R>,
}
/// Write-only registers.
// To successfully alias this structure onto hardware registers in memory, this
// struct must be exactly the size of the `T`.
#[repr(transparent)]
pub struct WriteOnly<T: IntLike, R: RegisterLongName = ()> {
value: T,
value: UnsafeCell<T>,
associated_register: PhantomData<R>,
}
/// Read-only and write-only registers aliased to the same address.
///
/// Unlike the `ReadWrite` register, this represents a register which has different meanings based
/// on if it is written or read. This might be found on a device where control and status
/// registers are accessed via the same memory address via writes and reads, respectively.
// To successfully alias this structure onto hardware registers in memory, this
// struct must be exactly the size of the `T`.
#[repr(transparent)]
pub struct Aliased<T: IntLike, R: RegisterLongName = (), W: RegisterLongName = ()> {
value: UnsafeCell<T>,
associated_register: PhantomData<(R, W)>,
}
impl<T: IntLike, R: RegisterLongName> ReadWrite<T, R> {
pub const fn new(value: T) -> Self {
ReadWrite {
value: value,
associated_register: PhantomData,
}
}
#[inline]
/// Get the raw register value
pub fn get(&self) -> T {
unsafe { ::core::ptr::read_volatile(&self.value) }
unsafe { ::core::ptr::read_volatile(self.value.get()) }
}
#[inline]
/// Set the raw register value
pub fn set(&self, value: T) {
unsafe { ::core::ptr::write_volatile(&self.value as *const T as *mut T, value) }
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
}
#[inline]
/// Read the value of the given field
pub fn read(&self, field: Field<T, R>) -> T {
(self.get() & (field.mask << field.shift)) >> field.shift
field.read(self.get())
}
#[inline]
/// Read value of the given field as an enum member
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
let val: T = self.read(field);
E::try_from(val)
field.read_as_enum(self.get())
}
#[inline]
/// Make a local copy of the register
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
LocalRegisterCopy::new(self.get())
}
#[inline]
/// Write the value of one or more fields, overwriting the other fields with zero
pub fn write(&self, field: FieldValue<T, R>) {
self.set(field.value);
}
#[inline]
/// Write the value of one or more fields, leaving the other fields unchanged
pub fn modify(&self, field: FieldValue<T, R>) {
let reg: T = self.get();
self.set((reg & !field.mask) | field.value);
self.set(field.modify(self.get()));
}
#[inline]
/// Write the value of one or more fields, maintaining the value of unchanged fields via a
/// provided original value, rather than a register read.
pub fn modify_no_read(&self, original: LocalRegisterCopy<T, R>, field: FieldValue<T, R>) {
self.set((original.get() & !field.mask) | field.value);
self.set(field.modify(original.get()));
}
#[inline]
/// Check if one or more bits in a field are set
pub fn is_set(&self, field: Field<T, R>) -> bool {
self.read(field) != T::zero()
field.is_set(self.get())
}
#[inline]
/// Check if any specified parts of a field match
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
self.get() & field.mask != T::zero()
field.matches_any(self.get())
}
#[inline]
/// Check if all specified parts of a field match
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
self.get() & field.mask == field.value
field.matches_all(self.get())
}
}
impl<T: IntLike, R: RegisterLongName> ReadOnly<T, R> {
pub const fn new(value: T) -> Self {
ReadOnly {
value: value,
associated_register: PhantomData,
}
}
#[inline]
/// Get the raw register value
pub fn get(&self) -> T {
unsafe { ::core::ptr::read_volatile(&self.value) }
}
#[inline]
/// Read the value of the given field
pub fn read(&self, field: Field<T, R>) -> T {
(self.get() & (field.mask << field.shift)) >> field.shift
field.read(self.get())
}
#[inline]
/// Read value of the given field as an enum member
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
let val: T = self.read(field);
E::try_from(val)
field.read_as_enum(self.get())
}
#[inline]
/// Make a local copy of the register
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
LocalRegisterCopy::new(self.get())
}
#[inline]
/// Check if one or more bits in a field are set
pub fn is_set(&self, field: Field<T, R>) -> bool {
self.read(field) != T::zero()
field.is_set(self.get())
}
#[inline]
/// Check if any specified parts of a field match
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
self.get() & field.mask != T::zero()
field.matches_any(self.get())
}
#[inline]
/// Check if all specified parts of a field match
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
self.get() & field.mask == field.value
field.matches_all(self.get())
}
}
impl<T: IntLike, R: RegisterLongName> WriteOnly<T, R> {
pub const fn new(value: T) -> Self {
WriteOnly {
value: value,
associated_register: PhantomData,
}
}
#[inline]
/// Set the raw register value
pub fn set(&self, value: T) {
unsafe { ::core::ptr::write_volatile(&self.value as *const T as *mut T, value) }
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
}
#[inline]
/// Write the value of one or more fields, overwriting the other fields with zero
pub fn write(&self, field: FieldValue<T, R>) {
self.set(field.value);
}
}
impl<T: IntLike, R: RegisterLongName, W: RegisterLongName> Aliased<T, R, W> {
#[inline]
/// Get the raw register value
pub fn get(&self) -> T {
unsafe { ::core::ptr::read_volatile(self.value.get()) }
}
#[inline]
/// Set the raw register value
pub fn set(&self, value: T) {
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
}
#[inline]
/// Read the value of the given field
pub fn read(&self, field: Field<T, R>) -> T {
field.read(self.get())
}
#[inline]
/// Read value of the given field as an enum member
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
field.read_as_enum(self.get())
}
#[inline]
/// Make a local copy of the register
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
LocalRegisterCopy::new(self.get())
}
#[inline]
/// Write the value of one or more fields, overwriting the other fields with zero
pub fn write(&self, field: FieldValue<T, W>) {
self.set(field.value);
}
#[inline]
/// Check if one or more bits in a field are set
pub fn is_set(&self, field: Field<T, R>) -> bool {
field.is_set(self.get())
}
#[inline]
/// Check if any specified parts of a field match
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
field.matches_any(self.get())
}
#[inline]
/// Check if all specified parts of a field match
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
field.matches_all(self.get())
}
}
/// A read-only copy register contents
///
/// This behaves very similarly to a read-only register, but instead of doing a
/// volatile read to MMIO to get the value for each function call, a copy of the
/// register contents are stored locally in memory. This allows a peripheral
@ -291,29 +371,27 @@ impl<T: IntLike, R: RegisterLongName> LocalRegisterCopy<T, R> {
#[inline]
pub fn read(&self, field: Field<T, R>) -> T {
(self.value & (field.mask << field.shift)) >> field.shift
field.read(self.get())
}
#[inline]
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
let val: T = self.read(field);
E::try_from(val)
field.read_as_enum(self.get())
}
#[inline]
pub fn is_set(&self, field: Field<T, R>) -> bool {
self.read(field) != T::zero()
field.is_set(self.get())
}
#[inline]
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
self.value & field.mask != T::zero()
field.matches_any(self.get())
}
#[inline]
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
self.value & field.mask == field.value
field.matches_all(self.get())
}
/// Do a bitwise AND operation of the stored value and the passed in value
@ -354,14 +432,106 @@ impl<R: RegisterLongName> From<LocalRegisterCopy<u64, R>> for u64 {
}
}
/// In memory volatile register.
// To successfully alias this structure onto hardware registers in memory, this
// struct must be exactly the size of the `T`.
#[repr(transparent)]
pub struct InMemoryRegister<T: IntLike, R: RegisterLongName = ()> {
value: UnsafeCell<T>,
associated_register: PhantomData<R>,
}
impl<T: IntLike, R: RegisterLongName> InMemoryRegister<T, R> {
pub const fn new(value: T) -> Self {
InMemoryRegister {
value: UnsafeCell::new(value),
associated_register: PhantomData,
}
}
#[inline]
pub fn get(&self) -> T {
unsafe { ::core::ptr::read_volatile(self.value.get()) }
}
#[inline]
pub fn set(&self, value: T) {
unsafe { ::core::ptr::write_volatile(self.value.get(), value) }
}
#[inline]
pub fn read(&self, field: Field<T, R>) -> T {
field.read(self.get())
}
#[inline]
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(&self, field: Field<T, R>) -> Option<E> {
field.read_as_enum(self.get())
}
#[inline]
pub fn extract(&self) -> LocalRegisterCopy<T, R> {
LocalRegisterCopy::new(self.get())
}
#[inline]
pub fn write(&self, field: FieldValue<T, R>) {
self.set(field.value);
}
#[inline]
pub fn modify(&self, field: FieldValue<T, R>) {
self.set(field.modify(self.get()));
}
#[inline]
pub fn modify_no_read(&self, original: LocalRegisterCopy<T, R>, field: FieldValue<T, R>) {
self.set(field.modify(original.get()));
}
#[inline]
pub fn is_set(&self, field: Field<T, R>) -> bool {
field.is_set(self.get())
}
#[inline]
pub fn matches_any(&self, field: FieldValue<T, R>) -> bool {
field.matches_any(self.get())
}
#[inline]
pub fn matches_all(&self, field: FieldValue<T, R>) -> bool {
field.matches_all(self.get())
}
}
/// Specific section of a register.
#[derive(Copy, Clone)]
pub struct Field<T: IntLike, R: RegisterLongName> {
pub mask: T,
mask: T,
pub shift: usize,
associated_register: PhantomData<R>,
}
impl<T: IntLike, R: RegisterLongName> Field<T, R> {
#[inline]
pub fn read(self, val: T) -> T {
(val & (self.mask << self.shift)) >> self.shift
}
#[inline]
/// Check if one or more bits in a field are set
pub fn is_set(self, val: T) -> bool {
val & (self.mask << self.shift) != T::zero()
}
#[inline]
/// Read value of the field as an enum member
pub fn read_as_enum<E: TryFromValue<T, EnumType = E>>(self, val: T) -> Option<E> {
E::try_from(self.read(val))
}
}
// For the Field, the mask is unshifted, ie. the LSB should always be set
impl<R: RegisterLongName> Field<u8, R> {
pub const fn new(mask: u8, shift: usize) -> Field<u8, R> {
@ -424,26 +594,22 @@ impl<R: RegisterLongName> Field<u64, R> {
// location in the register.
#[derive(Copy, Clone)]
pub struct FieldValue<T: IntLike, R: RegisterLongName> {
pub mask: T,
mask: T,
pub value: T,
associated_register: PhantomData<R>,
}
// Necessary to split the implementation of u8 and u32 out because the bitwise
// Necessary to split the implementation of new() out because the bitwise
// math isn't treated as const when the type is generic.
// Tracking issue: https://github.com/rust-lang/rfcs/pull/2632
impl<R: RegisterLongName> FieldValue<u8, R> {
pub const fn new(mask: u8, shift: usize, value: u8) -> Self {
FieldValue {
mask: mask << shift,
value: (value << shift) & (mask << shift),
value: (value & mask) << shift,
associated_register: PhantomData,
}
}
/// Get the raw bitmask represented by this FieldValue.
pub fn mask(self) -> u8 {
self.mask as u8
}
}
impl<R: RegisterLongName> From<FieldValue<u8, R>> for u8 {
@ -456,7 +622,7 @@ impl<R: RegisterLongName> FieldValue<u16, R> {
pub const fn new(mask: u16, shift: usize, value: u16) -> Self {
FieldValue {
mask: mask << shift,
value: (value << shift) & (mask << shift),
value: (value & mask) << shift,
associated_register: PhantomData,
}
}
@ -472,15 +638,10 @@ impl<R: RegisterLongName> FieldValue<u32, R> {
pub const fn new(mask: u32, shift: usize, value: u32) -> Self {
FieldValue {
mask: mask << shift,
value: (value << shift) & (mask << shift),
value: (value & mask) << shift,
associated_register: PhantomData,
}
}
/// Get the raw bitmask represented by this FieldValue.
pub fn mask(self) -> u32 {
self.mask as u32
}
}
impl<R: RegisterLongName> From<FieldValue<u32, R>> for u32 {
@ -493,15 +654,10 @@ impl<R: RegisterLongName> FieldValue<u64, R> {
pub const fn new(mask: u64, shift: usize, value: u64) -> Self {
FieldValue {
mask: mask << shift,
value: (value << shift) & (mask << shift),
value: (value & mask) << shift,
associated_register: PhantomData,
}
}
/// Get the raw bitmask represented by this FieldValue.
pub fn mask(self) -> u64 {
self.mask as u64
}
}
impl<R: RegisterLongName> From<FieldValue<u64, R>> for u64 {
@ -511,10 +667,30 @@ impl<R: RegisterLongName> From<FieldValue<u64, R>> for u64 {
}
impl<T: IntLike, R: RegisterLongName> FieldValue<T, R> {
// Modify fields in a register value
/// Get the raw bitmask represented by this FieldValue.
pub fn mask(self) -> T {
self.mask as T
}
#[inline]
pub fn read(&self, field: Field<T, R>) -> T {
field.read(self.value)
}
/// Modify fields in a register value
pub fn modify(self, val: T) -> T {
(val & !self.mask) | self.value
}
/// Check if any specified parts of a field match
pub fn matches_any(self, val: T) -> bool {
val & self.mask != T::zero()
}
/// Check if all specified parts of a field match
pub fn matches_all(self, val: T) -> bool {
val & self.mask == self.value
}
}
// Combine two fields with the addition operator
@ -532,10 +708,270 @@ impl<T: IntLike, R: RegisterLongName> Add for FieldValue<T, R> {
// Combine two fields with the += operator
impl<T: IntLike, R: RegisterLongName> AddAssign for FieldValue<T, R> {
fn add_assign(&mut self, rhs: FieldValue<T, R>) {
*self = FieldValue {
mask: self.mask | rhs.mask,
value: self.value | rhs.value,
associated_register: PhantomData,
};
self.mask |= rhs.mask;
self.value |= rhs.value;
}
}
#[cfg(not(feature = "no_std_unit_tests"))]
#[cfg(test)]
mod tests {
#[derive(Debug, PartialEq, Eq)]
enum Foo {
Foo0,
Foo1,
Foo2,
Foo3,
Foo4,
Foo5,
Foo6,
Foo7,
}
impl super::TryFromValue<u16> for Foo {
type EnumType = Foo;
fn try_from(v: u16) -> Option<Self::EnumType> {
Self::try_from(v as u32)
}
}
impl super::TryFromValue<u32> for Foo {
type EnumType = Foo;
fn try_from(v: u32) -> Option<Self::EnumType> {
match v {
0 => Some(Foo::Foo0),
1 => Some(Foo::Foo1),
2 => Some(Foo::Foo2),
3 => Some(Foo::Foo3),
4 => Some(Foo::Foo4),
5 => Some(Foo::Foo5),
6 => Some(Foo::Foo6),
7 => Some(Foo::Foo7),
_ => None,
}
}
}
mod field {
use super::super::{Field, TryFromValue};
use super::Foo;
#[test]
fn test_new() {
let field8 = Field::<u8, ()>::new(0x12, 3);
assert_eq!(field8.mask, 0x12_u8);
assert_eq!(field8.shift, 3);
let field16 = Field::<u16, ()>::new(0x1234, 5);
assert_eq!(field16.mask, 0x1234_u16);
assert_eq!(field16.shift, 5);
let field32 = Field::<u32, ()>::new(0x12345678, 9);
assert_eq!(field32.mask, 0x12345678_u32);
assert_eq!(field32.shift, 9);
let field64 = Field::<u64, ()>::new(0x12345678_9abcdef0, 1);
assert_eq!(field64.mask, 0x12345678_9abcdef0_u64);
assert_eq!(field64.shift, 1);
}
#[test]
fn test_read() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(field.read(0x123), 0x12);
let field = Field::<u32, ()>::new(0xF0F, 4);
assert_eq!(field.read(0x1234), 0x103);
}
#[test]
fn test_is_set() {
let field = Field::<u16, ()>::new(0xFF, 4);
assert_eq!(field.is_set(0), false);
assert_eq!(field.is_set(0xFFFF), true);
assert_eq!(field.is_set(0x0FF0), true);
assert_eq!(field.is_set(0x1000), false);
assert_eq!(field.is_set(0x0100), true);
assert_eq!(field.is_set(0x0010), true);
assert_eq!(field.is_set(0x0001), false);
for shift in 0..24 {
let field = Field::<u32, ()>::new(0xFF, shift);
for x in 1..=0xFF {
assert_eq!(field.is_set(x << shift), true);
}
assert_eq!(field.is_set(!(0xFF << shift)), false);
}
}
#[test]
fn test_read_as_enum() {
let field = Field::<u16, ()>::new(0x7, 4);
assert_eq!(field.read_as_enum(0x1234), Some(Foo::Foo3));
assert_eq!(field.read_as_enum(0x5678), Some(Foo::Foo7));
assert_eq!(field.read_as_enum(0xFFFF), Some(Foo::Foo7));
assert_eq!(field.read_as_enum(0x0000), Some(Foo::Foo0));
assert_eq!(field.read_as_enum(0x0010), Some(Foo::Foo1));
assert_eq!(field.read_as_enum(0x1204), Some(Foo::Foo0));
for shift in 0..29 {
let field = Field::<u32, ()>::new(0x7, shift);
for x in 0..8 {
assert_eq!(field.read_as_enum(x << shift), Foo::try_from(x));
}
}
}
}
mod field_value {
use super::super::Field;
#[test]
fn test_from() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(u32::from(field.val(0)), 0);
assert_eq!(u32::from(field.val(0xFFFFFFFF)), 0xFF0);
assert_eq!(u32::from(field.val(0x12)), 0x120);
assert_eq!(u32::from(field.val(0x123)), 0x230);
for shift in 0..32 {
let field = Field::<u32, ()>::new(0xFF, shift);
for x in 0..=0xFF {
assert_eq!(u32::from(field.val(x)), x << shift);
}
}
}
#[test]
fn test_read_same_field() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(field.val(0).read(field), 0);
assert_eq!(field.val(0xFFFFFFFF).read(field), 0xFF);
assert_eq!(field.val(0x12).read(field), 0x12);
assert_eq!(field.val(0x123).read(field), 0x23);
for shift in 0..24 {
let field = Field::<u32, ()>::new(0xFF, shift);
for x in 0..=0xFF {
assert_eq!(field.val(x).read(field), x);
}
}
}
#[test]
fn test_read_disjoint_fields() {
for shift in 0..24 {
let field1 = Field::<u32, ()>::new(0xF0, shift);
let field2 = Field::<u32, ()>::new(0x0F, shift);
for x in 0..=0xFF {
assert_eq!(field1.val(x).read(field2), 0);
assert_eq!(field2.val(x).read(field1), 0);
}
}
for shift in 0..24 {
let field1 = Field::<u32, ()>::new(0xF, shift);
let field2 = Field::<u32, ()>::new(0xF, shift + 4);
for x in 0..=0xFF {
assert_eq!(field1.val(x).read(field2), 0);
assert_eq!(field2.val(x).read(field1), 0);
}
}
}
#[test]
fn test_modify() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(field.val(0x23).modify(0x0000), 0x0230);
assert_eq!(field.val(0x23).modify(0xFFFF), 0xF23F);
assert_eq!(field.val(0x23).modify(0x1234), 0x1234);
assert_eq!(field.val(0x23).modify(0x5678), 0x5238);
}
#[test]
fn test_matches_any() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(field.val(0x23).matches_any(0x1234), true);
assert_eq!(field.val(0x23).matches_any(0x5678), true);
assert_eq!(field.val(0x23).matches_any(0x5008), false);
for shift in 0..24 {
let field = Field::<u32, ()>::new(0xFF, shift);
for x in 0..=0xFF {
let field_value = field.val(x);
for y in 1..=0xFF {
assert_eq!(field_value.matches_any(y << shift), true);
}
assert_eq!(field_value.matches_any(0), false);
assert_eq!(field_value.matches_any(!(0xFF << shift)), false);
}
}
}
#[test]
fn test_matches_all() {
let field = Field::<u32, ()>::new(0xFF, 4);
assert_eq!(field.val(0x23).matches_all(0x1234), true);
assert_eq!(field.val(0x23).matches_all(0x5678), false);
for shift in 0..24 {
let field = Field::<u32, ()>::new(0xFF, shift);
for x in 0..=0xFF {
assert_eq!(field.val(x).matches_all(x << shift), true);
assert_eq!(field.val(x + 1).matches_all(x << shift), false);
}
}
}
#[test]
fn test_add_disjoint_fields() {
let field1 = Field::<u32, ()>::new(0xFF, 24);
let field2 = Field::<u32, ()>::new(0xFF, 16);
let field3 = Field::<u32, ()>::new(0xFF, 8);
let field4 = Field::<u32, ()>::new(0xFF, 0);
assert_eq!(
u32::from(
field1.val(0x12) + field2.val(0x34) + field3.val(0x56) + field4.val(0x78)
),
0x12345678
);
for shift in 0..24 {
let field1 = Field::<u32, ()>::new(0xF, shift);
let field2 = Field::<u32, ()>::new(0xF, shift + 4);
for x in 0..=0xF {
for y in 0..=0xF {
assert_eq!(
u32::from(field1.val(x) + field2.val(y)),
(x | (y << 4)) << shift
);
}
}
}
}
#[test]
fn test_add_assign_disjoint_fields() {
let field1 = Field::<u32, ()>::new(0xFF, 24);
let field2 = Field::<u32, ()>::new(0xFF, 16);
let field3 = Field::<u32, ()>::new(0xFF, 8);
let field4 = Field::<u32, ()>::new(0xFF, 0);
let mut value = field1.val(0x12);
value += field2.val(0x34);
value += field3.val(0x56);
value += field4.val(0x78);
assert_eq!(u32::from(value), 0x12345678);
for shift in 0..24 {
let field1 = Field::<u32, ()>::new(0xF, shift);
let field2 = Field::<u32, ()>::new(0xF, shift + 4);
for x in 0..=0xF {
for y in 0..=0xF {
let mut value = field1.val(x);
value += field2.val(y);
assert_eq!(u32::from(value), (x | (y << 4)) << shift);
}
}
}
}
}
// TODO: More unit tests here.
}