aboutsummaryrefslogtreecommitdiff
path: root/tests/macros.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/macros.rs')
-rw-r--r--tests/macros.rs462
1 files changed, 382 insertions, 80 deletions
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]);
}