HomeAbout UsContact Us

Cortex-M FPU Context Switching: Lazy vs Eager

By Jithin Tom
Published in Embedded Concepts
August 10, 2026
3 min read
Cortex-M FPU Context Switching: Lazy vs Eager

Table Of Contents

01
Hardware FPU State and Exceptions
02
Lazy Stacking Mechanism (Default)
03
The Deferred Stacking Trap
04
Eager State Preservation
05
RTOS Context Switching Reality
06
Summary
07
Related Reading
08
References
09
Frequently Asked Questions

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.

Hardware FPU State and Exceptions

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:

  • Caller-saved registers: S0-S15 and FPSCR (17 words). The hardware exception entry sequence manages these.
  • Callee-saved registers: S16-S31 (16 words). The hardware never automatically stacks these; software (like an RTOS) must preserve them if needed.

Lazy Stacking Mechanism (Default)

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 Deferred Stacking Trap

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.

Eager State Preservation

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).

RTOS Context Switching Reality

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 Switch
void 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 FPU
if ((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 FPU
if ((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!

Summary

Cortex-M FPU context switching leverages lazy stacking to avoid unnecessary memory accesses when ISRs don’t use floating-point math.

  • Hardware only auto-stacks caller-saved registers (S0-S15 and FPSCR), not all 32 FPU registers.
  • The deferred stacking trap delays the ISR (when it first uses the FPU), not the resuming task.
  • CONTROL.FPCA persists through exception entry; EXC_RETURN[4] is set to the inverse of FPCA and used to restore it on return.
  • RTOSes manage per-task FPU state using 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.

References

  1. ARM, Cortex-M4 Devices Generic User Guide, Section 4.6 “Floating-Point Context Control Register (FPCCR)”, Document ARM DUI 0553A
  2. ARM, ARMv7-M Architecture Reference Manual, Section B1.5.13 “Floating-point context control”, ARM DDI 0403E
  3. FreeRTOS, Running FreeRTOS on ARM Cortex-M3/M4, https://freertos.org/Documentation/02-Kernel/03-Supported-devices/04-Demos/ARM-Cortex/RTOS-Cortex-M3-M4
  4. Joseph Yiu, The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors, 3rd Edition, Chapter 14 “Floating-Point Unit”
  5. STMicroelectronics, STM32F4 Series Reference Manual (RM0090), Section 4.3.3 “FPU context switching”, https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf

Frequently Asked Questions

What is lazy FPU stacking on Cortex-M?

Lazy stacking defers saving the caller-saved FPU registers (S0-S15, FPSCR) until the exception handler (ISR) actually uses floating-point instructions. The processor allocates stack space on exception entry but delays the actual register push.

When does eager state save outperform lazy stacking?

Eager saving (disabling lazy stacking) can be preferable in systems with high-frequency interrupts where the ISR always uses the FPU. Paying the stacking cost upfront avoids the deferred stacking trap overhead.

How does the FPCCR register control FPU context switching behavior?

FPCCR (Floating-Point Context Control Register) bit 30, LSPEN (Lazy State Preservation Enable), controls this behavior. LSPEN=1 enables lazy stacking (default); clearing it forces eager save (if the interrupted task used the FPU) on exception entry.

What is the typical latency penalty of lazy stacking on first FPU use?

If an ISR executes its first FPU instruction, the hardware triggers a deferred stacking operation, pushing S0-S15 and FPSCR. This adds roughly 17 cycles of latency to the ISR's execution, which must be accounted for in hard real-time systems.

How do RTOSes handle FPU context switching?

RTOSes typically leave lazy stacking globally enabled. During a context switch, they check if the outgoing task used the FPU (via EXC_RETURN). If so, they manually save the callee-saved FPU registers (S16-S31) since hardware only handles S0-S15.

Tags

cortex-mfpucontext-switchinglazy-stackingrtosarm

Share


Previous Article
SPI Slave DMA Implementation on STM32
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

Optimizing Cortex-M Tail-Chaining for Sub-Microsecond Latency
Optimizing Cortex-M Tail-Chaining for Sub-Microsecond Latency
August 15, 2026
5 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media