diff --git a/riscv/CHANGELOG.md b/riscv/CHANGELOG.md index 98d1b7b4..04080506 100644 --- a/riscv/CHANGELOG.md +++ b/riscv/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `Mstatus` vector extension support - Add fallible counterparts to all functions that `panic` - Add `riscv-pac` as a dependency +- Add CSR-defining macros to create in-memory types ### Fixed diff --git a/riscv/Cargo.toml b/riscv/Cargo.toml index 35bc7fd8..ccde8ed3 100644 --- a/riscv/Cargo.toml +++ b/riscv/Cargo.toml @@ -29,3 +29,4 @@ critical-section = "1.1.2" embedded-hal = "1.0.0" riscv-pac = { path = "../riscv-pac", version = "0.2.0" } riscv-macros = { path = "macros", version = "0.1.0", optional = true } +paste = "1.0.15" diff --git a/riscv/src/lib.rs b/riscv/src/lib.rs index 6290c18c..9f44cb73 100644 --- a/riscv/src/lib.rs +++ b/riscv/src/lib.rs @@ -35,8 +35,10 @@ #![no_std] #![allow(clippy::missing_safety_doc)] +pub use paste::paste; + pub mod asm; -pub(crate) mod bits; +pub mod bits; pub mod delay; pub mod interrupt; pub mod register; diff --git a/riscv/src/register.rs b/riscv/src/register.rs index e99264b5..be788310 100644 --- a/riscv/src/register.rs +++ b/riscv/src/register.rs @@ -107,6 +107,9 @@ pub mod minstreth; mod mhpmeventx; pub use self::mhpmeventx::*; +#[cfg(test)] +mod tests; + // TODO: Debug/Trace Registers (shared with Debug Mode) // TODO: Debug Mode Registers diff --git a/riscv/src/register/macros.rs b/riscv/src/register/macros.rs index 1153dedc..1aeb082d 100644 --- a/riscv/src/register/macros.rs +++ b/riscv/src/register/macros.rs @@ -600,3 +600,431 @@ macro_rules! clear_pmp { } }; } + +/// Helper macro to define a CSR type. +/// +/// This macro creates a type represents a CSR register, without defining any bitfields. +/// +/// It is mainly used by [read_write_csr](crate::read_write_csr), +/// [read_only_csr](crate::read_only_csr), and [write_only_csr](crate::write_only_csr) macros. +#[macro_export] +macro_rules! csr { + ($(#[$doc:meta])* + $ty:ident, + $mask:literal) => { + #[repr(C)] + $(#[$doc])* + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct $ty { + bits: usize, + } + + impl $ty { + /// Bitmask for legal bitfields of the CSR type. + pub const BITMASK: usize = $mask; + + /// Creates a new CSR type from raw bits value. + /// + /// Only bits in the [BITMASK](Self::BITMASK) will be set. + pub const fn from_bits(bits: usize) -> Self { + Self { bits: bits & $mask } + } + + /// Gets the raw bits value. + pub const fn bits(&self) -> usize { + self.bits & $mask + } + + /// Gets the bitmask for the CSR type's bitfields. + pub const fn bitmask(&self) -> usize { + Self::BITMASK + } + } + }; +} + +#[macro_export] +macro_rules! csr_field_enum { + ($(#[$field_ty_doc:meta])* + $field_ty:ident { + range: [$field_start:literal : $field_end:literal], + default: $default_variant:ident, + $($variant:ident = $value:expr$(,)?)+ + }$(,)? + ) => { + $(#[$field_ty_doc])* + #[repr(usize)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub enum $field_ty { + $($variant = $value),+ + } + + impl $field_ty { + /// Creates a new field variant. + pub const fn new() -> Self { + Self::$default_variant + } + + /// Attempts to convert a [`usize`] into a valid variant. + pub const fn from_usize(val: usize) -> $crate::result::Result { + match val { + $($value => Ok(Self::$variant),)+ + _ => Err($crate::result::Error::InvalidVariant(val)), + } + } + + /// Converts the variant into a [`usize`]. + pub const fn into_usize(self) -> usize { + self as usize + } + } + + impl Default for $field_ty { + fn default() -> Self { + Self::new() + } + } + + impl From<$field_ty> for usize { + fn from(val: $field_ty) -> Self { + val.into_usize() + } + } + + impl TryFrom for $field_ty { + type Error = $crate::result::Error; + + fn try_from(val: usize) -> $crate::result::Result { + Self::from_usize(val) + } + } + }; +} + +/// Helper macro to create a read-write CSR type. +/// +/// The type allows to read the CSR value into memory, and update the field values in-memory. +/// +/// The user can then write the entire bitfield value back into the CSR with a single write. +#[macro_export] +macro_rules! read_write_csr { + ($(#[$doc:meta])+ + $ty:ident: $csr:tt, + mask: $mask:tt$(,)? + ) => { + $crate::csr!($(#[$doc])+ $ty, $mask); + + $crate::read_csr_as!($ty, $csr); + $crate::write_csr_as!($ty, $csr); + }; +} + +/// Helper macro to create a read-only CSR type. +/// +/// The type allows to read the CSR value into memory. +#[macro_export] +macro_rules! read_only_csr { + ($(#[$doc:meta])+ + $ty:ident: $csr:tt, + mask: $mask:tt$(,)? + ) => { + $crate::csr! { $(#[$doc])+ $ty, $mask } + + $crate::read_csr_as!($ty, $csr); + }; +} + +/// Helper macro to create a read-only CSR type. +/// +/// The type allows to read the CSR value into memory. +#[macro_export] +macro_rules! write_only_csr { + ($(#[$doc:meta])+ + $ty:ident: $csr:literal, + mask: $mask:literal$(,)? + ) => { + $crate::csr! { $(#[$doc])+ $ty, $mask } + + $crate::write_csr_as!($ty, $csr); + }; +} + +/// Defines field accesor functions for a read-write CSR type. +#[macro_export] +macro_rules! read_write_csr_field { + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident: $bit:literal$(,)? + ) => { + $crate::paste! { + $crate::read_only_csr_field!( + $ty, + $(#[$field_doc])+ + $field: $bit, + ); + + $crate::write_only_csr_field!( + $ty, + $(#[$field_doc])+ + []: $bit, + ); + } + }; + + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident: $bit_start:literal ..= $bit_end:literal$(,)? + ) => { + $crate::paste! { + $crate::read_only_csr_field!( + $ty, + $(#[$field_doc])+ + $field: $bit_start ..= $bit_end, + ); + + $crate::write_only_csr_field!( + $ty, + $(#[$field_doc])+ + []: $bit_start ..= $bit_end, + ); + } + }; + + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident: [$bit_start:literal : $bit_end:literal]$(,)? + ) => { + $crate::paste! { + $crate::read_only_csr_field!( + $ty, + $(#[$field_doc])+ + $field: [$bit_start : $bit_end], + ); + + $crate::write_only_csr_field!( + $ty, + $(#[$field_doc])+ + []: [$bit_start : $bit_end], + ); + } + }; + + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident, + $field_ty:ident: [$field_start:literal : $field_end:literal], + ) => { + $crate::paste! { + $crate::read_only_csr_field!( + $ty, + $(#[$field_doc])+ + $field, + $field_ty: [$field_start : $field_end], + ); + + $crate::write_only_csr_field!( + $ty, + $(#[$field_doc])+ + [], + $field_ty: [$field_start : $field_end], + ); + } + }; +} + +/// Defines field accesor functions for a read-only CSR type. +#[macro_export] +macro_rules! read_only_csr_field { + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident: $bit:literal$(,)?) => { + const _: () = assert!($bit < usize::BITS); + + impl $ty { + $(#[$field_doc])+ + #[inline] + pub fn $field(&self) -> bool { + $crate::bits::bf_extract(self.bits, $bit, 1) != 0 + } + } + }; + + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident: $bit_start:literal..=$bit_end:literal$(,)?) => { + const _: () = assert!($bit_end < usize::BITS); + const _: () = assert!($bit_start < $bit_end); + + $crate::paste! { + impl $ty { + $(#[$field_doc])+ + #[inline] + pub fn $field(&self, index: usize) -> bool { + self.[](index).unwrap() + } + + $(#[$field_doc])+ + #[inline] + pub fn [](&self, index: usize) -> $crate::result::Result { + if ($bit_start..=$bit_end).contains(&index) { + Ok($crate::bits::bf_extract(self.bits, index, 1) != 0) + } else { + Err($crate::result::Error::IndexOutOfBounds { + index, + min: $bit_start, + max: $bit_end, + }) + } + } + } + } + }; + + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident: [$bit_start:literal : $bit_end:literal]$(,)?) => { + const _: () = assert!($bit_end < usize::BITS); + const _: () = assert!($bit_start < $bit_end); + + impl $ty { + $(#[$field_doc])+ + #[inline] + pub fn $field(&self) -> usize { + $crate::bits::bf_extract(self.bits, $bit_start, $bit_end - $bit_start + 1) + } + } + }; + + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident, + $field_ty:ident: [$field_start:literal : $field_end:literal]$(,)? + ) => { + const _: () = assert!($field_end < usize::BITS); + const _: () = assert!($field_start <= $field_end); + + $crate::paste! { + impl $ty { + $(#[$field_doc])+ + #[inline] + pub fn $field(&self) -> $field_ty { + self.[]().unwrap() + } + + $(#[$field_doc])+ + #[inline] + pub fn [](&self) -> $crate::result::Result<$field_ty> { + let value = $crate::bits::bf_extract( + self.bits, + $field_start, + $field_end - $field_start + 1, + ); + + $field_ty::from_usize(value) + } + } + } + }; +} + +/// Defines field accesor functions for a write-only CSR type. +#[macro_export] +macro_rules! write_only_csr_field { + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident: $bit:literal$(,)?) => { + const _: () = assert!($bit < usize::BITS); + + impl $ty { + $(#[$field_doc])+ + #[doc = ""] + #[doc = "**NOTE**: only updates in-memory values, does not write to CSR."] + #[inline] + pub fn $field(&mut self, $field: bool) { + self.bits = $crate::bits::bf_insert(self.bits, $bit, 1, $field as usize); + } + } + }; + + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident: $bit_start:literal..=$bit_end:literal$(,)?) => { + const _: () = assert!($bit_end < usize::BITS); + const _: () = assert!($bit_start < $bit_end); + + $crate::paste! { + impl $ty { + $(#[$field_doc])+ + #[doc = ""] + #[doc = "**NOTE**: only updates in-memory values, does not write to CSR."] + #[inline] + pub fn $field(&mut self, index: usize, $field: bool) { + self.[](index, $field).unwrap(); + } + + $(#[$field_doc])+ + #[doc = ""] + #[doc = "**NOTE**: only updates in-memory values, does not write to CSR."] + #[inline] + pub fn [](&mut self, index: usize, $field: bool) -> $crate::result::Result<()> { + if ($bit_start..=$bit_end).contains(&index) { + self.bits = $crate::bits::bf_insert(self.bits, index, 1, $field as usize); + Ok(()) + } else { + Err($crate::result::Error::IndexOutOfBounds { + index, + min: $bit_start, + max: $bit_end, + }) + } + } + } + } + }; + + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident: [$bit_start:literal : $bit_end:literal]$(,)?) => { + const _: () = assert!($bit_end < usize::BITS); + const _: () = assert!($bit_start < $bit_end); + + impl $ty { + $(#[$field_doc])+ + #[doc = ""] + #[doc = "**NOTE**: only updates in-memory values, does not write to CSR."] + #[inline] + pub fn $field(&mut self, $field: usize) { + self.bits = $crate::bits::bf_insert( + self.bits, + $bit_start, + $bit_end - $bit_start + 1, + $field, + ); + } + } + }; + + ($ty:ident, + $(#[$field_doc:meta])+ + $field:ident, + $field_ty:ident: [$field_start:literal : $field_end:literal]$(,)? + ) => { + const _: () = assert!($field_end < usize::BITS); + const _: () = assert!($field_start <= $field_end); + + impl $ty { + $(#[$field_doc])+ + #[doc = ""] + #[doc = "**NOTE**: only updates in-memory values, does not write to CSR."] + #[inline] + pub fn $field(&mut self, $field: $field_ty) { + self.bits = $crate::bits::bf_insert( + self.bits, + $field_start, + $field_end - $field_start + 1, + $field.into(), + ); + } + } + }; +} diff --git a/riscv/src/register/mcountinhibit.rs b/riscv/src/register/mcountinhibit.rs index 60d32af3..daa71c67 100644 --- a/riscv/src/register/mcountinhibit.rs +++ b/riscv/src/register/mcountinhibit.rs @@ -1,100 +1,36 @@ //! `mcountinhibit` register -use crate::bits::{bf_extract, bf_insert}; use crate::result::{Error, Result}; -/// `mcountinhibit` register -#[derive(Clone, Copy, Debug)] -pub struct Mcountinhibit { - bits: usize, +read_write_csr! { + /// `mcountinhibit` register + Mcountinhibit: 0x320, + mask: 0xffff_fffd, } -impl Mcountinhibit { - /// Machine "cycle\[h\]" Disable - #[inline] - pub fn cy(&self) -> bool { - bf_extract(self.bits, 0, 1) != 0 - } - - /// Sets whether to inhibit the "cycle\[h\]" counter. - /// - /// Only updates the in-memory value, does not modify the `mcountinhibit` register. - #[inline] - pub fn set_cy(&mut self, cy: bool) { - self.bits = bf_insert(self.bits, 0, 1, cy as usize); - } - - /// Machine "instret\[h\]" Disable - #[inline] - pub fn ir(&self) -> bool { - bf_extract(self.bits, 2, 1) != 0 - } - - /// Sets whether to inhibit the "instret\[h\]" counter. - /// - /// Only updates the in-memory value, does not modify the `mcountinhibit` register. - #[inline] - pub fn set_ir(&mut self, ir: bool) { - self.bits = bf_insert(self.bits, 2, 1, ir as usize); - } - - /// Machine "hpm\[x\]" Disable (bits 3-31) - #[inline] - pub fn hpm(&self, index: usize) -> bool { - assert!((3..32).contains(&index)); - bf_extract(self.bits, index, 1) != 0 - } +set!(0x320); +clear!(0x320); - /// Machine "hpm\[x\]" Disable (bits 3-31) - /// - /// Attempts to read the "hpm\[x\]" value, and returns an error if the index is invalid. - #[inline] - pub fn try_hpm(&self, index: usize) -> Result { - if (3..32).contains(&index) { - Ok(bf_extract(self.bits, index, 1) != 0) - } else { - Err(Error::IndexOutOfBounds { - index, - min: 3, - max: 31, - }) - } - } +read_write_csr_field! { + Mcountinhibit, + /// Gets the `cycle[h]` inhibit field value. + cy: 0, +} - /// Sets whether to inhibit the "hpm\[X\]" counter. - /// - /// Only updates the in-memory value, does not modify the `mcountinhibit` register. - #[inline] - pub fn set_hpm(&mut self, index: usize, hpm: bool) { - assert!((3..32).contains(&index)); - self.bits = bf_insert(self.bits, index, 1, hpm as usize); - } +read_write_csr_field! { + Mcountinhibit, + /// Gets the `instret[h]` inhibit field value. + ir: 2, +} - /// Sets whether to inhibit the "hpm\[X\]" counter. - /// - /// Only updates the in-memory value, does not modify the `mcountinhibit` register. +read_write_csr_field! { + Mcountinhibit, + /// Gets the `mhpmcounterX[h]` inhibit field value. /// - /// Attempts to update the "hpm\[x\]" value, and returns an error if the index is invalid. - #[inline] - pub fn try_set_hpm(&mut self, index: usize, hpm: bool) -> Result<()> { - if (3..32).contains(&index) { - self.bits = bf_insert(self.bits, index, 1, hpm as usize); - Ok(()) - } else { - Err(Error::IndexOutOfBounds { - index, - min: 3, - max: 31, - }) - } - } + /// **WARN**: `index` must be in the range `[31:3]`. + hpm: 3..=31, } -read_csr_as!(Mcountinhibit, 0x320); -write_csr_as!(Mcountinhibit, 0x320); -set!(0x320); -clear!(0x320); - set_clear_csr!( /// Machine cycle Disable , set_cy, clear_cy, 1 << 0); diff --git a/riscv/src/register/tests.rs b/riscv/src/register/tests.rs new file mode 100644 index 00000000..639fd0df --- /dev/null +++ b/riscv/src/register/tests.rs @@ -0,0 +1,3 @@ +mod read_only_csr; +mod read_write_csr; +mod write_only_csr; diff --git a/riscv/src/register/tests/read_only_csr.rs b/riscv/src/register/tests/read_only_csr.rs new file mode 100644 index 00000000..477e884e --- /dev/null +++ b/riscv/src/register/tests/read_only_csr.rs @@ -0,0 +1,121 @@ +use crate::result::{Error, Result}; + +read_only_csr! { + /// test CSR register type + Mtest: 0x000, + mask: 0b1111_1111_1111, +} + +read_only_csr_field! { + Mtest, + /// test single-bit field + single: 0, +} + +read_only_csr_field! { + Mtest, + /// multiple single-bit field range + multi_range: 1..=3, +} + +read_only_csr_field! { + Mtest, + /// multi-bit field + multi_field: [4:7], +} + +csr_field_enum! { + /// field enum type with valid field variants + MtestFieldEnum { + range: [8:11], + default: Field1, + Field1 = 1, + Field2 = 2, + Field3 = 3, + Field4 = 15, + } +} + +read_only_csr_field! { + Mtest, + /// multi-bit field + field_enum, + MtestFieldEnum: [8:11], +} + +// we don't test the `read` function, we are only testing in-memory functions. +#[allow(unused)] +pub fn _read_csr() -> Mtest { + read() +} + +#[allow(unused)] +pub fn _try_read_csr() -> Result { + try_read() +} + +#[test] +fn test_mtest_read_only() { + let mut mtest = Mtest::from_bits(0); + + assert_eq!(mtest.bitmask(), Mtest::BITMASK); + assert_eq!(mtest.bits(), 0); + + // check that single bit field getter/setters work. + assert!(!mtest.single()); + + mtest = Mtest::from_bits(1); + assert!(mtest.single()); + + mtest = Mtest::from_bits(0); + + // check that single bit range field getter/setters work. + for i in 1..=3 { + assert!(!mtest.multi_range(i)); + assert_eq!(mtest.try_multi_range(i), Ok(false)); + + mtest = Mtest::from_bits(1 << i); + assert!(mtest.multi_range(i)); + assert_eq!(mtest.try_multi_range(i), Ok(true)); + + mtest = Mtest::from_bits(0 << i); + assert!(!mtest.multi_range(i)); + assert_eq!(mtest.try_multi_range(i), Ok(false)); + } + + // check that multi-bit field getter/setters work. + assert_eq!(mtest.multi_field(), 0); + + mtest = Mtest::from_bits(0xf << 4); + assert_eq!(mtest.multi_field(), 0xf); + + mtest = Mtest::from_bits(0x3 << 4); + assert_eq!(mtest.multi_field(), 0x3); + + // check that only bits in the field are set. + mtest = Mtest::from_bits(0xff << 4); + assert_eq!(mtest.multi_field(), 0xf); + assert_eq!(mtest.bits(), 0xff << 4); + + mtest = Mtest::from_bits(0x0 << 4); + assert_eq!(mtest.multi_field(), 0x0); + + assert_eq!(mtest.try_field_enum(), Err(Error::InvalidVariant(0)),); + + [ + MtestFieldEnum::Field1, + MtestFieldEnum::Field2, + MtestFieldEnum::Field3, + MtestFieldEnum::Field4, + ] + .into_iter() + .for_each(|variant| { + mtest = Mtest::from_bits(variant.into_usize() << 8); + assert_eq!(mtest.field_enum(), variant); + assert_eq!(mtest.try_field_enum(), Ok(variant)); + }); + + // check that setting an invalid variant returns `None` + mtest = Mtest::from_bits(0xbad << 8); + assert_eq!(mtest.try_field_enum(), Err(Error::InvalidVariant(13)),); +} diff --git a/riscv/src/register/tests/read_write_csr.rs b/riscv/src/register/tests/read_write_csr.rs new file mode 100644 index 00000000..bcad6478 --- /dev/null +++ b/riscv/src/register/tests/read_write_csr.rs @@ -0,0 +1,129 @@ +use crate::result::{Error, Result}; + +read_write_csr! { + /// test CSR register type + Mtest: 0x000, + mask: 0b1111_1111_1111, +} + +read_write_csr_field! { + Mtest, + /// test single-bit field + single: 0, +} + +read_write_csr_field! { + Mtest, + /// multiple single-bit field range + multi_range: 1..=3, +} + +read_write_csr_field! { + Mtest, + /// multi-bit field + multi_field: [4:7], +} + +csr_field_enum! { + /// field enum type with valid field variants + MtestFieldEnum { + range: [8:11], + default: Field1, + Field1 = 1, + Field2 = 2, + Field3 = 3, + Field4 = 15, + } +} + +read_write_csr_field! { + Mtest, + /// multi-bit field + field_enum, + MtestFieldEnum: [8:11], +} + +// we don't test the `read` and `write` functions, we are only testing in-memory functions. +#[allow(unused)] +pub fn _read_csr() -> Mtest { + read() +} + +#[allow(unused)] +pub fn _try_read_csr() -> Result { + try_read() +} + +#[allow(unused)] +pub fn _write_csr(csr: Mtest) { + write(csr); +} + +#[allow(unused)] +pub fn _try_write_csr(csr: Mtest) { + try_write(csr); +} + +#[test] +fn test_mtest_read_write() { + let mut mtest = Mtest::from_bits(0); + + assert_eq!(mtest.bitmask(), Mtest::BITMASK); + assert_eq!(mtest.bits(), 0); + + // check that single bit field getter/setters work. + assert!(!mtest.single()); + + mtest.set_single(true); + assert!(mtest.single()); + + mtest.set_single(false); + assert!(!mtest.single()); + + // check that single bit range field getter/setters work. + for i in 1..=3 { + assert!(!mtest.multi_range(i)); + + mtest.set_multi_range(i, true); + assert!(mtest.multi_range(i)); + + mtest.set_multi_range(i, false); + assert!(!mtest.multi_range(i)); + } + + // check that multi-bit field getter/setters work. + assert_eq!(mtest.multi_field(), 0); + + mtest.set_multi_field(0xf); + assert_eq!(mtest.multi_field(), 0xf); + + mtest.set_multi_field(0x3); + assert_eq!(mtest.multi_field(), 0x3); + + // check that only bits in the field are set. + mtest.set_multi_field(0xff); + assert_eq!(mtest.multi_field(), 0xf); + assert_eq!(mtest.bits(), 0xf << 4); + + mtest.set_multi_field(0x0); + assert_eq!(mtest.multi_field(), 0x0); + + assert_eq!(mtest.try_field_enum(), Err(Error::InvalidVariant(0))); + + [ + MtestFieldEnum::Field1, + MtestFieldEnum::Field2, + MtestFieldEnum::Field3, + MtestFieldEnum::Field4, + ] + .into_iter() + .for_each(|variant| { + mtest.set_field_enum(variant); + assert_eq!(mtest.field_enum(), variant); + assert_eq!(mtest.try_field_enum(), Ok(variant)); + }); + + // check that setting an invalid variant returns `None` + mtest = Mtest::from_bits(0xbad << 8); + assert_eq!(mtest.try_field_enum(), Err(Error::InvalidVariant(13))); +} diff --git a/riscv/src/register/tests/write_only_csr.rs b/riscv/src/register/tests/write_only_csr.rs new file mode 100644 index 00000000..4c550f55 --- /dev/null +++ b/riscv/src/register/tests/write_only_csr.rs @@ -0,0 +1,119 @@ +use crate::result::{Error, Result}; + +write_only_csr! { + /// test CSR register type + Mtest: 0x000, + mask: 0b1111_1111_1111, +} + +write_only_csr_field! { + Mtest, + /// setter test single-bit field + set_single: 0, +} + +write_only_csr_field! { + Mtest, + /// setter multiple single-bit field range + set_multi_range: 1..=3, +} + +write_only_csr_field! { + Mtest, + /// setter multi-bit field + set_multi_field: [4:7], +} + +csr_field_enum! { + /// field enum type with valid field variants + MtestFieldEnum { + range: [8:11], + default: Field1, + Field1 = 1, + Field2 = 2, + Field3 = 3, + Field4 = 15, + } +} + +write_only_csr_field! { + Mtest, + /// setter multi-bit field + set_field_enum, + MtestFieldEnum: [8:11], +} + +// we don't test the `write` function, we are only testing in-memory functions. +#[allow(unused)] +pub fn _write_csr(csr: Mtest) { + write(csr); +} + +#[allow(unused)] +pub fn _try_write_csr(csr: Mtest) -> Result<()> { + try_write(csr) +} + +#[test] +fn test_mtest_write_only() { + let mut mtest = Mtest::from_bits(0); + + assert_eq!(mtest.bitmask(), Mtest::BITMASK); + assert_eq!(mtest.bits(), 0); + + // check that single bit field getter/setters work. + mtest.set_single(true); + assert_eq!(mtest.bits(), 1); + + mtest.set_single(false); + assert_eq!(mtest.bits(), 0); + + // check that single bit range field getter/setters work. + for i in 1..=3 { + mtest.set_multi_range(i, true); + assert_ne!(mtest.bits() & (1 << i), 0); + + mtest.set_multi_range(i, false); + assert_eq!(mtest.bits() & (1 << i), 0); + } + + // check that multi-bit field getter/setters work. + mtest.set_multi_field(0xf); + assert_eq!(mtest.bits() >> 4, 0xf); + + mtest.set_multi_field(0x3); + assert_eq!(mtest.bits() >> 4, 0x3); + + // check that only bits in the field are set. + mtest.set_multi_field(0xff); + assert_eq!(mtest.bits() >> 4, 0xf); + + mtest.set_multi_field(0x0); + assert_eq!(mtest.bits() >> 4, 0x0); + + mtest = Mtest::from_bits(0); + + assert_eq!( + MtestFieldEnum::from_usize(mtest.bits() >> 8), + Err(Error::InvalidVariant(0)) + ); + + [ + MtestFieldEnum::Field1, + MtestFieldEnum::Field2, + MtestFieldEnum::Field3, + MtestFieldEnum::Field4, + ] + .into_iter() + .for_each(|variant| { + mtest.set_field_enum(variant); + assert_eq!(MtestFieldEnum::from_usize(mtest.bits() >> 8), Ok(variant)); + }); + + // check that setting an invalid variant returns `None` + mtest = Mtest::from_bits(0xbad << 8); + assert_eq!( + MtestFieldEnum::from_usize(mtest.bits() >> 8), + Err(Error::InvalidVariant(13)) + ); +}