aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorIan McIntyre <ianpmcintyre@gmail.com>2022-11-03 11:44:43 -0400
committerIan McIntyre <ianpmcintyre@gmail.com>2022-12-07 09:42:54 -0500
commit1290bae4efb487e2b51ae6176a7211d84cbb447e (patch)
treef5de802507920f311cf94ead929370c442e88bc7 /tests
parenta5850ab6d6c508fc3351fd86646bcf3fb1b69103 (diff)
Expand macros testing for register scalars, arrays
The parent commit extends the macros to support both scalar and array register access. This commit extends the macro test suite to test both scalar and array register access. It also tests that you can use things that deref to a register block, including normal references, deref smart pointers, and raw pointers. The tests cases carry over the shadowed variable tests.
Diffstat (limited to 'tests')
-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]);
}