From c7a9b9f3d4b9e71303c7b988d2bd916c2e4df9bc Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Tue, 2 Aug 2022 06:21:12 -0400 Subject: First commit --- src/host.rs | 822 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 822 insertions(+) create mode 100644 src/host.rs (limited to 'src/host.rs') diff --git a/src/host.rs b/src/host.rs new file mode 100644 index 0000000..f0fa512 --- /dev/null +++ b/src/host.rs @@ -0,0 +1,822 @@ +//! Host-side configurations for the target. +//! +//! See [`RuntimeBuilder::build`] to understand the linker script generation +//! steps. + +use std::{ + env, + fmt::Display, + fs::{self, File}, + io::{self, Write}, + path::PathBuf, +}; + +/// Memory partitions. +/// +/// Use with [`RuntimeBuilder`] to specify the placement of sections +/// in the final program. Note that the `RuntimeBuilder` only does limited +/// checks on memory placements. Generally, it's OK to place data in ITCM, +/// and instructions in DTCM; however, this isn't recommended for optimal +/// performance. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Memory { + /// Place the section in (external) flash. + /// + /// Reads and writes are translated into commands on an external + /// bus, like FlexSPI. + Flash, + /// Place the section in data tightly coupled memory (DTCM). + Dtcm, + /// Place the section in instruction tightly coupled memory (ITCM). + Itcm, + /// Place the section in on-chip RAM (OCRAM). + /// + /// If your chip includes dedicated OCRAM memory, the implementation + /// utilizes that OCRAM before utilizing any FlexRAM OCRAM banks. + Ocram, +} + +/// The FlexSPI peripheral that interfaces your flash chip. +/// +/// The [`RuntimeBuilder`] selects `FlexSpi1` for nearly all chip +/// families. However, it selects `FlexSpi2` for the 1064 in order +/// to utilize its on-board flash. You can override the selection +/// using [`RuntimeBuilder::flexspi()`]. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FlexSpi { + /// Interface flash using FlexSPI 1. + FlexSpi1, + /// Interface flash using FlexSPI 2. + FlexSpi2, +} + +impl FlexSpi { + fn family_default(family: Family) -> Self { + if Family::Imxrt1064 == family { + FlexSpi::FlexSpi2 + } else { + FlexSpi::FlexSpi1 + } + } + fn start_address(self, family: Family) -> Option { + match (self, family) { + // FlexSPI1, 10xx + ( + FlexSpi::FlexSpi1, + Family::Imxrt1010 + | Family::Imxrt1015 + | Family::Imxrt1020 + | Family::Imxrt1050 + | Family::Imxrt1060 + | Family::Imxrt1064, + ) => Some(0x6000_0000), + // FlexSPI2 not available on 10xx families + ( + FlexSpi::FlexSpi2, + Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050, + ) => None, + // FlexSPI 2 available on 10xx families + (FlexSpi::FlexSpi2, Family::Imxrt1060 | Family::Imxrt1064) => Some(0x7000_0000), + // 11xx support + (FlexSpi::FlexSpi1, Family::Imxrt1170) => Some(0x3000_0000), + (FlexSpi::FlexSpi2, Family::Imxrt1170) => Some(0x6000_0000), + } + } + fn supported_for_family(self, family: Family) -> bool { + self.start_address(family).is_some() + } +} + +impl Display for Memory { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Flash => f.write_str("FLASH"), + Self::Itcm => f.write_str("ITCM"), + Self::Dtcm => f.write_str("DTCM"), + Self::Ocram => f.write_str("OCRAM"), + } + } +} + +/// Define an alias for `name` that maps to a memory block named `placement`. +fn region_alias(output: &mut dyn Write, name: &str, placement: Memory) -> io::Result<()> { + writeln!(output, "REGION_ALIAS(\"REGION_{}\", {});", name, placement) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct FlashOpts { + size: usize, + flexspi: FlexSpi, +} + +/// Builder for the i.MX RT runtime. +/// +/// `RuntimeBuilder` let you assign sections to memory regions. It also lets +/// you partition FlexRAM DTCM/ITCM/OCRAM. Call [`build()`](RuntimeBuilder::build) to commit the +/// runtime configuration. +/// +/// # Behaviors +/// +/// The implementation tries to place the stack in the lowest-possible memory addresses. +/// This means the stack will grow down into reserved memory below DTCM and OCRAM for most +/// chip families. The outlier is the 1170, where the stack will grow into OCRAM backdoor for +/// the CM4 coprocessor. Be careful here... +/// +/// Similarly, the implementation tries to place the heap in the highest-possible memory +/// addresses. This means the heap will grow up into reserved memory above DTCM and OCRAM +/// for most chip families. +/// +/// The vector table requires a 1024-byte alignment. The vector table's placement is prioritized +/// above all other sections, except the stack. If placing the stack and vector table in the +/// same section (which is the default behavior), consider keeping the stack size as a multiple +/// of 1 KiB to minimize internal fragmentation. +/// +/// # Default values +/// +/// The example below demonstrates the default `RuntimeBuilder` memory placements, +/// stack sizes, and heap sizes. +/// +/// ``` +/// use imxrt_rt::{Family, RuntimeBuilder, Memory}; +/// +/// const FLASH_SIZE: usize = 16 * 1024; +/// let family = Family::Imxrt1060; +/// +/// let mut b = RuntimeBuilder::from_flexspi(family, FLASH_SIZE); +/// // FlexRAM banks represent default fuse values. +/// b.flexram_banks(family.default_flexram_banks()); +/// b.text(Memory::Itcm); // Copied from flash. +/// b.rodata(Memory::Ocram); // Copied from flash. +/// b.data(Memory::Ocram); // Copied from flash. +/// b.vectors(Memory::Dtcm); // Copied from flash. +/// b.bss(Memory::Ocram); +/// b.uninit(Memory::Ocram); +/// b.stack(Memory::Dtcm); +/// b.stack_size(8 * 1024); // 8 KiB stack. +/// b.heap(Memory::Dtcm); // Heap in DTCM... +/// b.heap_size(0); // ...but no space given to the heap. +/// +/// assert_eq!(b, RuntimeBuilder::from_flexspi(family, FLASH_SIZE)); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RuntimeBuilder { + family: Family, + flexram_banks: FlexRamBanks, + text: Memory, + rodata: Memory, + data: Memory, + vectors: Memory, + bss: Memory, + uninit: Memory, + stack: Memory, + stack_size: usize, + heap: Memory, + heap_size: usize, + flash_opts: Option, + linker_script_name: String, +} + +const DEFAULT_LINKER_SCRIPT_NAME: &str = "imxrt-link.x"; + +impl RuntimeBuilder { + /// Creates a runtime that can execute and load contents from + /// FlexSPI flash. + /// + /// `flash_size` is the size of your flash component, in bytes. + pub fn from_flexspi(family: Family, flash_size: usize) -> Self { + Self { + family, + flexram_banks: family.default_flexram_banks(), + text: Memory::Itcm, + rodata: Memory::Ocram, + data: Memory::Ocram, + vectors: Memory::Dtcm, + bss: Memory::Ocram, + uninit: Memory::Ocram, + stack: Memory::Dtcm, + stack_size: 8 * 1024, + heap: Memory::Dtcm, + heap_size: 0, + flash_opts: Some(FlashOpts { + size: flash_size, + flexspi: FlexSpi::family_default(family), + }), + linker_script_name: DEFAULT_LINKER_SCRIPT_NAME.into(), + } + } + /// Set the FlexRAM bank allocation. + /// + /// Use this to customize the sizes of DTCM, ITCM, and OCRAM. + /// See the `FlexRamBanks` documentation for requirements on the + /// bank allocations. + pub fn flexram_banks(&mut self, flexram_banks: FlexRamBanks) -> &mut Self { + self.flexram_banks = flexram_banks; + self + } + /// Set the memory placement for code. + pub fn text(&mut self, memory: Memory) -> &mut Self { + self.text = memory; + self + } + /// Set the memory placement for read-only data. + pub fn rodata(&mut self, memory: Memory) -> &mut Self { + self.rodata = memory; + self + } + /// Set the memory placement for mutable data. + pub fn data(&mut self, memory: Memory) -> &mut Self { + self.data = memory; + self + } + /// Set the memory placement for the vector table. + pub fn vectors(&mut self, memory: Memory) -> &mut Self { + self.vectors = memory; + self + } + /// Set the memory placement for zero-initialized data. + pub fn bss(&mut self, memory: Memory) -> &mut Self { + self.bss = memory; + self + } + /// Set the memory placement for uninitialized data. + pub fn uninit(&mut self, memory: Memory) -> &mut Self { + self.uninit = memory; + self + } + /// Set the memory placement for stack memory. + pub fn stack(&mut self, memory: Memory) -> &mut Self { + self.stack = memory; + self + } + /// Set the size, in bytes, of the stack. + pub fn stack_size(&mut self, bytes: usize) -> &mut Self { + self.stack_size = bytes; + self + } + /// Set the memory placement for the heap. + /// + /// Note that the default heap has no size. Use [`heap_size`](Self::heap_size) + /// to allocate space for a heap. + pub fn heap(&mut self, memory: Memory) -> &mut Self { + self.heap = memory; + self + } + /// Set the size, in bytes, of the heap. + pub fn heap_size(&mut self, bytes: usize) -> &mut Self { + self.heap_size = bytes; + self + } + /// Set the FlexSPI peripheral that interfaces flash. + /// + /// See the [`FlexSpi`] to understand the default values. + /// If this builder is not configuring a flash-loaded runtime, this + /// call is silently ignored. + pub fn flexspi(&mut self, peripheral: FlexSpi) -> &mut Self { + if let Some(flash_opts) = &mut self.flash_opts { + flash_opts.flexspi = peripheral; + } + self + } + + /// Set the name of the linker script file. + /// + /// You can use this to customize the linker script name for your users. + /// See the [crate-level documentation](crate#linker-script) for more + /// information. + pub fn linker_script_name(&mut self, name: &str) -> &mut Self { + self.linker_script_name = name.into(); + self + } + + /// Commit the runtime configuration. + /// + /// # Errors + /// + /// The implementation ensures that your chip can support the FlexRAM bank + /// allocation. An invalid allocation is signaled by an error. + /// + /// Returns an error if any of the following sections are placed in flash: + /// + /// - data + /// - vectors + /// - bss + /// - uninit + /// - stack + /// - heap + /// + /// The implementation may rely on the _linker_ to signal other errors. + /// For example, suppose a runtime configuration with no ITCM banks. If a + /// section is placed in ITCM, that error could be signaled here, or through + /// the linker. No matter the error path, the implementation ensures that there + /// will be an error. + pub fn build(&self) -> Result<(), Box> { + self.check_configurations()?; + + // Since `build` is called from a build script, the output directory + // represents the path to the _user's_ crate. + let out_dir = PathBuf::from(env::var("OUT_DIR")?); + println!("cargo:rustc-link-search={}", out_dir.display()); + + // The main linker script expects to INCLUDE this file. This file + // uses region aliases to associate region names to actual memory + // regions (see the Memory enum). + let mut memory_x = File::create(out_dir.join("imxrt-memory.x"))?; + + if let Some(flash_opts) = &self.flash_opts { + write_flash_memory_map(&mut memory_x, self.family, flash_opts, &self.flexram_banks)?; + } else { + write_ram_memory_map(&mut memory_x, self.family, &self.flexram_banks)?; + } + + #[cfg(feature = "device")] + writeln!(&mut memory_x, "INCLUDE device.x")?; + + // Keep these alias names in sync with the primary linker script. + // The main linker script uses these region aliases for placing + // sections. Then, the user specifies the actual placement through + // the builder. This saves us the step of actually generating SECTION + // commands. + region_alias(&mut memory_x, "TEXT", self.text)?; + region_alias(&mut memory_x, "VTABLE", self.vectors)?; + region_alias(&mut memory_x, "RODATA", self.rodata)?; + region_alias(&mut memory_x, "DATA", self.data)?; + region_alias(&mut memory_x, "BSS", self.bss)?; + region_alias(&mut memory_x, "UNINIT", self.uninit)?; + + region_alias(&mut memory_x, "STACK", self.stack)?; + region_alias(&mut memory_x, "HEAP", self.heap)?; + // Used in the linker script and / or target code. + writeln!(&mut memory_x, "__stack_size = {:#010X};", self.stack_size)?; + writeln!(&mut memory_x, "__heap_size = {:#010X};", self.heap_size)?; + + if self.flash_opts.is_some() { + // Runtime will see different VMA and LMA, and copy the sections. + region_alias(&mut memory_x, "LOAD_VTABLE", Memory::Flash)?; + region_alias(&mut memory_x, "LOAD_TEXT", Memory::Flash)?; + region_alias(&mut memory_x, "LOAD_RODATA", Memory::Flash)?; + region_alias(&mut memory_x, "LOAD_DATA", Memory::Flash)?; + } else { + // When the VMA and LMA are equal, the runtime performs no copies. + region_alias(&mut memory_x, "LOAD_VTABLE", self.vectors)?; + region_alias(&mut memory_x, "LOAD_TEXT", self.text)?; + region_alias(&mut memory_x, "LOAD_RODATA", self.rodata)?; + region_alias(&mut memory_x, "LOAD_DATA", self.data)?; + } + + // Referenced in target code. + writeln!( + &mut memory_x, + "__flexram_config = {:#010X};", + self.flexram_banks.config() + )?; + // The target runtime looks at this value to predicate some pre-init instructions. + // Could be helpful for binary identification, but it's an undocumented feature. + writeln!(&mut memory_x, "__imxrt_family = {};", self.family.id(),)?; + + // Place the primary linker script in the user's output directory. Name may be decided + // by the user. + let link_x = include_bytes!("host/imxrt-link.x"); + fs::write(out_dir.join(&self.linker_script_name), link_x)?; + + // Also place the boot header in the search path. Do this unconditionally (even if + // the user is booting from RAM). Name matters, since it's INCLUDEd in our linker + // scripts. + let boot_header_x = include_bytes!("host/imxrt-boot-header.x"); + fs::write(out_dir.join("imxrt-boot-header.x"), boot_header_x)?; + + Ok(()) + } + + /// Implement i.MX RT specific sanity checks. + /// + /// This might not check everything! If the linker may detect a condition, we'll + /// let the linker do that. + fn check_configurations(&self) -> Result<(), String> { + if self.family.flexram_bank_count() < self.flexram_banks.bank_count() { + return Err(format!( + "Chip {:?} only has {} total FlexRAM banks. Cannot allocate {:?}, a total of {} banks", + self.family, + self.family.flexram_bank_count(), + self.flexram_banks, + self.flexram_banks.bank_count() + )); + } + if self.flexram_banks.ocram < self.family.bootrom_ocram_banks() { + return Err(format!( + "Chip {:?} requires at least {} OCRAM banks for the bootloader ROM", + self.family, + self.family.bootrom_ocram_banks() + )); + } + if let Some(flash_opts) = &self.flash_opts { + if !flash_opts.flexspi.supported_for_family(self.family) { + return Err(format!( + "Chip {:?} does not support {:?}", + self.family, flash_opts.flexspi + )); + } + } + + fn prevent_flash(name: &str, memory: Memory) -> Result<(), String> { + if memory == Memory::Flash { + Err(format!("Section '{}' cannot be placed in flash", name)) + } else { + Ok(()) + } + } + macro_rules! prevent_flash { + ($sec:ident) => { + prevent_flash(stringify!($sec), self.$sec) + }; + } + + prevent_flash!(data)?; + prevent_flash!(vectors)?; + prevent_flash!(bss)?; + prevent_flash!(uninit)?; + prevent_flash!(stack)?; + prevent_flash!(heap)?; + + Ok(()) + } +} + +/// Write RAM-like memory blocks. +/// +/// Skips a section if there's no FlexRAM block allocated. If a user references one +/// of this skipped sections, linking fails. +fn write_flexram_memories( + output: &mut dyn Write, + family: Family, + flexram_banks: &FlexRamBanks, +) -> io::Result<()> { + if flexram_banks.itcm > 0 { + writeln!( + output, + "ITCM (RWX) : ORIGIN = 0x00000000, LENGTH = {:#X}", + flexram_banks.itcm * family.flexram_bank_size(), + )?; + } + if flexram_banks.dtcm > 0 { + writeln!( + output, + "DTCM (RWX) : ORIGIN = 0x20000000, LENGTH = {:#X}", + flexram_banks.dtcm * family.flexram_bank_size(), + )?; + } + + let ocram_size = + flexram_banks.ocram * family.flexram_bank_size() + family.dedicated_ocram_size(); + if ocram_size > 0 { + writeln!( + output, + "OCRAM (RWX) : ORIGIN = {:#X}, LENGTH = {:#X}", + family.ocram_start(), + ocram_size, + )?; + } + Ok(()) +} + +/// Generate a linker script MEMORY command that includes a FLASH block. +/// +/// If called, the linker script includes the boot header, which is also +/// expressed as a linker script. +fn write_flash_memory_map( + output: &mut dyn Write, + family: Family, + flash_opts: &FlashOpts, + flexram_banks: &FlexRamBanks, +) -> io::Result<()> { + writeln!( + output, + "/* Memory map for '{:?}' with custom flash length {}. */", + family, flash_opts.size + )?; + writeln!(output, "MEMORY {{")?; + writeln!( + output, + "FLASH (RX) : ORIGIN = {:#X}, LENGTH = {:#X}", + flash_opts + .flexspi + .start_address(family) + .expect("Already checked"), + flash_opts.size + )?; + write_flexram_memories(output, family, flexram_banks)?; + writeln!(output, "}}")?; + writeln!(output, "__fcb_offset = {:#X};", family.fcb_offset())?; + writeln!(output, "INCLUDE imxrt-boot-header.x")?; + Ok(()) +} + +/// Generate a linker script MEMORY command that supports RAM execution. +/// +/// It's like [`write_flash_memory_map`], but it doesn't include the flash +/// important tidbits. +fn write_ram_memory_map( + output: &mut dyn Write, + family: Family, + flexram_banks: &FlexRamBanks, +) -> io::Result<()> { + writeln!( + output, + "/* Memory map for '{:?}' that executes from RAM. */", + family, + )?; + writeln!(output, "MEMORY {{")?; + write_flexram_memories(output, family, flexram_banks)?; + writeln!(output, "}}")?; + Ok(()) +} + +/// i.MX RT chip family. +/// +/// Chip families are designed by reference manuals and produce categories. +/// Supply this to a [`RuntimeBuilder`] in order to check runtime configurations. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Family { + Imxrt1010, + Imxrt1015, + Imxrt1020, + Imxrt1050, + Imxrt1060, + Imxrt1064, + Imxrt1170, +} + +impl Family { + /// Family identifier. + /// + /// These values may be stored in the image and observe by the runtime + /// initialzation routine. Make sure these numbers are kept in sync with + /// any hard-coded values. + const fn id(self) -> u32 { + match self { + Family::Imxrt1010 => 1010, + Family::Imxrt1015 => 1015, + Family::Imxrt1020 => 1020, + Family::Imxrt1050 => 1050, + Family::Imxrt1060 => 1060, + Family::Imxrt1064 => 1064, + Family::Imxrt1170 => 1170, + } + } + /// How many FlexRAM banks are available? + pub const fn flexram_bank_count(self) -> u32 { + match self { + Family::Imxrt1010 | Family::Imxrt1015 => 4, + Family::Imxrt1020 => 8, + Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => 16, + // No ECC support; treating all banks as equal. + Family::Imxrt1170 => 16, + } + } + /// How large (bytes) is each FlexRAM bank? + const fn flexram_bank_size(self) -> u32 { + 32 * 1024 + } + /// How many OCRAM banks does the boot ROM need? + const fn bootrom_ocram_banks(self) -> u32 { + match self { + Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050 => 1, + // 9.5.1. memory maps point at OCRAM2. + Family::Imxrt1060 | Family::Imxrt1064 => 0, + // Boot ROM uses dedicated OCRAM1. + Family::Imxrt1170 => 0, + } + } + /// Where's the FlexSPI configuration bank located? + fn fcb_offset(self) -> usize { + match self { + Family::Imxrt1010 | Family::Imxrt1170 => 0x400, + _ => 0x000, + } + } + + /// Where does the OCRAM region begin? + /// + /// This includes dedicated any OCRAM regions, if any exist for the chip. + fn ocram_start(self) -> u32 { + if Family::Imxrt1170 == self { + // 256 KiB offset from the OCRAM M4 backdoor. + 0x2024_0000 + } else { + // Either starts the FlexRAM OCRAM banks, or the + // dedicated OCRAM regions (for supported devices). + 0x2020_0000 + } + } + + /// What's the size, in bytes, of the dedicated OCRAM section? + /// + /// This isn't supported by all chips. + const fn dedicated_ocram_size(self) -> u32 { + match self { + Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050 => 0, + Family::Imxrt1060 | Family::Imxrt1064 => 512 * 1024, + // - Two dedicated OCRAMs + // - Two dedicated OCRAM ECC regions that aren't used for ECC + // - One FlexRAM OCRAM ECC region that's strictly OCRAM, without ECC + Family::Imxrt1170 => (2 * 512 + 2 * 64 + 128) * 1024, + } + } + + /// Returns the default FlexRAM bank allocations for this chip. + /// + /// The default values represent the all-zero fuse values. + pub fn default_flexram_banks(self) -> FlexRamBanks { + match self { + Family::Imxrt1010 | Family::Imxrt1015 => FlexRamBanks { + ocram: 2, + itcm: 1, + dtcm: 1, + }, + Family::Imxrt1020 => FlexRamBanks { + ocram: 4, + itcm: 2, + dtcm: 2, + }, + Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => FlexRamBanks { + ocram: 8, + itcm: 4, + dtcm: 4, + }, + Family::Imxrt1170 => FlexRamBanks { + ocram: 0, + itcm: 8, + dtcm: 8, + }, + } + } +} + +/// FlexRAM bank allocations. +/// +/// Depending on your device, you may need a non-zero number of +/// OCRAM banks to support the boot ROM. Consult your processor's +/// reference manual for more information. +/// +/// You should keep the sum of all banks below or equal to the +/// total number of banks supported by your device. Unallocated memory +/// banks are disabled. +/// +/// Banks are typically 32KiB large. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FlexRamBanks { + /// How many banks are allocated for OCRAM? + /// + /// This may need to be non-zero to support the boot ROM. + /// Consult your reference manual. + /// + /// Note: these are FlexRAM OCRAM banks. Do not include any banks + /// that would represent dedicated OCRAM; the runtime implementation + /// allocates those automatically. In fact, if your chip includes + /// dedicated OCRAM, you may set this to zero in order to maximize + /// DTCM and ITCM utilization. + pub ocram: u32, + /// How many banks are allocated for ITCM? + pub itcm: u32, + /// How many banks are allocated for DTCM? + pub dtcm: u32, +} + +impl FlexRamBanks { + /// Total FlexRAM banks. + const fn bank_count(&self) -> u32 { + self.ocram + self.itcm + self.dtcm + } + + /// Produces the FlexRAM configuration. + fn config(&self) -> u32 { + assert!( + self.bank_count() <= 16, + "Something is wrong; this should have been checked earlier." + ); + + // If a FlexRAM memory type could be allocated + // to _all_ memory banks, these would represent + // the configuration masks... + const OCRAM: u32 = 0x5555_5555; // 0b01... + const DTCM: u32 = 0xAAAA_AAAA; // 0b10... + const ITCM: u32 = 0xFFFF_FFFF; // 0b11... + + fn mask(bank_count: u32) -> u32 { + 1u32.checked_shl(bank_count * 2) + .map(|bit| bit - 1) + .unwrap_or(u32::MAX) + } + + let ocram_mask = mask(self.ocram); + let dtcm_mask = mask(self.dtcm).checked_shl(self.ocram * 2).unwrap_or(0); + let itcm_mask = mask(self.itcm) + .checked_shl((self.ocram + self.dtcm) * 2) + .unwrap_or(0); + + (OCRAM & ocram_mask) | (DTCM & dtcm_mask) | (ITCM & itcm_mask) + } +} + +#[cfg(test)] +mod tests { + use super::FlexRamBanks; + + #[test] + fn flexram_config() { + /// Testing table of banks and expected configuration mask. + #[allow(clippy::unusual_byte_groupings)] // Spacing delimits ITCM / DTCM / OCRAM banks. + const TABLE: &[(FlexRamBanks, u32)] = &[ + ( + FlexRamBanks { + ocram: 16, + dtcm: 0, + itcm: 0, + }, + 0x55555555, + ), + ( + FlexRamBanks { + ocram: 0, + dtcm: 16, + itcm: 0, + }, + 0xAAAAAAAA, + ), + ( + FlexRamBanks { + ocram: 0, + dtcm: 0, + itcm: 16, + }, + 0xFFFFFFFF, + ), + ( + FlexRamBanks { + ocram: 0, + dtcm: 0, + itcm: 0, + }, + 0, + ), + ( + FlexRamBanks { + ocram: 1, + dtcm: 1, + itcm: 1, + }, + 0b11_10_01, + ), + ( + FlexRamBanks { + ocram: 3, + dtcm: 3, + itcm: 3, + }, + 0b111111_101010_010101, + ), + ( + FlexRamBanks { + ocram: 5, + dtcm: 5, + itcm: 5, + }, + 0b1111111111_1010101010_0101010101, + ), + ( + FlexRamBanks { + ocram: 1, + dtcm: 1, + itcm: 14, + }, + 0b1111111111111111111111111111_10_01, + ), + ( + FlexRamBanks { + ocram: 1, + dtcm: 14, + itcm: 1, + }, + 0b11_1010101010101010101010101010_01, + ), + ( + FlexRamBanks { + ocram: 14, + dtcm: 1, + itcm: 1, + }, + 0b11_10_0101010101010101010101010101, + ), + ]; + + for (banks, expected) in TABLE { + let actual = banks.config(); + assert!( + actual == *expected, + "\nActual: {actual:#034b}\nExpected: {expected:#034b}\nBanks: {banks:?}" + ); + } + } +} -- cgit v1.2.3