aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan McIntyre <me@mciantyre.dev>2025-04-12 18:37:30 -0400
committerIan McIntyre <me@mciantyre.dev>2025-09-28 10:32:16 -0400
commit495fbc58b6f4d2e751b16883a2c5cee3c598fd79 (patch)
tree733294d810dbd02dba79c156d47cdee246a88aec
parent7bf03005703186667623bfc681e10c44f7f8923d (diff)
Prototype a v0.2HEADv0.2
Needs to be documented. The gist: remove references to MMIO, and add support for nested structures. Drop the reset_reg! macro.
-rw-r--r--.cargo/config.toml4
-rw-r--r--CHANGELOG.md128
-rw-r--r--Cargo.toml10
-rw-r--r--memory.x5
-rw-r--r--src/lib.rs809
-rw-r--r--tests/macros.rs719
-rw-r--r--tests/pathological.rs70
7 files changed, 768 insertions, 977 deletions
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..56da414
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,4 @@
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+rustflags = [
+ "-Clink-arg=-Tlink.x",
+]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5be6d3..b5a4a1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,134 @@
## [Unreleased]
+**BREAKING** The `reset_reg!` macro is removed. It may be added later.
+
+**BREAKING** The API uses raw pointers to access registers, avoiding
+references. To support this goal, the package has new read, write, and modify
+macros. The macros expect a different definition for register blocks and
+registers.
+
+Here's a small example demonstrating the new definition of register blocks,
+registers, and fields. It also demonstrates the macro syntax.
+
+```rust
+//
+// Peripheral definition.
+//
+#![allow(non_snake_case)]
+
+pub mod pit {
+ #[repr(C)]
+ pub struct RegisterBlock {
+ pub MCR: u32,
+ pub LTMR: [u32; 2],
+ _reserved_0: [u8; 16],
+ pub CHANNEL: [CHANNEL::RegisterBlock; 4],
+ }
+
+ ral_registers::register!(pub MCR<u32> RW [
+ FRZ start(0) width(1) RW {}
+ MDIS start(1) width(1) RW {
+ DISABLE = 1,
+ ENABLE = 0,
+ }
+ ]);
+
+ ral_registers::register!(pub LTMR<u32> RO []);
+
+ pub mod CHANNEL {
+ #[repr(C)]
+ pub struct RegisterBlock {
+ pub LDVAL: u32,
+ pub CVAL: u32,
+ pub TCTRL: u32,
+ pub TFLG: u32,
+ }
+
+ ral_registers::register!(pub LDVAL RW []);
+ ral_registers::register!(pub CVAL RO []);
+
+ ral_registers::register!(pub TCTRL<u32> RW [
+ TEN start(0) width(1) RW {
+ DISABLE = 0,
+ ENABLE = 1,
+ }
+ TIE start(1) width(1) RW {}
+ CHN start(2) width(1) RW {}
+ ]);
+ ral_registers::register!(pub TFLG<u32> RW [
+ TIF start(0) width(1) RW {}
+ ]);
+ }
+}
+
+//
+// Peripheral usage.
+//
+use ral_registers as ral;
+
+// pit1() expanded from ral::instances! shown later
+let pit1 = unsafe { instances::pit1() };
+ral::modify_reg!(pit, pit1, MCR, MDIS: ENABLE);
+ral::modify_reg!(pit, pit1, CHANNEL[2].TCTRL, TEN: ENABLE);
+loop {
+ while ral::read_reg!(pit, pit1, CHANNEL[2].TFLG, TIF == 0) {}
+ ral::write_reg!(pit, pit1, CHANNEL[2].TFLG, TIF: 1);
+}
+
+//
+// NEW: instance definitions.
+//
+mod instances {
+ ral::instances! {
+ unsafe {
+ pit1<pit::RegisterBlock> = 0x4008_4000;
+ pit2<pit::RegisterBlock> = 0x400C_4000;
+
+ // Expansion:
+ // pub const unsafe fn pit1() -> ral::Instance<pit::RegisterBlock> { /* ... */ }
+ // pub const unsafe fn pit2() -> ral::Instance<pit::RegisterBlock> { /* ... */ }
+ }
+ }
+}
+```
+
+A `RegisterBlock` only describes a layout using unsigned or signed primitives.
+There are no more `RWRegister`, `RORegister`, or `WORegister` types to convey
+access. Instead, each register module describes its access. The approach allows
+a single register block layout to be shared for different use cases, cases that
+may vary the access for a given register.
+
+The `register!` macros provides a convenient way to define fields and field
+enums. The macro is optional; the expanded code is specified and stable. Codegen
+tools can directly generate compatible modules without using `register!`.
+
+Despite the new register block and register definitions, the read, write, and
+modify macros support the same syntax as 0.1. Specifically,
+
+```rust
+ral::modify_reg!(pit, pit1, MCR, MDIS: ENABLE);
+```
+
+is unchanged from the 0.1 macros. The remaining usages like
+
+```rust
+ral::modify_reg!(pit, pit1, CHANNEL[2].TCTRL, TEN: ENABLE);
+```
+
+is newly supported. All macros expand through an arbitrary depth of array
+offset and block members from the top-level instance.
+
+For compatibility with the macros, the instance type must implement the
+unsafe `ral_registers::Inst` trait. The type must implement an
+`fn as_ptr() -> *mut RegisterBlock` method. For convenience, the package
+provides an opinionated `Instance` type.
+
+Finally, the package exports an `instances!` macro that lets users declare
+the base address of register blocks. Each name-address pair expands to a
+`const unsafe fn` that produces an `Instance` object. The `const fn` permits
+C-style usage when inlined directly into the macro.
+
## [v0.1.3] - 2023-06-18
* Allow trailing commas inside macros (#7)
diff --git a/Cargo.toml b/Cargo.toml
index a87b2e1..f42d85d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,16 +1,10 @@
[package]
name = "ral-registers"
-version = "0.1.3"
+version = "0.2.0"
authors = ["Adam Greig <adam@adamgreig.com>"]
-edition = "2018"
+edition = "2024"
description = "MMIO registers abstraction with a macro API"
repository = "https://github.com/adamgreig/ral-registers"
license = "MIT OR Apache-2.0"
keywords = ["mmio", "embedded"]
categories = ["embedded", "no-std"]
-autoexamples = false
-
-[dependencies]
-
-[dev-dependencies]
-stm32ral = { version = "0.5.0", features = ["stm32f0x0"] }
diff --git a/memory.x b/memory.x
new file mode 100644
index 0000000..19825e7
--- /dev/null
+++ b/memory.x
@@ -0,0 +1,5 @@
+MEMORY
+{
+ FLASH : ORIGIN = 0x08000000, LENGTH = 64K
+ RAM : ORIGIN = 0x20000000, LENGTH = 20K
+}
diff --git a/src/lib.rs b/src/lib.rs
index 70cedf7..3de33d2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
// Copyright 2018 Adam Greig
+// Copyright 2025 Ian McIntyre
// See LICENSE-APACHE and LICENSE-MIT for license details.
//! This crate contains an MMIO abstraction that uses macros to read,
@@ -9,655 +10,287 @@
#![no_std]
-use core::cell::UnsafeCell;
+use core::ptr::NonNull;
-/// A read-write register of type T.
+/// Describes a type that acts as a peripheral instance.
///
-/// Contains one value of type T and provides volatile read/write functions to it.
+/// If you're not sure how to implement this, try using [`Instance`].
///
/// # Safety
-/// This register should be used where reads and writes to this peripheral register do not
-/// lead to memory unsafety. For example, it is a poor choice for a DMA target, but less
-/// worrisome for a GPIO output data register.
///
-/// Access to this register must be synchronised; if multiple threads (or the main thread and an
-/// interrupt service routine) are accessing it simultaneously you may encounter data races.
-#[repr(transparent)]
-pub struct RWRegister<T> {
- register: UnsafeCell<T>,
-}
+/// This can only be implemented atop "smart pointer" objects that
+/// produce the same raw pointer to a static register block.
+pub unsafe trait Inst {}
-impl<T: Copy> RWRegister<T> {
- /// Reads the value of the register.
- #[inline(always)]
- pub fn read(&self) -> T {
- unsafe { ::core::ptr::read_volatile(self.register.get()) }
- }
+/// Safety: any shared reference to an instance can similarly provide
+/// access to the underlying static object.
+unsafe impl<I> Inst for &'_ I where I: Inst {}
- /// Writes a new value to the register.
- #[inline(always)]
- pub fn write(&self, val: T) {
- unsafe { ::core::ptr::write_volatile(self.register.get(), val) }
- }
-}
+/// Safety: see shared reference documentation. If a shared
+/// reference can provide this access, then so can an exclusive
+/// reference.
+unsafe impl<I> Inst for &'_ mut I where I: Inst {}
-/// A read-write register of type T, where read/write access is unsafe.
-///
-/// Contains one value of type T and provides volatile read/write functions to it.
-///
-/// # Safety
-/// This register should be used where reads and writes to this peripheral may invoke
-/// undefined behaviour or memory unsafety. For example, any registers you write a memory
-/// address into.
+/// A pointer to a peripheral instance.
///
-/// Access to this register must be synchronised; if multiple threads (or the main thread and an
-/// interrupt service routine) are accessing it simultaneously you may encounter data races.
+/// This is transparently a `NonNull<T>`. Like a `NonNull<T>`,
+/// the type is `Copy` and `Clone`, so it may be shared throughout
+/// a single execution context.
+#[derive(PartialEq, Eq)]
#[repr(transparent)]
-pub struct UnsafeRWRegister<T> {
- register: UnsafeCell<T>,
-}
+pub struct Instance<T: ?Sized>(NonNull<T>);
-impl<T: Copy> UnsafeRWRegister<T> {
- /// Reads the value of the register.
- ///
- /// # Safety
- /// Refer to [UnsafeRWRegister]'s Safety section.
- #[inline(always)]
- pub unsafe fn read(&self) -> T {
- ::core::ptr::read_volatile(self.register.get())
- }
+/// Safety: the constructor's safety contract enforces the
+/// trait's safety requirements.
+unsafe impl<T: ?Sized> Inst for Instance<T> {}
- /// Writes a new value to the register.
+impl<T: ?Sized> Instance<T> {
+ /// Create an instance that points to a peripheral register block.
///
/// # Safety
- /// Refer to [UnsafeRWRegister]'s Safety section.
+ ///
+ /// The pointer must not be NULL. The pointer is expected to point
+ /// to static memory that represents a block of MMIO registers.
+ /// The layout of `T` must be correct for the base address.
#[inline(always)]
- pub unsafe fn write(&self, val: T) {
- ::core::ptr::write_volatile(self.register.get(), val)
+ #[must_use]
+ pub const unsafe fn new_unchecked(ptr: *mut T) -> Self {
+ // Safety: caller claims the pointer isn't NULL.
+ Self(unsafe { NonNull::new_unchecked(ptr) })
}
-}
-
-/// A read-only register of type T.
-///
-/// Contains one value of type T and provides a volatile read function to it.
-///
-/// # Safety
-/// This register should be used where reads and writes to this peripheral register do not
-/// lead to memory unsafety.
-///
-/// Access to this register must be synchronised; if multiple threads (or the main thread and an
-/// interrupt service routine) are accessing it simultaneously you may encounter data races.
-#[repr(transparent)]
-pub struct RORegister<T> {
- register: UnsafeCell<T>,
-}
-impl<T: Copy> RORegister<T> {
- /// Reads the value of the register.
+ /// Access the inner pointer.
#[inline(always)]
- pub fn read(&self) -> T {
- unsafe { ::core::ptr::read_volatile(self.register.get()) }
+ #[must_use]
+ pub const fn as_ptr(self) -> *mut T {
+ self.0.as_ptr()
}
}
-/// A read-only register of type T, where read access is unsafe.
-///
-/// Contains one value of type T and provides a volatile read function to it.
-///
-/// # Safety
-/// This register should be used where reads to this peripheral may invoke
-/// undefined behaviour or memory unsafety.
-///
-/// Access to this register must be synchronised; if multiple threads (or the main thread and an
-/// interrupt service routine) are accessing it simultaneously you may encounter data races.
-#[repr(transparent)]
-pub struct UnsafeRORegister<T> {
- register: UnsafeCell<T>,
-}
-
-impl<T: Copy> UnsafeRORegister<T> {
- /// Reads the value of the register.
- ///
- /// # Safety
- /// Refer to [UnsafeRWRegister]'s Safety section.
+impl<T: ?Sized> Clone for Instance<T> {
#[inline(always)]
- pub unsafe fn read(&self) -> T {
- ::core::ptr::read_volatile(self.register.get())
+ fn clone(&self) -> Self {
+ *self
}
}
-/// A write-only register of type T.
-///
-/// Contains one value of type T and provides a volatile write function to it.
-///
-/// # Safety
-/// This register should be used where writes to this peripheral register do not lead to memory
-/// unsafety.
-///
-/// Access to this register must be synchronised; if multiple threads (or the main thread and an
-/// interrupt service routine) are accessing it simultaneously you may encounter data races.
-#[repr(transparent)]
-pub struct WORegister<T> {
- register: UnsafeCell<T>,
-}
+impl<T: ?Sized> Copy for Instance<T> {}
-impl<T: Copy> WORegister<T> {
- /// Writes a new value to the register.
- #[inline(always)]
- pub fn write(&self, val: T) {
- unsafe { ::core::ptr::write_volatile(self.register.get(), val) }
+impl<T> core::fmt::Debug for Instance<T> {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ let ptr = self.0.as_ptr() as usize;
+ f.debug_tuple("Instance")
+ .field(&format_args!("{ptr:#X}"))
+ .finish()
}
}
-/// A write-only register of type T, where write access is unsafe.
-///
-/// Contains one value of type T and provides a volatile write function to it.
-///
-/// # Safety
-/// This register should be used where reads and writes to this peripheral may invoke
-/// undefined behaviour or memory unsafety.
+/// Check expressions that may be unsafe.
///
-/// Access to this register must be synchronised; if multiple threads (or the main thread and an
-/// interrupt service routine) are accessing it simultaneously you may encounter data races.
-#[repr(transparent)]
-pub struct UnsafeWORegister<T> {
- register: UnsafeCell<T>,
+/// Users are not expected to pass non-Copy expressions into
+/// this expansion.
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __expand_unsafe {
+ ( $( $expr:expr, )+ ) => { if false { $( let _ = $expr; )* } };
}
-impl<T: Copy> UnsafeWORegister<T> {
- /// Writes a new value to the register.
- ///
- /// # Safety
- /// Refer to [UnsafeRWRegister]'s Safety section.
- #[inline(always)]
- pub unsafe fn write(&self, val: T) {
- ::core::ptr::write_volatile(self.register.get(), val)
- }
-}
+#[doc(hidden)]
+#[allow(non_snake_case)]
+pub const fn must_be_ral_registers_Inst<I: Inst>(_: &I) {}
-/// Write to a RWRegister or UnsafeRWRegister.
-///
-/// # Examples
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// // Safely acquire the peripheral instance (will panic if already acquired)
-/// let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-///
-/// // Write some value to the register.
-/// write_reg!(stm32ral::gpio, gpioa, ODR, 1<<3);
-///
-/// // Write values to specific fields. Unspecified fields are written to 0.
-/// write_reg!(stm32ral::gpio, gpioa, MODER, MODER3: Output, MODER4: Analog);
-///
-/// // Unsafe access without requiring you to first `take()` the instance
-/// unsafe { write_reg!(stm32ral::gpio, GPIOA, MODER, MODER3: Output, MODER4: Analog) };
-/// # }
-/// ```
-///
-/// 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.
-///
-/// In both cases, the first arguments are:
-/// * the path to the peripheral module: `stm32ral::gpio`,
-/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
-/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
-/// `*const 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
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// // Turn on PA3 (and turn everything else off).
-/// write_reg!(stm32ral::gpio, gpioa, ODR, 1<<3);
-/// # }
-/// ```
-///
-/// Otherwise, the remaining arguments are each `Field: Value` pairs:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// // Set PA3 to Output, PA4 to Analog, and everything else to 0 (which is Input).
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// write_reg!(stm32ral::gpio, gpioa, MODER, MODER3: 0b01, MODER4: 0b11);
-/// # }
-/// ```
-/// For fields with annotated values, you can also specify a named value:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// // As above, but with named values.
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// write_reg!(stm32ral::gpio, gpioa, MODER, MODER3: Output, MODER4: Analog);
-/// # }
-/// ```
-///
-/// This macro expands to calling `(*$instance).$register.write(value)`,
-/// where in the second usage, the value is computed as the bitwise OR of
-/// each field value, which are masked and shifted appropriately for the given field.
-/// The named values are brought into scope by `use $peripheral::$register::$field::*` for
-/// each field. The same constants could just be specified manually:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// // As above, but being explicit about named values.
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// write_reg!(stm32ral::gpio, gpioa, MODER, MODER3: stm32ral::gpio::MODER::MODER3::RW::Output,
-/// MODER4: stm32ral::gpio::MODER::MODER4::RW::Analog);
-/// # }
-/// ```
-///
-/// The fully expanded form is equivalent to:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// // As above, but expanded.
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// (*gpioa).MODER.write(
-/// ((stm32ral::gpio::MODER::MODER3::RW::Output << stm32ral::gpio::MODER::MODER3::offset)
-/// & stm32ral::gpio::MODER::MODER3::mask)
-/// |
-/// ((stm32ral::gpio::MODER::MODER4::RW::Analog << stm32ral::gpio::MODER::MODER4::offset)
-/// & stm32ral::gpio::MODER::MODER4::mask)
-/// );
-/// # }
-/// ```
-///
-/// # Safety
-/// This macro will require an unsafe function or block when used with an UnsafeRWRegister,
-/// but not if used with RWRegister.
-///
-/// When run in an unsafe context, peripheral instances are directly accessible without requiring
-/// having called `take()` beforehand:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// unsafe { write_reg!(stm32ral::gpio, GPIOA, MODER, MODER3: Output, MODER4: Analog) };
-/// # }
-/// ```
-/// This works because `GPIOA` is a `*const RegisterBlock` in the `stm32ral::gpio` module;
-/// and the macro brings such constants into scope and then dereferences the provided reference.
+/// Write to a register.
#[macro_export]
macro_rules! write_reg {
- ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $( $field:ident : $value:expr ),+ $(,)? ) => {{
- #[allow(unused_imports)]
- use $periph::{*};
- #[allow(unused_imports)]
- (*$instance).$reg $([$offset])*.write(
+ ( $(::)? $($periph:ident)::+, $instance:expr, $( $(.)? $reg:ident $([$offset:expr])* )+, $( $field:ident : $value:expr ),+ $(,)? ) => {{
+ use $($periph)::+ $( :: $reg )* as __register;
+
+ $crate::write_reg!($($periph)::+, $instance, $(. $reg $([$offset])* )*,
$({
- use $periph::{$reg::$field::{W::*, RW::*}};
- ($value << { use $periph::{$reg::$field::offset}; offset })
- & { use $periph::{$reg::$field::mask}; mask }
+ #[allow(unused_imports)]
+ use __register::{$field::vals::{*}};
+ const { assert!(__register::$field::access.is_write()); }
+ ($value << __register::$field::offset)
+ & __register::$field::mask
}) | *
);
}};
- ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $value:expr ) => {{
+ ( $(::)? $($periph:ident)::+, $instance:expr, $( $(.)? $reg:ident $([$offset:expr])* )+, $value:expr ) => {{
#[allow(unused_imports)]
- use $periph::{*};
- (*$instance).$reg $([$offset])*.write($value);
+ use $($periph)::+ $( :: $reg )* as __register;
+ const { assert!(__register::access.is_write()); }
+
+ $crate::must_be_ral_registers_Inst(&$instance);
+ $crate::__expand_unsafe!($instance, $( $( $offset ,)* )*);
+ #[allow(unused_unsafe)]
+ unsafe { (&raw mut (*$instance.as_ptr())$(. $reg $([$offset])* )*).write_volatile($value) }
}};
}
-/// Modify a RWRegister or UnsafeRWRegister.
-///
-/// # Examples
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// // Safely acquire the peripheral instance (will panic if already acquired)
-/// let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-///
-/// // Update the register to ensure bit 3 is set.
-/// modify_reg!(stm32ral::gpio, gpioa, ODR, |reg| reg | (1<<3));
-///
-/// // Write values to specific fields. Unspecified fields are left unchanged.
-/// modify_reg!(stm32ral::gpio, gpioa, MODER, MODER3: Output, MODER4: Analog);
-///
-/// // Unsafe access without requiring you to first `take()` the instance
-/// unsafe { modify_reg!(stm32ral::gpio, GPIOA, MODER, MODER3: Output, MODER4: Analog) };
-/// # }
-/// ```
-///
-/// 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.
-///
-/// In both cases, the first arguments are:
-/// * the path to the peripheral module: `stm32ral::gpio`,
-/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
-/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
-/// `*const 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:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// // Turn on PA3 without affecting anything else.
-/// modify_reg!(stm32ral::gpio, gpioa, ODR, |reg| reg | (1<<3));
-/// # }
-/// ```
-///
-/// Otherwise, the remaining arguments are `Field: Value` pairs:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// // Set PA3 to Output, PA4 to Analog, and leave everything else unchanged.
-/// modify_reg!(stm32ral::gpio, gpioa, MODER, MODER3: 0b01, MODER4: 0b11);
-/// # }
-/// ```
-///
-/// For fields with annotated values, you can also specify a named value:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// // As above, but with named values.
-/// modify_reg!(stm32ral::gpio, gpioa, MODER, MODER3: Output, MODER4: Analog);
-/// # }
-/// ```
-///
-/// This macro expands to calling `(*instance).register.write(value)`.
-/// When called with a closure, `(*instance).register.read()` is called, the result
-/// passed in to the closure, and the return value of the closure is used for `value`.
-/// When called with `Field: Value` arguments, the current value is read and then masked
-/// according to the specified fields, and then ORd with the OR of each field value,
-/// each masked and shifted appropriately for the field. The named values are brought into scope
-/// by `use peripheral::register::field::*` for each field. The same constants could just be
-/// specified manually:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// // As above, but being explicit about named values.
-/// modify_reg!(stm32ral::gpio, gpioa, MODER, MODER3: stm32ral::gpio::MODER::MODER3::RW::Output,
-/// MODER4: stm32ral::gpio::MODER::MODER4::RW::Analog);
-/// # }
-/// ```
-///
-/// The fully expanded form is equivalent to:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// // As above, but expanded.
-/// (*gpioa).MODER.write(
-/// (
-/// // First read the current value...
-/// (*gpioa).MODER.read()
-/// // Then AND it with an appropriate mask...
-/// &
-/// !( stm32ral::gpio::MODER::MODER3::mask | stm32ral::gpio::MODER::MODER4::mask )
-/// )
-/// // Then OR with each field value.
-/// |
-/// ((stm32ral::gpio::MODER::MODER3::RW::Output << stm32ral::gpio::MODER::MODER3::offset)
-/// & stm32ral::gpio::MODER::MODER3::mask)
-/// |
-/// ((stm32ral::gpio::MODER::MODER4::RW::Analog << stm32ral::gpio::MODER::MODER3::offset)
-/// & stm32ral::gpio::MODER::MODER3::mask)
-/// );
-/// # }
-/// ```
-///
-/// # Safety
-/// This macro will require an unsafe function or block when used with an UnsafeRWRegister,
-/// but not if used with RWRegister.
-///
-/// When run in an unsafe context, peripheral instances are directly accessible without requiring
-/// having called `take()` beforehand:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// unsafe { modify_reg!(stm32ral::gpio, GPIOA, MODER, MODER3: Output, MODER4: Analog) };
-/// # }
-/// ```
-/// This works because `GPIOA` is a `*const RegisterBlock` in the `stm32ral::gpio` module;
-/// and the macro brings such constants into scope and then dereferences the provided reference.
+/// Modify a register.
#[macro_export]
macro_rules! modify_reg {
- ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $( $field:ident : $value:expr ),+ $(,)? ) => {{
- #[allow(unused_imports)]
- use $periph::{*};
- #[allow(unused_imports)]
- (*$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 })
- & { use $periph::{$reg::$field::mask}; mask }
- }) | *
- );
+ ( $(::)? $($periph:ident)::+, $instance:expr, $( $(.)? $reg:ident $([$offset:expr])* )+, $( $field:ident : $value:expr ),+ $(,)? ) => {{
+ $crate::__expand_unsafe!($instance, $( $( $offset ,)* )*);
+ $crate::must_be_ral_registers_Inst(&$instance);
+ use $($periph)::+ $( :: $reg )* as __register;
+ const { assert!(__register::access.is_read_write()); }
+
+ #[allow(unused_unsafe)]
+ unsafe {
+ (&raw mut (*$instance.as_ptr())$(. $reg $([$offset])* )*).write_volatile(
+ (&raw const (*$instance.as_ptr())$(. $reg $([$offset])* )*).read_volatile() & const { !( $(__register::$field::mask) | * ) }
+ | $({
+ #[allow(unused_imports)]
+ use __register::{$field::vals::{*}};
+ const { assert!(__register::$field::access.is_read_write()); }
+ ($value << __register::$field::offset )
+ & __register::$field::mask
+ }) | *
+ )
+ }
}};
- ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $fn:expr ) => {{
+ ( $(::)? $($periph:ident)::+, $instance:expr, $( $(.)? $reg:ident $([$offset:expr])* )+, $fn:expr ) => {{
#[allow(unused_imports)]
- use $periph::{*};
- (*$instance).$reg $([$offset])*.write($fn((*$instance).$reg $([$offset])*.read()));
+ use $($periph)::+ $( :: $reg )* as __register;
+ const { assert!(__register::access.is_read_write()); }
+
+ $crate::__expand_unsafe!($instance, $( $( $offset ,)* )*);
+ $crate::must_be_ral_registers_Inst(&$instance);
+ #[allow(unused_unsafe)]
+ unsafe {
+ (&raw mut (*$instance.as_ptr())$(. $reg $([$offset])* )*).write_volatile(
+ $fn((&raw const (*$instance.as_ptr())$(. $reg $([$offset])* )*).read_volatile())
+ )
+ }
}};
}
-/// Read the value from a RORegister, RWRegister, UnsafeRORegister, or UnsafeRWRegister.
-///
-/// # Examples
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// // Safely acquire the peripheral instance (will panic if already acquired)
-/// let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-///
-/// // Read the whole register.
-/// let val = read_reg!(stm32ral::gpio, gpioa, IDR);
-///
-/// // Read one field from the register.
-/// let val = read_reg!(stm32ral::gpio, gpioa, IDR, IDR2);
-///
-/// // Read multiple fields from the register.
-/// let (val1, val2, val3) = read_reg!(stm32ral::gpio, gpioa, IDR, IDR0, IDR1, IDR2);
-///
-/// // Check if one field is equal to a specific value, with the field's named values in scope.
-/// while read_reg!(stm32ral::gpio, gpioa, IDR, IDR2 == High) {}
-///
-/// // Unsafe access without requiring you to first `take()` the instance
-/// let val = unsafe { read_reg!(stm32ral::gpio, GPIOA, IDR) };
-/// # }
-/// ```
-///
-/// 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.
-///
-/// In all cases, the first arguments are:
-/// * the path to the peripheral module: `stm32ral::gpio`,
-/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
-/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
-/// `*const 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
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// // Read the entire value of GPIOA.IDR into `val`.
-/// let val = read_reg!(stm32ral::gpio, gpioa, IDR);
-/// # }
-/// ```
-///
-/// For reading individual fields, the macro masks and shifts appropriately:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// // Read just the value of the field GPIOA.IDR2 into `val`.
-/// let val = read_reg!(stm32ral::gpio, gpioa, IDR, IDR2);
-///
-/// // As above, but expanded for exposition:
-/// let val = ((*gpioa).IDR.read() & stm32ral::gpio::IDR::IDR2::mask)
-/// >> stm32ral::gpio::IDR::IDR2::offset;
-///
-/// // Read multiple fields
-/// let (val1, val2) = read_reg!(stm32ral::gpio, gpioa, IDR, IDR2, IDR3);
-///
-/// // As above, but expanded for exposition:
-/// let (val1, val2) = { let val = (*gpioa).IDR.read();
-/// ((val & stm32ral::gpio::IDR::IDR2::mask) >> stm32ral::gpio::IDR::IDR2::offset,
-/// (val & stm32ral::gpio::IDR::IDR3::mask) >> stm32ral::gpio::IDR::IDR3::offset,
-/// )};
-/// # }
-/// ```
-///
-/// For comparing a single field, the macro masks and shifts and then performs the comparison:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// # let rcc = stm32ral::rcc::RCC::take().unwrap();
-/// // Loop while PA2 is High.
-/// while read_reg!(stm32ral::gpio, gpioa, IDR, IDR2 == High) {}
-///
-/// // Only proceed if the clock is not the HSI.
-/// if read_reg!(stm32ral::rcc, rcc, CFGR, SWS != HSI) { }
-///
-/// // Equivalent expansion:
-/// if (((*rcc).CFGR.read() & stm32ral::rcc::CFGR::SWS::mask)
-/// >> stm32ral::rcc::CFGR::SWS::offset) != stm32ral::rcc::CFGR::SWS::R::HSI { }
-/// # }
-/// ```
-///
-/// # Safety
-/// This macro will require an unsafe function or block when used with an UnsafeRWRegister or
-/// UnsafeRORegister, but not if used with RWRegister, or RORegister.
-///
-/// When run in an unsafe context, peripheral instances are directly accessible without requiring
-/// having called `take()` beforehand:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// let val = unsafe { read_reg!(stm32ral::gpio, GPIOA, MODER) };
-/// # }
-/// ```
-/// This works because `GPIOA` is a `*const RegisterBlock` in the `stm32ral::gpio` module;
-/// and the macro brings such constants into scope and then dereferences the provided reference.
+/// Read the value from a register.
#[macro_export]
macro_rules! read_reg {
- ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $( $field:ident ),+ $(,)? ) => {{
- #[allow(unused_imports)]
- use $periph::{*};
- let val = ((*$instance).$reg $([$offset])*.read());
+ ( $(::)? $($periph:ident)::+, $instance:expr, $( $(.)? $reg:ident $([$offset:expr])* )+, $( $field:ident ),+ $(,)? ) => {{
+ let __value = $crate::read_reg!($($periph)::+, $instance, $(. $reg $([$offset])* )*);
+ use $($periph)::+ $( :: $reg )* as __register;
+
( $({
#[allow(unused_imports)]
- use $periph::{$reg::$field::{mask, offset, R::*, RW::*}};
- (val & mask) >> offset
+ use __register::$field::vals::{*};
+ const { assert!(__register::$field::access.is_read()); }
+ (__value & __register::$field::mask) >> __register::$field::offset
}) , *)
}};
- ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])*, $field:ident $($cmp:tt)* ) => {{
- #[allow(unused_imports)]
- use $periph::{*};
+ ( $(::)? $($periph:ident)::+, $instance:expr, $( $(.)? $reg:ident $([$offset:expr])* )+, $field:ident $($cmp:tt)* ) => {{
+ use $($periph)::+ $( :: $reg )* as __register;
+
#[allow(unused_imports)]
- use $periph::{$reg::$field::{mask, offset, R::*, RW::*}};
- (((*$instance).$reg $([$offset])*.read() & mask) >> offset) $($cmp)*
+ use __register::$field::vals::{*};
+ const { assert!(__register::$field::access.is_read()); }
+
+ (($crate::read_reg!($($periph)::+, $instance, $(. $reg $([$offset])* )* ) & __register::$field::mask) >> __register::$field::offset) $($cmp)*
}};
- ( $periph:path, $instance:expr, $reg:ident $([$offset:expr])* ) => {{
+ ( $(::)? $($periph:ident)::+, $instance:expr, $( $(.)? $reg:ident $([$offset:expr])* )+ ) => {{
#[allow(unused_imports)]
- use $periph::{*};
- ((*$instance).$reg $([$offset])*.read())
+ use $($periph)::+ $( :: $reg )* as __register;
+ const { assert!(__register::access.is_read()); }
+
+ $crate::__expand_unsafe!($instance, $( $( $offset ,)* )*);
+ $crate::must_be_ral_registers_Inst(&$instance);
+ #[allow(unused_unsafe)]
+ unsafe {
+ (&raw const (*$instance.as_ptr())$(. $reg $([$offset])* )*).read_volatile()
+ }
}};
}
-/// Reset a RWRegister, UnsafeRWRegister, WORegister, or UnsafeWORegister to its reset value.
-///
-/// # Examples
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// // Safely acquire the peripheral instance (will panic if already acquired)
-/// let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-///
-/// // Reset PA14 and PA15 to their reset state
-/// reset_reg!(stm32ral::gpio, gpioa, GPIOA, MODER, MODER14, MODER15);
-///
-/// // Reset the entire GPIOA.MODER to its reset state
-/// reset_reg!(stm32ral::gpio, gpioa, GPIOA, MODER);
-/// # }
-/// ```
-///
-/// 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
-/// reset values.
-///
-/// In both cases, the first arguments are:
-/// * the path to the peripheral module: `stm32ral::gpio`,
-/// * a reference to the instance of that peripheral: 'gpioa' (anything which dereferences to
-/// `RegisterBlock`, such as `Instance`, `&Instance`, `&RegisterBlock`, or
-/// `*const RegisterBlock`),
-/// * the module for the instance of that peripheral: `GPIOA`,
-/// * 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
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// // Reset the entire GPIOA.MODER
-/// reset_reg!(stm32ral::gpio, gpioa, GPIOA, MODER);
-/// # }
-/// ```
-///
-/// Otherwise, the remaining arguments are each field names:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// # let gpioa = stm32ral::gpio::GPIOA::take().unwrap();
-/// // Reset the JTAG pins
-/// reset_reg!(stm32ral::gpio, gpioa, GPIOA, MODER, MODER13, MODER14, MODER15);
-/// reset_reg!(stm32ral::gpio, gpioa, GPIOB, MODER, MODER3, MODER4);
-/// # }
-/// ```
-///
-/// The second form is only available to RWRegister and UnsafeRWRegister, since `.read()` is
-/// not available for WORegister and UnsafeWORegister.
-///
-/// This macro expands to calling `(*$instance).$register.write(value)`, where
-/// `value` is either the register's reset value, or the current read value of the register
-/// masked appropriately and combined with the reset value for each field.
-///
-/// # Safety
-/// This macro will require an unsafe function or block when used with an UnsafeRWRegister or
-/// UnsafeRORegister, but not if used with RWRegister or RORegister.
-///
-/// When run in an unsafe context, peripheral instances are directly accessible without requiring
-/// having called `take()` beforehand:
-/// ```rust,no_run
-/// # use stm32ral::{read_reg, write_reg, modify_reg, reset_reg}; fn main() {
-/// unsafe { reset_reg!(stm32ral::gpio, GPIOA, GPIOA, MODER) };
-/// # }
-/// ```
-/// This works because `GPIOA` is a `*const RegisterBlock` in the `stm32ral::gpio` module;
-/// and the macro brings such constants into scope and then dereferences the provided reference.
-///
-/// Note that the second argument is a `*const` and the third is a path; despite both being written
-/// `GPIOA` they are not the same thing.
+/// Access for registers and fields.
+pub enum Access {
+ /// Read-only access.
+ RO,
+ /// Write-only access.
+ WO,
+ /// Read-write access.
+ RW,
+}
+
+impl Access {
+ /// Returns `true` if the access is read.
+ #[inline(always)]
+ #[must_use]
+ pub const fn is_read(self) -> bool {
+ matches!(self, Self::RO | Self::RW)
+ }
+ /// Returns `true` if the access is write.
+ #[inline(always)]
+ #[must_use]
+ pub const fn is_write(self) -> bool {
+ matches!(self, Self::WO | Self::RW)
+ }
+ /// Returns `true` if the access is read and write.
+ #[inline(always)]
+ #[must_use]
+ pub const fn is_read_write(self) -> bool {
+ matches!(self, Self::RW)
+ }
+}
+
#[macro_export]
-macro_rules! reset_reg {
- ( $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 $([$offset])*.write({
- let resetmask: u32 = $({ use $periph::{$reg::$field::mask}; mask }) | *;
- ((*$instance).$reg $([$offset])*.read() & !resetmask) | (reset.$reg & resetmask)
- });
- }};
- ( $periph:path, $instance:expr, $instancemod:path, $reg:ident $([$offset:expr])*) => {{
- #[allow(unused_imports)]
- use $periph::{*};
- use $periph::{$instancemod::{reset}};
- (*$instance).$reg $([$offset])*.write(reset.$reg);
- }};
+macro_rules! register {
+ (
+ $(#[$register_meta:meta])*
+ $vis:vis $register_name:ident <$ty:ty> $register_access:ident [$(
+ $(#[$field_meta:meta])*
+ $field_name:ident start($start_bit:expr) width($width:expr) $field_access:ident {$(
+ $(#[$enum_meta:meta])*
+ $enum_name:ident = $enum_val:expr $(,)?
+ )*}
+ )*]
+ ) => {
+ $(#[$register_meta])*
+ #[allow(non_snake_case, non_upper_case_globals)]
+ $vis mod $register_name {
+ #[doc(hidden)]
+ pub const access: $crate::Access = $crate::Access::$register_access;
+ $(
+ $(#[$field_meta])*
+ #[allow(non_snake_case)]
+ pub mod $field_name {
+ #[doc(hidden)]
+ pub const offset: $ty = $start_bit;
+ #[doc(hidden)]
+ pub const mask: $ty = ((1 << $width) - 1) << $start_bit;
+ #[doc(hidden)]
+ pub const access: $crate::Access = $crate::Access::$field_access;
+
+ #[doc(hidden)]
+ pub mod vals {
+ $(
+ $(#[$enum_meta])*
+ pub const $enum_name: $ty = $enum_val;
+ )*
+ }
+
+ #[doc(inline)]
+ pub use self::vals::*;
+ }
+ )*
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! instances {
+ (unsafe { $(
+ $(#[$doc:meta])*
+ $vis:vis $name:ident<$block:ty> = $base_address:expr;
+ )* }) => {$(
+ $(#[$doc])*
+ #[allow(non_snake_case)]
+ #[inline(always)]
+ $vis const unsafe fn $name() -> $crate::Instance<$block> {
+ // Safety: Macro caller swears that the $base_address points
+ // to the given kind of register block.
+ unsafe { $crate::Instance::new_unchecked($base_address as _) }
+ }
+ )*};
}
diff --git a/tests/macros.rs b/tests/macros.rs
index 7f84221..211c7bc 100644
--- a/tests/macros.rs
+++ b/tests/macros.rs
@@ -1,414 +1,511 @@
-//! 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;
+use ral_registers::{self as ral, Instance};
/// 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 MY_SCALAR: u32,
+ pub MY_ARRAY: [u32; 3],
+ pub MY_2D_ARRAY: [[u32; 3]; 3],
+ pub MY_BLOCK: MY_BLOCK::RegisterBlock,
+ pub MY_BLOCKS: [MY_BLOCK::RegisterBlock; 4],
}
- 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 {}
+ ral_registers::register!(pub MY_SCALAR<u32> RW [
+ FIELD_A start(0) width(7) RW {}
+ /// Field B documentation.
+ ///
+ /// It's a field.
+ FIELD_B start(27) width(2) RW {
+ /// This is some magic number.
+ ///
+ /// It's three.
+ MAGIC = 0b11,
}
- }
+ ]);
- /// 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 MY_2D_ARRAY {
+ pub use super::MY_SCALAR::*;
}
- pub mod INST {
- pub const reset: super::ResetValues = super::ResetValues {
- MY_SCALAR: 42,
- MY_ARRAY: 42,
- };
- }
-}
+ pub mod MY_BLOCK {
+ #[repr(C)]
+ pub struct RegisterBlock {
+ pub BLOCK_SCALAR: u32,
+ pub BLOCK_ARRAY: [u32; 4],
+ pub BLOCK_SUBBLOCKS: [[BLOCK_SUBBLOCKS::RegisterBlock; 2]; 2],
+ }
-fn register_block() -> periph::RegisterBlock {
- // Safety: bitpattern of zero is fine.
- use std::mem::MaybeUninit;
- unsafe { MaybeUninit::zeroed().assume_init() }
-}
+ ral_registers::register!(pub BLOCK_SCALAR<u32> RW [
+ BLOCK_FIELD_A start(0) width(7) RW {}
+ BLOCK_FIELD_B start(27) width(2) RW { MAGIC = 0b11 }
+ ]);
-struct Instance<'a> {
- rb: &'a periph::RegisterBlock,
-}
+ pub mod BLOCK_ARRAY {
+ pub use super::BLOCK_SCALAR::*;
+ }
-impl<'a> Instance<'a> {
- fn new(rb: &'a periph::RegisterBlock) -> Self {
- Self { rb }
+ pub mod BLOCK_SUBBLOCKS {
+ #[repr(C)]
+ pub struct RegisterBlock {
+ pub SUBBLOCK_SCALAR: u32,
+ pub SUBBLOCK_ARRAY: [u32; 3],
+ }
+
+ ral_registers::register!(pub SUBBLOCK_SCALAR<u32> RW [
+ SUBBLOCK_FIELD_A start(0) width(7) RW {}
+ SUBBLOCK_FIELD_B start(27) width(2) RW { MAGIC = 0b11 }
+
+ ]);
+ pub mod SUBBLOCK_ARRAY {
+ pub use super::SUBBLOCK_SCALAR::*;
+ }
+ }
}
-}
-impl std::ops::Deref for Instance<'_> {
- type Target = periph::RegisterBlock;
- fn deref(&self) -> &Self::Target {
- self.rb
+ pub mod MY_BLOCKS {
+ pub use super::MY_BLOCK::*;
}
}
-/// 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,), 0x7F, "Individual field read with trailing comma");
-
- 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");
- };
+fn zeroed() -> periph::RegisterBlock {
+ unsafe { std::mem::MaybeUninit::zeroed().assume_init() }
}
#[test]
-fn read_scalar_ref() {
- let rb = register_block();
- read_reg_test_cases!(&rb, MY_SCALAR);
-}
+fn read_scalar() {
+ let mut block = zeroed();
+ block.MY_SCALAR = 4;
-#[test]
-fn read_scalar_deref() {
- let rb = register_block();
- let inst = Instance::new(&rb);
- read_reg_test_cases!(inst, MY_SCALAR);
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ assert_eq!(ral::read_reg!(periph, inst, MY_SCALAR), 4);
}
#[test]
-fn read_scalar_ptr() {
- let ptr: *const _ = &register_block();
- unsafe {
- read_reg_test_cases!(ptr, MY_SCALAR);
- }
-}
+fn read_array() {
+ let mut block = zeroed();
+ block.MY_ARRAY[1] = 4;
-#[test]
-fn read_array_ref() {
- let rb = register_block();
- read_reg_test_cases!(&rb, MY_ARRAY[1]);
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ assert_eq!(ral::read_reg!(periph, inst, MY_ARRAY[1]), 4);
}
#[test]
-fn read_array_deref() {
- let rb = register_block();
- let inst = Instance::new(&rb);
- read_reg_test_cases!(inst, MY_ARRAY[1]);
-}
+fn read_2d_array() {
+ let mut block = zeroed();
+ block.MY_2D_ARRAY[1][1] = 4;
-#[test]
-fn read_array_ptr() {
- let ptr: *const _ = &register_block();
- unsafe {
- read_reg_test_cases!(ptr, MY_ARRAY[1]);
- }
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ assert_eq!(ral::read_reg!(periph, inst, MY_2D_ARRAY[1][1]), 4);
}
-#[should_panic]
#[test]
-fn read_array_out_of_bounds() {
- let rb = register_block();
- ral::read_reg!(periph, &rb, MY_ARRAY[42]);
-}
+fn read_block_scalar() {
+ let mut block = zeroed();
+ block.MY_BLOCK.BLOCK_SCALAR = 4;
-/// 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,);
- assert_eq!((*$instance).$register $([$offset])*.read(), 0x7F, "write with trailing comma");
-
- 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);
- }
- };
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ assert_eq!(ral::read_reg!(periph, inst, MY_BLOCK.BLOCK_SCALAR), 4);
}
#[test]
-fn write_scalar_ref() {
- let rb = register_block();
- write_reg_test_cases!(&rb, MY_SCALAR);
+fn read_block_array() {
+ let mut block = zeroed();
+ block.MY_BLOCK.BLOCK_ARRAY[1] = 4;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ assert_eq!(ral::read_reg!(periph, inst, MY_BLOCK.BLOCK_ARRAY[1]), 4);
}
#[test]
-fn write_scalar_deref() {
- let rb = register_block();
- let inst = Instance::new(&rb);
- write_reg_test_cases!(inst, MY_SCALAR);
+fn read_block_subblock_array() {
+ let mut block = zeroed();
+ block.MY_BLOCK.BLOCK_SUBBLOCKS[1][1].SUBBLOCK_SCALAR = 4;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ assert_eq!(
+ ral::read_reg!(periph, inst, MY_BLOCK.BLOCK_SUBBLOCKS[1][1].SUBBLOCK_SCALAR),
+ 4
+ );
}
#[test]
-fn write_scalar_ptr() {
- let ptr: *const _ = &register_block();
- unsafe {
- write_reg_test_cases!(ptr, MY_SCALAR);
- }
+fn read_scalar_fields() {
+ let mut block = zeroed();
+ block.MY_SCALAR = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+
+ let a = ral::read_reg!(periph, &&&&&&&&&&inst, MY_SCALAR, FIELD_A);
+ assert_eq!(a, 0x7F);
+
+ let b = ral::read_reg!(periph, inst, MY_SCALAR, FIELD_B);
+ assert_eq!(b, 0b11);
+
+ let (a, b) = ral::read_reg!(periph, inst, MY_SCALAR, FIELD_A, FIELD_B);
+ assert_eq!(a, 0x7F);
+ assert_eq!(b, 0b11);
}
#[test]
-fn write_array_ref() {
- let rb = register_block();
- write_reg_test_cases!(&rb, MY_ARRAY[1]);
+fn read_array_fields() {
+ let mut block = zeroed();
+ block.MY_ARRAY[1] = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+
+ let a = ral::read_reg!(periph, inst, MY_ARRAY[1], FIELD_A);
+ assert_eq!(a, 0x7F);
+
+ let b = ral::read_reg!(periph, inst, MY_ARRAY[1], FIELD_B);
+ assert_eq!(b, 0b11);
+
+ let (a, b) = ral::read_reg!(periph, inst, MY_ARRAY[1], FIELD_A, FIELD_B);
+ assert_eq!(a, 0x7F);
+ assert_eq!(b, 0b11);
}
#[test]
-fn write_array_deref() {
- let rb = register_block();
- let inst = Instance::new(&rb);
- write_reg_test_cases!(inst, MY_ARRAY[1]);
+fn read_2d_array_fields() {
+ let mut block = zeroed();
+ block.MY_2D_ARRAY[1][1] = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+
+ let a = ral::read_reg!(::self::periph, inst, MY_2D_ARRAY[1][1], FIELD_A);
+ assert_eq!(a, 0x7F);
+
+ let b = ral::read_reg!(periph, inst, MY_2D_ARRAY[1][1], FIELD_B);
+ assert_eq!(b, 0b11);
+
+ let (a, b) = ral::read_reg!(periph, inst, MY_2D_ARRAY[1][1], FIELD_A, FIELD_B);
+ assert_eq!(a, 0x7F);
+ assert_eq!(b, 0b11);
}
#[test]
-fn write_array_ptr() {
- let ptr: *const _ = &register_block();
- unsafe {
- write_reg_test_cases!(ptr, MY_ARRAY[1]);
- }
+fn read_block_scalar_fields() {
+ let mut block = zeroed();
+ block.MY_BLOCK.BLOCK_SCALAR = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+
+ let a = ral::read_reg!(periph, inst, MY_BLOCK.BLOCK_SCALAR, BLOCK_FIELD_A);
+ assert_eq!(a, 0x7F);
+
+ let b = ral::read_reg!(periph, inst, MY_BLOCK.BLOCK_SCALAR, BLOCK_FIELD_B);
+ assert_eq!(b, 0b11);
+
+ let (a, b) = ral::read_reg!(
+ periph,
+ inst,
+ MY_BLOCK.BLOCK_SCALAR,
+ BLOCK_FIELD_A,
+ BLOCK_FIELD_B
+ );
+ assert_eq!(a, 0x7F);
+ assert_eq!(b, 0b11);
}
-#[should_panic]
#[test]
-fn write_array_out_of_bounds() {
- let rb = register_block();
- ral::write_reg!(periph, &rb, MY_ARRAY[42], 7);
-}
+fn read_block_array_fields() {
+ let mut block = zeroed();
+ block.MY_BLOCK.BLOCK_ARRAY[1] = u32::MAX;
-/// 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: u32::MIN,);
- assert_eq!((*$instance).$register $([$offset])*.read(), (0b11 << 27), "RMW with trailing comma");
-
- 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);
- }
- };
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+
+ let a = ral::read_reg!(periph, inst, MY_BLOCK.BLOCK_ARRAY[1], BLOCK_FIELD_A);
+ assert_eq!(a, 0x7F);
+
+ let b = ral::read_reg!(periph, inst, MY_BLOCK.BLOCK_ARRAY[1], BLOCK_FIELD_B);
+ assert_eq!(b, 0b11);
+
+ let (a, b) = ral::read_reg!(
+ periph,
+ inst,
+ MY_BLOCK.BLOCK_ARRAY[1],
+ BLOCK_FIELD_A,
+ BLOCK_FIELD_B
+ );
+ assert_eq!(a, 0x7F);
+ assert_eq!(b, 0b11);
}
#[test]
-fn modify_scalar_ref() {
- let rb = register_block();
- modify_reg_test_cases!(&rb, MY_SCALAR);
+fn read_block_subblock_scalar_fields() {
+ let mut block = zeroed();
+ block.MY_BLOCK.BLOCK_SUBBLOCKS[1][1].SUBBLOCK_SCALAR = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+
+ let a = ral::read_reg!(
+ periph,
+ inst,
+ MY_BLOCK.BLOCK_SUBBLOCKS[1][1].SUBBLOCK_SCALAR,
+ SUBBLOCK_FIELD_A
+ );
+ assert_eq!(a, 0x7F);
+
+ let b = ral::read_reg!(
+ periph,
+ inst,
+ MY_BLOCK.BLOCK_SUBBLOCKS[1][1].SUBBLOCK_SCALAR,
+ SUBBLOCK_FIELD_B
+ );
+ assert_eq!(b, 0b11);
+
+ let (a, b) = ral::read_reg!(
+ periph,
+ inst,
+ MY_BLOCK.BLOCK_SUBBLOCKS[1][1].SUBBLOCK_SCALAR,
+ SUBBLOCK_FIELD_A,
+ SUBBLOCK_FIELD_B
+ );
+ assert_eq!(a, 0x7F);
+ assert_eq!(b, 0b11);
}
#[test]
-fn modify_scalar_deref() {
- let rb = register_block();
- let inst = Instance::new(&rb);
- modify_reg_test_cases!(inst, MY_SCALAR);
+fn read_blocks_subblock_array_fields() {
+ let mut block = zeroed();
+ block.MY_BLOCKS[0].BLOCK_SUBBLOCKS[1][1].SUBBLOCK_ARRAY[0] = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+
+ let a = ral::read_reg!(
+ periph,
+ inst,
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[1][1].SUBBLOCK_ARRAY[0],
+ SUBBLOCK_FIELD_A
+ );
+ assert_eq!(a, 0x7F);
+
+ let b = ral::read_reg!(
+ periph,
+ inst,
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[1][1].SUBBLOCK_ARRAY[0],
+ SUBBLOCK_FIELD_B
+ );
+ assert_eq!(b, 0b11);
+
+ let (a, b) = ral::read_reg!(
+ periph,
+ inst,
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[1][1].SUBBLOCK_ARRAY[0],
+ SUBBLOCK_FIELD_A,
+ SUBBLOCK_FIELD_B
+ );
+ assert_eq!(a, 0x7F);
+ assert_eq!(b, 0b11);
}
#[test]
-fn modify_scalar_ptr() {
- let ptr: *const _ = &register_block();
- unsafe {
- modify_reg_test_cases!(ptr, MY_SCALAR);
- }
+fn read_scalar_field_cmp() {
+ let mut block = zeroed();
+ block.MY_SCALAR = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ assert!(ral::read_reg!(periph, inst, MY_SCALAR, FIELD_A == 0x7f));
+ assert!(ral::read_reg!(periph, inst, MY_SCALAR, FIELD_B == MAGIC));
}
#[test]
-fn modify_array_ref() {
- let rb = register_block();
- modify_reg_test_cases!(&rb, MY_ARRAY[1]);
+fn read_array_field_cmp() {
+ let mut block = zeroed();
+ block.MY_ARRAY[1] = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ assert!(ral::read_reg!(periph, inst, MY_ARRAY[1], FIELD_A == 0x7f));
+ assert!(ral::read_reg!(periph, inst, MY_ARRAY[1], FIELD_B == MAGIC));
}
#[test]
-fn modify_array_deref() {
- let rb = register_block();
- let inst = Instance::new(&rb);
- modify_reg_test_cases!(inst, MY_ARRAY[1]);
+fn read_2d_array_field_cmp() {
+ let mut block = zeroed();
+ block.MY_2D_ARRAY[0][1] = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ assert!(ral::read_reg!(
+ periph,
+ inst,
+ MY_2D_ARRAY[0][1],
+ FIELD_A == 0x7f
+ ));
+ assert!(ral::read_reg!(
+ periph,
+ inst,
+ MY_2D_ARRAY[0][1],
+ FIELD_B == MAGIC
+ ));
}
#[test]
-fn modify_array_ptr() {
- let ptr: *const _ = &register_block();
- unsafe {
- modify_reg_test_cases!(ptr, MY_ARRAY[1]);
- }
+fn read_blocks_subblock_array_fields_cmp() {
+ let mut block = zeroed();
+ block.MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0] = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ assert!(ral::read_reg!(
+ periph,
+ inst,
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ SUBBLOCK_FIELD_A == 0x7f
+ ));
+ assert!(ral::read_reg!(
+ periph,
+ inst,
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ SUBBLOCK_FIELD_B == MAGIC
+ ));
}
-#[should_panic]
#[test]
-fn modify_array_out_of_bounds() {
- let rb = register_block();
- ral::modify_reg!(periph, &rb, MY_ARRAY[42], |_| 7);
-}
+fn write_blocks_subblock_array() {
+ let mut block = zeroed();
-/// Base test cases for reset_reg.
-///
-/// 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,);
- assert_eq!((*$instance).$register $([$offset])*.read(), u32::MAX & !(0b11 << 27), "With trailing comma");
-
-
- (*$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"
- );
- };
-}
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ ral::write_reg!(
+ periph,
+ inst,
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ 42
+ );
-#[test]
-fn reset_scalar_ref() {
- let rb = register_block();
- reset_reg_test_cases!(&rb, MY_SCALAR);
+ assert_eq!(
+ block.MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ 42
+ );
}
#[test]
-fn reset_scalar_deref() {
- let rb = register_block();
- let inst = Instance::new(&rb);
- reset_reg_test_cases!(inst, MY_SCALAR);
+fn write_blocks_subblock_array_fields() {
+ let mut block = zeroed();
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+
+ ral::write_reg!(
+ periph,
+ &&&&&&inst,
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ SUBBLOCK_FIELD_A: u32::MAX,
+ SUBBLOCK_FIELD_B: u32::MAX,
+ );
+
+ assert_eq!(
+ block.MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ (0b11 << 27) | 0x7f
+ );
+
+ let get_inst = || inst;
+
+ ral::write_reg!(
+ periph,
+ get_inst(),
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ SUBBLOCK_FIELD_A: u32::MAX,
+ );
+
+ assert_eq!(
+ block.MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ 0x7f
+ );
+
+ ral::write_reg!(
+ crate::periph,
+ &&&inst,
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ SUBBLOCK_FIELD_B: MAGIC,
+ );
+
+ assert_eq!(
+ block.MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ 0b11 << 27
+ );
}
#[test]
-fn reset_scalar_ptr() {
- let ptr: *const _ = &register_block();
- unsafe {
- reset_reg_test_cases!(ptr, MY_SCALAR);
- }
-}
+fn modify_blocks_subblock_array() {
+ let mut block = zeroed();
+ block.MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0] = u32::MAX;
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ ral::modify_reg!(
+ periph,
+ &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&inst,
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ |v| {
+ let x = !0xAAAA_AAAA;
+ v & x
+ }
+ );
-#[test]
-fn reset_array_ref() {
- let rb = register_block();
- reset_reg_test_cases!(&rb, MY_ARRAY[1]);
+ assert_eq!(
+ block.MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ 0x5555_5555
+ );
}
#[test]
-fn reset_array_deref() {
- let rb = register_block();
- let inst = Instance::new(&rb);
- reset_reg_test_cases!(inst, MY_ARRAY[1]);
+fn modify_blocks_subblock_array_fields() {
+ let mut block = zeroed();
+
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+ ral::modify_reg!(
+ ::periph,
+ inst,
+ MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ SUBBLOCK_FIELD_A: u32::MAX,
+ SUBBLOCK_FIELD_B: MAGIC,
+ );
+
+ assert_eq!(
+ block.MY_BLOCKS[0].BLOCK_SUBBLOCKS[0][0].SUBBLOCK_ARRAY[0],
+ (0b11 << 27) | 0x7F,
+ );
}
#[test]
-fn reset_array_ptr() {
- let ptr: *const _ = &register_block();
- unsafe {
- reset_reg_test_cases!(ptr, MY_ARRAY[1]);
- }
+#[deny(warnings)]
+fn mask_offset_access_shadowing() {
+ let mut block = zeroed();
+ let inst = unsafe { Instance::new_unchecked(&mut block) };
+
+ let mask = 1;
+ let offset = 2;
+ let access = 3;
+
+ ral::write_reg!(::periph, inst, MY_SCALAR, FIELD_A: (mask | offset | access));
+ ral::modify_reg!(::periph, inst, MY_ARRAY[0], FIELD_A: mask, FIELD_B: offset);
+ ral::modify_reg!(::periph, inst, MY_SCALAR, FIELD_A: access);
}
-#[should_panic]
#[test]
-fn reset_array_out_of_bounds() {
- let rb = register_block();
- ral::reset_reg!(periph, &rb, INST, MY_ARRAY[42]);
+#[deny(warnings)]
+fn macro_unsafe_inline() {
+ let mut block = zeroed();
+ ral::read_reg!(
+ periph,
+ unsafe { Instance::new_unchecked(&mut block) },
+ MY_SCALAR
+ );
+ ral::write_reg!(
+ periph,
+ unsafe { Instance::new_unchecked(&mut block) },
+ MY_SCALAR,
+ 5
+ );
+ ral::modify_reg!(
+ periph, unsafe { Instance::new_unchecked(&mut block) }, MY_SCALAR, FIELD_A: 5
+ );
+ ral::modify_reg!(
+ periph,
+ unsafe { Instance::new_unchecked(&mut block) },
+ MY_SCALAR,
+ |_| 5
+ );
}
diff --git a/tests/pathological.rs b/tests/pathological.rs
deleted file mode 100644
index d463e29..0000000
--- a/tests/pathological.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-//! 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);
-}