aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib.rs68
-rw-r--r--tests/macros.rs462
-rw-r--r--tests/pathological.rs70
3 files changed, 496 insertions, 104 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 52401d6..bcf0370 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -194,6 +194,10 @@ impl<T: Copy> UnsafeWORegister<T> {
/// # }
/// ```
///
+/// To support register arrays, each macro form also supports one or more array indices after the
+/// register. For example, `write_reg!(stm32ral::gpio, gpioa, ODR[2], 42);` writes the value 42 to
+/// the third register in an `ODR` register array.
+///
/// # Usage
/// Like `modify_reg!`, this macro can be used in two ways, either with a single value to write to
/// the whole register, or with multiple fields each with their own value.
@@ -203,7 +207,8 @@ impl<T: Copy> UnsafeWORegister<T> {
/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
/// `*const RegisterBlock`),
-/// * the register you wish you access: `MODER` (a field on the `RegisterBlock`).
+/// * the register (and offset, for arrays) you wish you access: `MODER` (a field on the
+/// `RegisterBlock`).
///
/// In the single-value usage, the final argument is just the value to write:
/// ```rust,no_run
@@ -275,11 +280,11 @@ impl<T: Copy> UnsafeWORegister<T> {
/// and the macro brings such constants into scope and then dereferences the provided reference.
#[macro_export]
macro_rules! write_reg {
- ( $periph:path, $instance:expr, $reg:ident, $( $field:ident : $value:expr ),+ ) => {{
+ ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $( $field:ident : $value:expr ),+ ) => {{
#[allow(unused_imports)]
use $periph::{*};
#[allow(unused_imports)]
- (*$instance).$reg.write(
+ (*$instance).$reg $([$offset])*.write(
$({
use $periph::{$reg::$field::{W::*, RW::*}};
($value << { use $periph::{$reg::$field::offset}; offset })
@@ -287,10 +292,10 @@ macro_rules! write_reg {
}) | *
);
}};
- ( $periph:path, $instance:expr, $reg:ident, $value:expr ) => {{
+ ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $value:expr ) => {{
#[allow(unused_imports)]
use $periph::{*};
- (*$instance).$reg.write($value);
+ (*$instance).$reg $([$offset])*.write($value);
}};
}
@@ -313,6 +318,10 @@ macro_rules! write_reg {
/// # }
/// ```
///
+/// To support register arrays, each macro form also supports one or more array indices after the
+/// register. For example, `modify_reg!(stm32ral::gpio, gpioa, ODR[2], |reg| reg | (1<<3));` sets
+/// a high bit in the third register of an `ODR` register array.
+///
/// # Usage
/// Like `write_reg!`, this macro can be used in two ways, either with a modification of the entire
/// register, or by specifying which fields to change and what value to change them to.
@@ -322,7 +331,8 @@ macro_rules! write_reg {
/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
/// `*const RegisterBlock`),
-/// * the register you wish you access: `MODER` (a field on the `RegisterBlock`).
+/// * the register (and offset, for arrays) you wish you access: `MODER` (a field on the
+/// `RegisterBlock`).
///
/// In the whole-register usage, the final argument is a closure that accepts the current value
/// of the register and returns the new value to write:
@@ -408,12 +418,12 @@ macro_rules! write_reg {
/// and the macro brings such constants into scope and then dereferences the provided reference.
#[macro_export]
macro_rules! modify_reg {
- ( $periph:path, $instance:expr, $reg:ident, $( $field:ident : $value:expr ),+ ) => {{
+ ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $( $field:ident : $value:expr ),+ ) => {{
#[allow(unused_imports)]
use $periph::{*};
#[allow(unused_imports)]
- (*$instance).$reg.write(
- ((*$instance).$reg.read() & !( $({ use $periph::{$reg::$field::mask}; mask }) | * ))
+ (*$instance).$reg $([$offset])*.write(
+ ((*$instance).$reg $([$offset])*.read() & !( $({ use $periph::{$reg::$field::mask}; mask }) | * ))
| $({
use $periph::{$reg::$field::{W::*, RW::*}};
($value << { use $periph::{$reg::$field::offset}; offset })
@@ -421,10 +431,10 @@ macro_rules! modify_reg {
}) | *
);
}};
- ( $periph:path, $instance:expr, $reg:ident, $fn:expr ) => {{
+ ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $fn:expr ) => {{
#[allow(unused_imports)]
use $periph::{*};
- (*$instance).$reg.write($fn((*$instance).$reg.read()));
+ (*$instance).$reg $([$offset])*.write($fn((*$instance).$reg $([$offset])*.read()));
}};
}
@@ -453,6 +463,10 @@ macro_rules! modify_reg {
/// # }
/// ```
///
+/// To support register arrays, each macro form also supports one or more array indices after the
+/// register. For example, `read_reg!(stm32ral::gpio, gpioa, ODR[2]);` reads from the third
+/// register of an `ODR` register array.
+///
/// # Usage
/// Like `write_reg!`, this macro can be used multiple ways, either reading the entire register or
/// reading a one or more fields from it and potentially performing a comparison with one field.
@@ -462,7 +476,8 @@ macro_rules! modify_reg {
/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
/// `*const RegisterBlock`),
-/// * the register you wish to access: `IDR` (a field on the `RegisterBlock`).
+/// * the register (and offset, for arrays) you wish to access: `IDR` (a field on the
+/// `RegisterBlock`).
///
/// In the whole-register usage, the macro simply returns the register's value:
/// ```rust,no_run
@@ -527,27 +542,27 @@ macro_rules! modify_reg {
/// and the macro brings such constants into scope and then dereferences the provided reference.
#[macro_export]
macro_rules! read_reg {
- ( $periph:path, $instance:expr, $reg:ident, $( $field:ident ),+ ) => {{
+ ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $( $field:ident ),+ ) => {{
#[allow(unused_imports)]
use $periph::{*};
- let val = ((*$instance).$reg.read());
+ let val = ((*$instance).$reg $([$offset])*.read());
( $({
#[allow(unused_imports)]
use $periph::{$reg::$field::{mask, offset, R::*, RW::*}};
(val & mask) >> offset
}) , *)
}};
- ( $periph:path, $instance:expr, $reg:ident, $field:ident $($cmp:tt)* ) => {{
+ ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $field:ident $($cmp:tt)* ) => {{
#[allow(unused_imports)]
use $periph::{*};
#[allow(unused_imports)]
use $periph::{$reg::$field::{mask, offset, R::*, RW::*}};
- (((*$instance).$reg.read() & mask) >> offset) $($cmp)*
+ (((*$instance).$reg $([$offset])*.read() & mask) >> offset) $($cmp)*
}};
- ( $periph:path, $instance:expr, $reg:ident ) => {{
+ ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])* ) => {{
#[allow(unused_imports)]
use $periph::{*};
- ((*$instance).$reg.read())
+ ((*$instance).$reg $([$offset])*.read())
}};
}
@@ -567,6 +582,10 @@ macro_rules! read_reg {
/// # }
/// ```
///
+/// To support register arrays, each macro form also supports one or more array indices after
+/// the register. For example, `reset_reg!(stm32ral::gpio, gpioa, GPIOA, ODR[2]);` resets the
+/// third register in an `ODR` register array.
+///
/// # Usage
/// Like `write_reg!`, this macro can be used in two ways, either resetting the entire register
/// or just resetting specific fields within in. The register or fields are written with their
@@ -578,7 +597,8 @@ macro_rules! read_reg {
/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
/// `*const RegisterBlock`),
/// * the module for the instance of that peripheral: `GPIOA`,
-/// * the register you wish to access: `MODER` (a field on the `RegisterBlock`).
+/// * the register (and offset, for arrays) you wish to access: `MODER` (a field on the
+/// `RegisterBlock`).
///
/// In the whole-register usage, that's it:
/// ```rust,no_run
@@ -624,20 +644,20 @@ macro_rules! read_reg {
/// `GPIOA` they are not the same thing.
#[macro_export]
macro_rules! reset_reg {
- ( $periph:path, $instance:expr, $instancemod:path, $reg:ident, $( $field:ident ),+ ) => {{
+ ( $periph:path, $instance:expr, $instancemod:path, $reg:ident $([$offset:expr])*, $( $field:ident ),+ ) => {{
#[allow(unused_imports)]
use $periph::{*};
use $periph::{$instancemod::{reset}};
#[allow(unused_imports)]
- (*$instance).$reg.write({
+ (*$instance).$reg $([$offset])*.write({
let resetmask: u32 = $({ use $periph::{$reg::$field::mask}; mask }) | *;
- ((*$instance).$reg.read() & !resetmask) | (reset.$reg & resetmask)
+ ((*$instance).$reg $([$offset])*.read() & !resetmask) | (reset.$reg & resetmask)
});
}};
- ( $periph:path, $instance:expr, $instancemod:path, $reg:ident ) => {{
+ ( $periph:path, $instance:expr, $instancemod:path, $reg:ident $([$offset:expr])*) => {{
#[allow(unused_imports)]
use $periph::{*};
use $periph::{$instancemod::{reset}};
- (*$instance).$reg.write(reset.$reg);
+ (*$instance).$reg $([$offset])*.write(reset.$reg);
}};
}
diff --git a/tests/macros.rs b/tests/macros.rs
index 8ff36ac..ae33599 100644
--- a/tests/macros.rs
+++ b/tests/macros.rs
@@ -1,102 +1,404 @@
-//! Test the read, write, modify macros.
+//! Tests that read, write, modify, and reset macros
+//! work with
+//!
+//! - scalar register syntax
+//! - array register syntax
+//!
+//! when supplied with
+//!
+//! - a reference to a register block.
+//! - an "instance" type that derefs to a register block.
+//! - a pointer to a register block.
+
+#![allow(non_upper_case_globals, non_snake_case)] // Macro conventions.
use ral_registers as ral;
-struct DummyRegisterBlock {
- register: ral::RWRegister<u32>,
+/// A peripheral module.
+mod periph {
+ #[repr(C)]
+ pub struct RegisterBlock {
+ pub MY_SCALAR: ral_registers::RWRegister<u32>,
+ pub MY_ARRAY: [ral_registers::RWRegister<u32>; 3],
+ }
+
+ pub mod MY_SCALAR {
+ pub mod FIELD_A {
+ pub const offset: u32 = 0;
+ pub const mask: u32 = 0x7F << offset;
+ pub mod R {}
+ pub mod W {}
+ pub mod RW {}
+ }
+ pub mod FIELD_B {
+ pub const offset: u32 = 27;
+ pub const mask: u32 = 0b11 << offset;
+ pub mod R {}
+ pub mod W {}
+ pub mod RW {}
+ }
+ }
+
+ /// The register array module resembles the register module for
+ /// a scalar register. The macros distinguish the index
+ /// operator from the register name to look up items in
+ /// this module.
+ pub mod MY_ARRAY {
+ // For testing convenience, we're pretending that MY_ARRAY
+ // has the same fields as MY_SCALAR.
+ pub use super::MY_SCALAR::*;
+ }
+
+ /// Reset values are always expressed as a scalar, no matter
+ /// the register plurality.
+ pub struct ResetValues {
+ pub MY_SCALAR: u32,
+ pub MY_ARRAY: u32,
+ }
+
+ pub mod INST {
+ pub const reset: super::ResetValues = super::ResetValues {
+ MY_SCALAR: 42,
+ MY_ARRAY: 42,
+ };
+ }
+}
+
+fn register_block() -> periph::RegisterBlock {
+ // Safety: bitpattern of zero is fine.
+ use std::mem::MaybeUninit;
+ unsafe { MaybeUninit::zeroed().assume_init() }
+}
+
+struct Instance<'a> {
+ rb: &'a periph::RegisterBlock,
+}
+
+impl<'a> Instance<'a> {
+ fn new(rb: &'a periph::RegisterBlock) -> Self {
+ Self { rb }
+ }
+}
+
+impl std::ops::Deref for Instance<'_> {
+ type Target = periph::RegisterBlock;
+ fn deref(&self) -> &Self::Target {
+ self.rb
+ }
+}
+
+/// Defines the base cases for read_reg.
+///
+/// The goal is to have one macro path that supports both scalar
+/// and array tests cases. To achieve that, there's always a register
+/// identifier. There's optionally a bracket that has an offset.
+macro_rules! read_reg_test_cases {
+ ($instance:expr, $register:ident $([$offset:expr])*) => {
+ // Setup:
+ (*$instance).$register $([$offset])*.write(u32::MAX);
+
+ assert_eq!(ral::read_reg!(periph, $instance, $register $([$offset])*), u32::MAX, "Direct read");
+
+ assert_eq!(ral::read_reg!(periph, $instance, $register $([$offset])*, FIELD_A), 0x7F, "Individual field read (A)");
+ assert_eq!(ral::read_reg!(periph, $instance, $register $([$offset])*, FIELD_B), 0b11, "Individual field read (B)");
+
+ assert_eq!(
+ ral::read_reg!(periph, $instance, $register $([$offset])*, FIELD_A, FIELD_B),
+ (0x7F, 0b11),
+ "Tuple field reads"
+ );
+
+ assert!(ral::read_reg!(
+ periph,
+ $instance,
+ $register $([$offset])*,
+ FIELD_A == 0x7F
+ ), "Boolean expressions");
+ };
+}
+
+#[test]
+fn read_scalar_ref() {
+ let rb = register_block();
+ read_reg_test_cases!(&rb, MY_SCALAR);
+}
+
+#[test]
+fn read_scalar_deref() {
+ let rb = register_block();
+ let inst = Instance::new(&rb);
+ read_reg_test_cases!(inst, MY_SCALAR);
+}
+
+#[test]
+fn read_scalar_ptr() {
+ let ptr: *const _ = &register_block();
+ unsafe {
+ read_reg_test_cases!(ptr, MY_SCALAR);
+ }
+}
+
+#[test]
+fn read_array_ref() {
+ let rb = register_block();
+ read_reg_test_cases!(&rb, MY_ARRAY[1]);
+}
+
+#[test]
+fn read_array_deref() {
+ let rb = register_block();
+ let inst = Instance::new(&rb);
+ read_reg_test_cases!(inst, MY_ARRAY[1]);
+}
+
+#[test]
+fn read_array_ptr() {
+ let ptr: *const _ = &register_block();
+ unsafe {
+ read_reg_test_cases!(ptr, MY_ARRAY[1]);
+ }
+}
+
+#[should_panic]
+#[test]
+fn read_array_out_of_bounds() {
+ let rb = register_block();
+ ral::read_reg!(periph, &rb, MY_ARRAY[42]);
+}
+
+/// Base test cases for write_reg.
+///
+/// See [read_reg_test_cases] for more information.
+macro_rules! write_reg_test_cases {
+ ($instance:expr, $register:ident $([$offset:expr])*) => {
+ ral::write_reg!(periph, $instance, $register $([$offset])*, FIELD_A: u32::MAX);
+ assert_eq!((*$instance).$register $([$offset])*.read(), 0x7F, "1:1 write:field (A)");
+ ral::write_reg!(periph, $instance, $register $([$offset])*, FIELD_B: u32::MAX);
+ assert_eq!((*$instance).$register $([$offset])*.read(), 0b11 << 27, "1:1 write:field (B)");
+
+ ral::write_reg!(
+ periph,
+ $instance,
+ $register $([$offset])*,
+ FIELD_A: u32::MAX,
+ FIELD_B: u32::MAX
+ );
+ assert_eq!((*$instance).$register $([$offset])*.read(), (0b11 << 27) | 0x7F, "1:N write:field");
+
+ ral::write_reg!(periph, $instance, $register $([$offset])*, 0xAAAAAAAA);
+ assert_eq!((*$instance).$register $([$offset])*.read(), 0xAAAAAAAA, "Direct write");
+
+ // Make sure that local variables mask, offset don't shadow macro details.
+ #[deny(warnings)]
+ {
+ let mask = 7;
+ let offset = 8;
+ ral::write_reg!(periph, $instance, $register $([$offset])*, FIELD_A: mask, FIELD_B: offset);
+ }
+ };
+}
+
+#[test]
+fn write_scalar_ref() {
+ let rb = register_block();
+ write_reg_test_cases!(&rb, MY_SCALAR);
+}
+
+#[test]
+fn write_scalar_deref() {
+ let rb = register_block();
+ let inst = Instance::new(&rb);
+ write_reg_test_cases!(inst, MY_SCALAR);
}
-mod register {
- #![allow(non_upper_case_globals, non_snake_case)] // Macro conventions.
+#[test]
+fn write_scalar_ptr() {
+ let ptr: *const _ = &register_block();
+ unsafe {
+ write_reg_test_cases!(ptr, MY_SCALAR);
+ }
+}
- pub mod field_foo {
- pub const offset: u32 = 10;
- pub const mask: u32 = 0x7 << offset;
- pub mod RW {}
- pub mod W {}
- pub mod R {}
+#[test]
+fn write_array_ref() {
+ let rb = register_block();
+ write_reg_test_cases!(&rb, MY_ARRAY[1]);
+}
+
+#[test]
+fn write_array_deref() {
+ let rb = register_block();
+ let inst = Instance::new(&rb);
+ write_reg_test_cases!(inst, MY_ARRAY[1]);
+}
+
+#[test]
+fn write_array_ptr() {
+ let ptr: *const _ = &register_block();
+ unsafe {
+ write_reg_test_cases!(ptr, MY_ARRAY[1]);
}
}
-fn zeroed_register_block() -> DummyRegisterBlock {
- use core::mem::MaybeUninit;
- let register_block = MaybeUninit::zeroed();
- // Safety: 0 is a safe bitpattern
- unsafe { register_block.assume_init() }
+#[should_panic]
+#[test]
+fn write_array_out_of_bounds() {
+ let rb = register_block();
+ ral::write_reg!(periph, &rb, MY_ARRAY[42], 7);
+}
+
+/// Base test cases for modify_reg.
+///
+/// See [read_reg_test_cases] for more information.
+macro_rules! modify_reg_test_cases {
+ ($instance:expr, $register:ident $([$offset:expr])*) => {
+ ral::modify_reg!(periph, $instance, $register $([$offset])*, FIELD_A: u32::MAX);
+ assert_eq!((*$instance).$register $([$offset])*.read(), 0x7F, "RMW individual fields (A)");
+ ral::modify_reg!(periph, $instance, $register $([$offset])*, FIELD_B: u32::MAX);
+ assert_eq!((*$instance).$register $([$offset])*.read(), 0x7F | (0b11 << 27), "RMW individual fields (B)");
+
+ ral::modify_reg!(periph, $instance, $register $([$offset])*, FIELD_A: 2, FIELD_B: 2);
+ assert_eq!((*$instance).$register $([$offset])*.read(), 2 | (2 << 27), "RMW multiple fields");
+
+ ral::modify_reg!(periph, $instance, $register $([$offset])*, |reg| {
+ assert_eq!(reg, 2 | (2 << 27));
+ reg | u32::MAX
+ });
+ assert_eq!((*$instance).$register $([$offset])*.read(), u32::MAX, "RMW whole register");
+
+ // Make sure that local variables mask, offset don't shadow macro details.
+ #[deny(warnings)]
+ {
+ let mask = 7;
+ let offset = 8;
+ ral::modify_reg!(periph, $instance, $register $([$offset])*, FIELD_A: mask, FIELD_B: offset);
+ }
+ };
}
#[test]
-fn register_read() {
- let register_block = zeroed_register_block();
- register_block.register.write(0b111 << 10);
- assert_eq!(
- 0x7,
- ral::read_reg!(self, &register_block, register, field_foo)
- );
+fn modify_scalar_ref() {
+ let rb = register_block();
+ modify_reg_test_cases!(&rb, MY_SCALAR);
}
#[test]
-fn register_write() {
- let register_block = zeroed_register_block();
- ral::write_reg!(self, &register_block, register, field_foo: 5);
- assert_eq!(5 << 10, register_block.register.read());
+fn modify_scalar_deref() {
+ let rb = register_block();
+ let inst = Instance::new(&rb);
+ modify_reg_test_cases!(inst, MY_SCALAR);
}
#[test]
-fn register_modify() {
- let register_block = zeroed_register_block();
- register_block.register.write(1 << 10);
- ral::modify_reg!(self, &register_block, register, field_foo: 6);
- assert_eq!(6 << 10, register_block.register.read());
+fn modify_scalar_ptr() {
+ let ptr: *const _ = &register_block();
+ unsafe {
+ modify_reg_test_cases!(ptr, MY_SCALAR);
+ }
}
-/// Demonstrates that a local variable, 'mask'
-/// doesn't affect the macro's behavior.
+#[test]
+fn modify_array_ref() {
+ let rb = register_block();
+ modify_reg_test_cases!(&rb, MY_ARRAY[1]);
+}
+
+#[test]
+fn modify_array_deref() {
+ let rb = register_block();
+ let inst = Instance::new(&rb);
+ modify_reg_test_cases!(inst, MY_ARRAY[1]);
+}
+
+#[test]
+fn modify_array_ptr() {
+ let ptr: *const _ = &register_block();
+ unsafe {
+ modify_reg_test_cases!(ptr, MY_ARRAY[1]);
+ }
+}
+
+#[should_panic]
+#[test]
+fn modify_array_out_of_bounds() {
+ let rb = register_block();
+ ral::modify_reg!(periph, &rb, MY_ARRAY[42], |_| 7);
+}
+
+/// Base test cases for reset_reg.
///
-/// This is the same test as register_modify(), but
-/// with the number '6' in a variable called 'mask'.
-#[deny(warnings)] // Promotes "unused variable: mask" into an error. Should no longer happen.
-#[test]
-fn register_unused_mask_modify() {
- let register_block = zeroed_register_block();
- register_block.register.write(1 << 10);
- let mask = 6;
- ral::modify_reg!(self, &register_block, register, field_foo: mask);
- assert_eq!(6 << 10, register_block.register.read());
-}
-
-/// Same test as above, but with a variable called
-/// 'offset' instead of 'mask'.
-#[deny(warnings)]
-#[test]
-fn register_unused_offset_modify() {
- let register_block = zeroed_register_block();
- register_block.register.write(1 << 10);
- let offset = 6;
- ral::modify_reg!(self, &register_block, register, field_foo: offset);
- assert_eq!(6 << 10, register_block.register.read());
-}
-
-/// Same as above test, but using the 'write' macro
-/// instead of 'modify'.
-#[deny(warnings)]
-#[test]
-fn register_unused_mask_write() {
- let register_block = zeroed_register_block();
- register_block.register.write(1 << 10);
- let mask = 6;
- ral::write_reg!(self, &register_block, register, field_foo: mask);
- assert_eq!(6 << 10, register_block.register.read());
-}
-
-/// Same test as above, but with a variable called
-/// 'offset' instead of 'mask'.
-#[deny(warnings)]
-#[test]
-fn register_unused_offset_write() {
- let register_block = zeroed_register_block();
- register_block.register.write(1 << 10);
- let offset = 6;
- ral::write_reg!(self, &register_block, register, field_foo: offset);
- assert_eq!(6 << 10, register_block.register.read());
+/// See [read_reg_test_cases] for more information.
+macro_rules! reset_reg_test_cases {
+ ($instance:expr, $register:ident $([$offset:expr])*) => {
+ (*$instance).$register $([$offset])*.write(u32::MAX);
+ ral::reset_reg!(periph, $instance, INST, $register $([$offset])*);
+ assert_eq!((*$instance).$register $([$offset])*.read(), 42, "Entire register");
+
+ (*$instance).$register $([$offset])*.write(u32::MAX);
+ ral::reset_reg!(periph, $instance, INST, $register $([$offset])*, FIELD_B);
+ assert_eq!((*$instance).$register $([$offset])*.read(), u32::MAX & !(0b11 << 27), "Field in register (B)");
+ ral::reset_reg!(periph, $instance, INST, $register $([$offset])*, FIELD_A);
+ assert_eq!(
+ (*$instance).$register $([$offset])*.read(),
+ u32::MAX & !(0b11 << 27) & !0x7F | 42,
+ "Field in register (A)"
+ );
+
+ (*$instance).$register $([$offset])*.write(u32::MAX);
+ ral::reset_reg!(periph, $instance, INST, $register $([$offset])*, FIELD_B, FIELD_A);
+ assert_eq!(
+ (*$instance).$register $([$offset])*.read(),
+ u32::MAX & !(0b11 << 27) & !0x7F | 42,
+ "Fields in register"
+ );
+ };
+}
+
+#[test]
+fn reset_scalar_ref() {
+ let rb = register_block();
+ reset_reg_test_cases!(&rb, MY_SCALAR);
+}
+
+#[test]
+fn reset_scalar_deref() {
+ let rb = register_block();
+ let inst = Instance::new(&rb);
+ reset_reg_test_cases!(inst, MY_SCALAR);
+}
+
+#[test]
+fn reset_scalar_ptr() {
+ let ptr: *const _ = &register_block();
+ unsafe {
+ reset_reg_test_cases!(ptr, MY_SCALAR);
+ }
+}
+
+#[test]
+fn reset_array_ref() {
+ let rb = register_block();
+ reset_reg_test_cases!(&rb, MY_ARRAY[1]);
+}
+
+#[test]
+fn reset_array_deref() {
+ let rb = register_block();
+ let inst = Instance::new(&rb);
+ reset_reg_test_cases!(inst, MY_ARRAY[1]);
+}
+
+#[test]
+fn reset_array_ptr() {
+ let ptr: *const _ = &register_block();
+ unsafe {
+ reset_reg_test_cases!(ptr, MY_ARRAY[1]);
+ }
+}
+
+#[should_panic]
+#[test]
+fn reset_array_out_of_bounds() {
+ let rb = register_block();
+ ral::reset_reg!(periph, &rb, INST, MY_ARRAY[42]);
}
diff --git a/tests/pathological.rs b/tests/pathological.rs
new file mode 100644
index 0000000..d463e29
--- /dev/null
+++ b/tests/pathological.rs
@@ -0,0 +1,70 @@
+//! Testing corner cases.
+
+#![allow(non_upper_case_globals, non_snake_case)] // Macro conventions.
+
+use ral_registers as ral;
+
+mod periph {
+ #[repr(C)]
+ pub struct RegisterBlock {
+ /// Multi-dimensional arrays.
+ #[allow(clippy::type_complexity)] // Intentionally complex type.
+ pub DEEP_LEARNING: [[[[[[[[ral_registers::RWRegister<u32>; 1]; 2]; 3]; 4]; 5]; 6]; 7]; 8],
+ }
+
+ pub mod DEEP_LEARNING {
+ pub mod GRADIENT {
+ pub const offset: u32 = 3;
+ pub const mask: u32 = 0x1F << offset;
+ pub mod R {}
+ pub mod W {}
+ pub mod RW {}
+ }
+ }
+
+ pub struct ResetValues {
+ pub DEEP_LEARNING: u32,
+ }
+
+ pub mod INST {
+ pub const reset: super::ResetValues = super::ResetValues { DEEP_LEARNING: 42 };
+ }
+}
+
+fn register_block() -> periph::RegisterBlock {
+ // Safety: bitpattern of zero is fine.
+ use std::mem::MaybeUninit;
+ unsafe { MaybeUninit::zeroed().assume_init() }
+}
+
+#[test]
+fn read_deep_array() {
+ let rb = register_block();
+ rb.DEEP_LEARNING[7][6][5][4][3][2][1][0].write(u32::MAX);
+ let gradient = ral::read_reg!(periph, &rb, DEEP_LEARNING[7][6][5][4][3][2][1][0], GRADIENT);
+ assert_eq!(gradient, 0x1F);
+}
+
+#[test]
+fn write_deep_array() {
+ let rb = register_block();
+ ral::write_reg!(periph, &rb, DEEP_LEARNING[7][6][5][4][3][2][1][0], 23);
+ assert_eq!(rb.DEEP_LEARNING[7][6][5][4][3][2][1][0].read(), 23);
+}
+
+#[test]
+fn modify_deep_array() {
+ let rb = register_block();
+ ral::modify_reg!(periph, &rb, DEEP_LEARNING[7][6][5][4][3][2][1][0], GRADIENT: 42);
+ assert_eq!(
+ rb.DEEP_LEARNING[7][6][5][4][3][2][1][0].read(),
+ (42 & 0x1F) << 3
+ );
+}
+
+#[test]
+fn reset_deep_array() {
+ let rb = register_block();
+ ral::reset_reg!(periph, &rb, INST, DEEP_LEARNING[7][6][5][4][3][2][1][0]);
+ assert_eq!(rb.DEEP_LEARNING[7][6][5][4][3][2][1][0].read(), 42);
+}