
FPU context switching on Cortex-M is a subtle but critical design feature that directly impacts interrupt latency and real-time determinism. The ARMv7-M and ARMv8-M architectures provide two mechanisms for handling FPU state during exceptions: lazy stacking (default) and eager state preservation. Understanding the exact hardware behavior is essential for writing efficient RTOS context switch code and performing worst-case execution time (WCET) analysis.
When a Cortex-M processor executes an FPU instruction, it sets the CONTROL.FPCA (Floating-Point Context Active) bit to 1. This indicates that the current context has active state in the FPU registers.
The ARM Architecture Procedure Call Standard (AAPCS) defines two sets of FPU registers:
S0-S15 and FPSCR (17 words). The hardware exception entry sequence manages these.S16-S31 (16 words). The hardware never automatically stacks these; software (like an RTOS) must preserve them if needed.By default, FPCCR.LSPEN (Lazy State Preservation Enable) is 1. This optimizes interrupt latency for the common case where an interrupt handler (ISR) does not use the FPU, even if the interrupted task did.
+--------------------------------------------------------------+| LAZY EXCEPTION ENTRY SEQUENCE (LSPEN=1) |+--------------------------------------------------------------+| Context: Task is running, CONTROL.FPCA = 1 (FPU active) || || 1. ISR Fires. Hardware auto-saves integer registers: || R0-R3, R12, LR, PC, xPSR || 2. Hardware allocates space on the stack for S0-S15, FPSCR || (but does NOT write the FPU registers to memory yet). || 3. Sets FPCCR.LSPACT = 1 (Deferred stacking pending). || 4. Sets FPCAR to the reserved stack address. || 5. Sets EXC_RETURN[4] = 0 (inverse of FPCA, marks extended || frame). CONTROL.FPCA itself is NOT cleared by hardware. |+--------------------------------------------------------------+
If the ISR finishes without executing any FPU instructions, LSPACT stays 1 throughout the handler. Upon exception return, the hardware detects LSPACT == 1, clears it, and simply reclaims the reserved stack space without performing any FPU memory accesses. The task’s FPU state was never overwritten in the hardware registers, saving the time and power of pushing and popping 17 words.
The “lazy” part means the FPU state push is deferred. If the ISR does execute an FPU instruction (e.g., VADD.F32), the processor must save the interrupted task’s state before the ISR corrupts it.
+--------------------------------------------------------------+| DEFERRED STACKING EXCEPTION FLOW |+--------------------------------------------------------------+| || Task Context Kernel/ISR || +------------+ +-------------------------------+ || | CONTROL. | | Exception Entry (Lazy) | || | FPCA = 1 | --> | LSPACT=1, FPCA persists | || | ... | | EXC_RETURN[4]=0 (ext. frame) | || +------------+ | | || | ... integer math ... | || | | || | VADD.F32 (First FPU Insn) | || | -> Hardware detects LSPACT=1 | || | -> Stalls, pushes S0-S15 and | || | FPSCR to address in FPCAR | || | -> Clears LSPACT = 0 | || | -> Executes VADD.F32 | || +-------------------------------+ || || Latency cost: ~17 extra cycles added to the ISR execution. || |+--------------------------------------------------------------+
Notice that the deferred stacking happens in the ISR, not when the task resumes. It adds roughly 17 cycles of latency to the execution of the ISR’s first FPU instruction.
Setting FPCCR.LSPEN = 0 forces the processor into eager state preservation.
If the interrupted task had CONTROL.FPCA == 1, exception entry will immediately and unconditionally push S0-S15 and FPSCR to the stack, costing ~17 cycles upfront.
Eager preservation is rarely needed but can be deterministic. If you have a system where every interrupt uses the FPU, forcing eager stacking avoids the deferred stacking trap logic, potentially saving a few cycles of trap overhead, though the memory bandwidth cost is identical.
(Note: If CONTROL.FPCA == 0, meaning the interrupted task wasn’t using the FPU, neither eager nor lazy stacking pushes any FPU registers).
A common misconception is that an RTOS should dynamically toggle LSPEN per-task during context switches. FPCCR is a global system register; modifying it dynamically alters the behavior of all asynchronous hardware interrupts, which is highly undesirable.
Instead, a production-grade RTOS (like FreeRTOS) leaves LSPEN=1 globally. It manages FPU context by inspecting the EXC_RETURN value during the PendSV context switch:
// Simplified representation of RTOS PendSV Context Switchvoid PendSV_Handler(void) {// 1. Hardware exception entry already pushed R0-R3, R12, LR, PC, xPSR// and optionally reserved space for S0-S15, FPSCR if FPCA was 1.// 2. Check if the outgoing task used the FPUif ((LR & 0x10) == 0) {// EXC_RETURN bit 4 is 0: Extended FPU frame is active.// 3. Manually push the callee-saved FPU registers (S16-S31).// (The hardware only handles S0-S15).vPortPushFPUCalleeRegisters();}// 4. Save outgoing task's core registers (R4-R11) and SP...// 5. Restore incoming task's SP and core registers (R4-R11)...// 6. Check if incoming task uses FPUif ((NewLR & 0x10) == 0) {// 7. Manually pop the callee-saved FPU registers (S16-S31)vPortPopFPUCalleeRegisters();}// 8. Return using NewLR (EXC_RETURN). Hardware handles popping// R0-R3 etc., and optionally S0-S15 if NewLR indicates it.__asm volatile ("bx %0" :: "r" (NewLR));}
Notice that the RTOS software must manually push/pop S16-S31. If LSPACT is still 1 when PendSV attempts to pop the incoming task’s FPU frame, the Cortex-M hardware will automatically trigger the deferred stacking of the outgoing task’s S0-S15 to its stack before popping the incoming task’s state. The hardware elegantly handles the lazy resolution!
Cortex-M FPU context switching leverages lazy stacking to avoid unnecessary memory accesses when ISRs don’t use floating-point math.
S0-S15 and FPSCR), not all 32 FPU registers.CONTROL.FPCA persists through exception entry; EXC_RETURN[4] is set to the inverse of FPCA and used to restore it on return.EXC_RETURN and software stacking of S16-S31, rather than changing global hardware behavior.Understanding these hardware details ensures you can properly analyze interrupt latencies and avoid architectural bugs when porting or writing an RTOS.
Quick Links
Legal Stuff





