The ARM Cortex-M series of processors are 32-bit RISC CPUs designed for embedded and IoT applications. They are known for their low power consumption, good performance, and relative simplicity compared to more complex architectures. One key aspect of the Cortex-M stack is its alignment requirements.
In computer architecture, alignment refers to the positioning of data in memory. Certain data types have alignment requirements, meaning they need to be located at specific memory addresses. This is driven by the processor architecture and how it accesses memory. Adhering to alignment rules ensures data access is efficient.
Why Stack Alignment Matters
The call stack on Cortex-M processors must follow certain alignment rules for performance and reliability reasons. Specifically, the stack pointer must always be aligned to a 32-bit boundary. This means the least significant 2 bits of the stack pointer must be 00. For example, addresses 0x20000000, 0x20001000, 0x20002000 would all be valid stack pointer values.
Enforcing stack alignment brings several benefits:
- Allows efficient stack operations like PUSH and POP. The processor can access aligned stack data using 32-bit transfers.
- Avoids costly unaligned memory access exceptions. Cortex-M3 and M4 cores do not support unaligned accesses.
- Prevents faults and crashes related to misaligned stack data.
- Improves context switching speed between threads and exception handlers.
Overall, maintaining 32-bit stack alignment makes the system more robust and deterministic. It eliminates situations where misalignment could cause unwanted processor faults or slowdowns. So it is a critical correctness and performance consideration in Cortex-M embedded programming.
How the Stack Alignment Is Enforced
The Cortex-M processor and linker script take care of aligning the stack automatically in most cases. Here’s how it works:
- The VTOR register points to the vector table, which must be aligned to 0x100 byte addresses.
- The initial stack pointer value is loaded from the second word of the vector table during reset.
- The linker script aligns the vector table and sets the initial stack pointer value accordingly.
As long as no unaligned stack writes are done, the stack remains aligned to a 32-bit boundary. But there are some cases where stack misalignment can happen:
- Writing an odd stack size value into the stack limit registers MSP or PSP.
- Interrupts or context switches preempting a misaligned stack write.
- Buggy code accidentally writing a misaligned stack address.
The Cortex-M Memory Protection Unit (MPU) can be configured to generate a fault on unaligned stack accesses. This can help detect and prevent stack misalignment issues during development.
Handling Unaligned Data
At times it may be necessary to store unaligned data, like a 16-bit integer, on the stack. This can be done safely using linker macros to pad the data to the next 32-bit address: PUSH {R0-R3} ; Push some aligned registers PUSH {R1} ; Push misaligned 16-bit R1 PUSH {R4} ; R4 gets 32-bit aligned here
The compiler also automatically pads local variables to maintain stack alignment within a function. But care must still be taken with structures and arrays to prevent misalignment across function calls.
For alignment-sensitive data like floats, the aligner attribute can be used to insert padding: float __aligned(4) myFloat;
This helps avoid splitting a float across a 32-bit boundary. In summary, the Cortex-M stack is strictly aligned for good reasons. The linker and compiler help maintain alignment, but care must be taken in code that directly manipulates the stack.
Stack Alignment and Context Switching
Stack alignment also matters for robust context switching between threads or interrupt handlers. The PSP register points to the stacked process state for a thread or handler. A misaligned PSP can cause crashes on context save/restore.
During a context switch:
- The stacked state of the current thread is stored to the aligned stack.
- The handler stores its minimal context to the stack.
- PSP is updated to point to the handler stack frame.
- After handling, PSP is restored to the stored thread stack state.
This careful save and restore sequence keeps the stack aligned. But any interrupt during steps 1-3 could potentially cause misalignment. To avoid this, pend the interrupt until the PSP switch. // Thread context save PUSH {R4-R11, LR} // Disable interrupts CPSID I // Handler pre-push PUSH {R0-R3, R12, LR} // Update PSP MOV PSP, SP // Enable interrupts CPSIE I
With interrupts disabled during the PSP switch, the stack cannot get misaligned. In summary, maintaining alignment during context switches prevents hard faults on restore. The processor does not tolerate a misaligned stack.
Checking for Stack Alignment
There are a few methods to check for stack alignment during development:
- Configure the MPU to generate unaligned access faults. This will quickly detect any code causing misalignment.
- Enable the Alignment Check Exception. The processor can trigger a fault on unaligned accesses.
- Manually check the stack pointer alignment with bitwise AND:
MOVS R0, SP // Move SP to R0 ANDS R0, #0x3 // AND with 0x3, set flags BEQ stack_aligned // Branch if Z set (aligned) BNE stack_error // Branch if not aligned
It’s a good idea to periodically check alignment within long-running loops or critical routines. Catching misalignment early prevents system crashes down the line.
Debuggers and profilers may also provide stack alignment warnings. Overall, validating alignment helps catch bugs and improves system stability. A misaligned stack is one of the quickest ways to trigger a hard fault on Cortex-M.
Maintaining Alignment in Software
Here are some software strategies to help maintain stack alignment:
- Use stack alignment aware embedded libraries and RTOSes. They will incorporate padding and alignment checks.
- Review linker scripts to ensure the initial stack pointer is aligned and placed correctly.
- Enable the alignment fault exception to catch misalignment early.
- Use stack canaries or overwrite detectors to prevent stack overflows.
- Manually check alignment after critical push/pop operations.
- Pad stack writes to maintain alignment for custom data structures.
Tight assembly code also requires diligence to prevent unaligned accesses. Follow the compiler’s lead and pad any hand-written stack writes. While not always intuitive, the Cortex-M stack rules exist for good reason. Learning these requirements and following alignment best practices will help tame hard faults and debugging headaches!
Conclusion
Proper stack alignment is a critical performance and reliability consideration on ARM Cortex-M processors. The hardware enforces 32-bit alignment for efficient access and robust exception handling. Special care must be taken when directly manipulating the stack to maintain this alignment. Tools like the MPU and linker scripts can help validate and enforce alignment. With some learning and defensive programming, the complexities of stack alignment can be managed to build stable Cortex-M systems.