HomeAbout UsContact Us

Cortex-M Floating-Point Unit Lazy Stacking Optimization

By Jithin Tom
Published in Embedded C/C++
August 19, 2026
3 min read
Cortex-M Floating-Point Unit Lazy Stacking Optimization

Table Of Contents

01
The Problem: FPU State Preservation Overhead
02
Lazy Stacking: The Hardware Optimization
03
When Lazy Stacking Helps
04
When Lazy Stacking Hurts: Nested Interrupt Latency Spikes
05
Mitigation Strategies
06
Measuring the Impact
07
Decision Matrix
08
Practical Implementation Checklist
09
Summary
10
Related Reading
11
References
12
Frequently Asked Questions

The Cortex-M Floating-Point Unit (FPU) is a critical accelerator for DSP, motor control, and sensor fusion workloads. Yet its interaction with the NVIC exception model introduces a subtle but measurable latency penalty: lazy stacking. Understanding when lazy stacking helps, when it hurts, and how to control it is essential for firmware engineers targeting deterministic response times on Cortex-M4, M7, M33, and M55 cores.

The Problem: FPU State Preservation Overhead

The Cortex-M FPU provides 32 single-precision registers (S0-S31) plus the FPSCR status register — 33 registers total. On exception entry, the hardware must preserve the caller-saved state (S0-S15, FPSCR, plus one reserved padding word — 18 words total) so the interrupted context can resume floating-point computation seamlessly.

+----------------------------------------------------------+
| EXCEPTION ENTRY WITHOUT LAZY STACKING |
+----------------------------------------------------------+
| |
| 1. Hardware detects exception |
| 2. Push 18 FPU words to stack (~17 additional cycles) |
| 3. Push core registers (R0-R3, R12, LR, PC, xPSR) |
| 4. Execute handler |
| 5. Pop FPU registers on exit (~17 additional cycles) |
| |
| Total FPU overhead: ~34 cycles per exception |
+----------------------------------------------------------+

At 168 MHz, 34 cycles equals ~200 nanoseconds of pure register save/restore — before your handler executes a single instruction. For a high-frequency control loop, this deterministic overhead can add up.

Lazy Stacking: The Hardware Optimization

ARMv7-M and ARMv8-M architectures introduced lazy stacking (controlled via FPCCR.LSPEN and FPCCR.ASPEN) to eliminate this overhead for exceptions that never touch the FPU.

+----------------------------------------------------------+
| EXCEPTION ENTRY WITH LAZY STACKING |
+----------------------------------------------------------+
| |
| 1. Hardware detects exception |
| 2. Check: Has FPU been used (CONTROL.FPCA == 1)? |
| NO -> Push standard 8-word core frame only |
| YES -> Proceed to step 3 |
| 3. Allocate 26-word extended frame on stack, but |
| SKIP writing the 18 FPU words to memory. |
| 4. Set FPCCR.LSPACT = 1 (lazy state active) |
| 5. Write core registers only (~12 cycles) |
| 6. Execute handler |
| First FP instruction triggers deferred FPU save |
| 7. On exit: pop core registers, then FPU if LSPACT=1 |
| |
| Worst case (FPU used): ~34 cycles + deferral penalty |
+----------------------------------------------------------+

The processor tracks FPU usage via the FPCCR.LSPACT bit and CONTROL.FPCA. When an exception occurs and the FPU was used, the hardware allocates the stack space but skips the 18-word memory write. The first floating-point instruction inside the handler triggers the deferred save — writing the FPU registers to the reserved stack space while the handler is already running.

When Lazy Stacking Helps

Lazy stacking shines in systems where:

  • Most interrupts don’t use floating-point (GPIO, UART, timer, DMA completion)
  • FPU-intensive tasks are confined to specific threads (motor control loop, sensor fusion)
  • Interrupt frequency is high but FPU usage is sparse

Typical savings: ~17 cycles per non-FPU interrupt entry/exit. On a Cortex-M4 at 168 MHz with 10,000 non-FPU interrupts/sec, that’s 340,000 cycles/sec recovered.

When Lazy Stacking Hurts: Nested Interrupt Latency Spikes

The deferred save creates a non-preemptible latency window. Consider this scenario:

+----------------------------------------------------------+
| NESTED INTERRUPT LATENCY SPIKE |
+----------------------------------------------------------+
| |
| Time |
| ^ |
| | Low-priority IRQ enters (no FPU used yet) |
| | Lazy stacking enabled, LSPACT = 1 |
| | |
| | Low-priority handler executes VADD.F32 S0, S1, S2 |
| | -> FIRST FP INSTRUCTION |
| | -> Hardware DEFERS FPU save, begins memory writes |
| | -> Begins 18-word push to stack (~17 cycles) |
| | ^ |
| | | HIGH-PRIORITY IRQ FIRES HERE |
| | | (e.g., safety-critical watchdog, motor PWM) |
| | | |
| | | Processor CANNOT preempt until |
| | | deferred FPU save COMPLETES |
| | | (non-preemptible critical section) |
| | |
| | High-priority handler waits up to 17 cycles |
| | before it can even START |
| +----------------------------------------------------->|
| |
+----------------------------------------------------------+

This is the lazy stacking trap: a low-priority handler’s first FP instruction creates an ~17-cycle non-preemptible region that blocks all higher-priority interrupts. For hard real-time systems with ultra-strict deadlines, this jitter can be problematic.

Mitigation Strategies

1. Force Eager Stacking (FPCCR Configuration)

Disable lazy stacking by setting FPCCR.ASPEN=1 and FPCCR.LSPEN=0 during FPU initialization. This forces the hardware to save the FPU state immediately on exception entry.

// Enable FPU with EAGER stacking (deterministic latency)
void fpu_enable_eager_stacking(void) {
// CPACR: Enable FPU access (bits 20-23 = 0b11)
SCB->CPACR |= (0xF << 20);
// FPCCR: Enable automatic state preservation (ASPEN=1)
// Disable lazy state preservation (LSPEN=0)
// FPCCR address: 0xE000EF34
volatile uint32_t *fpccr = (volatile uint32_t *)0xE000EF34;
*fpccr = (*fpccr & ~(1 << 30)) | (1 << 31);
// DSB/ISB to ensure FPCCR write takes effect
__DSB();
__ISB();
}

With eager stacking, every exception entry that interrupts FPU usage pays the 17-cycle FPU save cost, but no exception ever blocks on a deferred save. Latency becomes deterministic — critical for safety-critical systems.

2. Force Early FPU State Save in Thread Context

If you want lazy stacking for non-FPU interrupts but need to eliminate the nested-interrupt spike, execute a dummy FP instruction during thread startup or context switch:

// Force FPU state to be "active" so lazy stacking doesn't defer
static inline void fpu_force_state_active(void) {
// Dummy FP instruction - compiler won't optimize away
volatile float dummy = 0.0f;
__asm volatile ("vmov.f32 s0, %0" : : "w" (dummy) : "s0");
__DSB();
}
// Call during thread initialization or RTOS context switch out
void task_init_fpu_context(void) {
fpu_force_state_active();
// Now LSPACT=0 and FPU state is "clean" -
// next exception will eager-save if needed
}

This ensures the FPU state is already marked as active, so the next exception entry performs eager stacking without deferral.

3. RTOS-Aware Context Switch Handling

FreeRTOS and Zephyr both handle FPU context switching, but lazy stacking adds complexity:

// FreeRTOS port layer snippet (ARM_CM4F/ARM_CM7)
#if (configUSE_TASK_FPU_SUPPORT == 2) // Lazy stacking enabled
// Check if lazy state is active on outgoing task
if ((*(volatile uint32_t *)0xE000EF34) & (1 << 0)) { // FPCCR.LSPACT (bit 0)
// Lazy state active - must save FPU registers manually
vPortSaveFPUContext();
*(volatile uint32_t *)0xE000EF34 &= ~(1 << 0); // Clear LSPACT
}
#endif

Key insight: The RTOS must check FPCCR.LSPACT during context switch. If lazy state is active, the FPU registers are partially saved on the interrupted task’s stack — the scheduler must complete the save before switching tasks.

Measuring the Impact

On a typical Cortex-M4 at 168 MHz:

ConfigurationNon-FPU IRQ EntryFPU IRQ Entry (first)Nested IRQ Latency Spike
Eager stacking29 cycles29 cycles0 cycles (deterministic)
Lazy stacking (no FPU used)12 cyclesN/A0 cycles
Lazy stacking (FPU used)12 cycles12 cycles + deferralUp to 17 cycles

The “deferral” in the nested case is the time to complete the deferred push after the high-priority IRQ fires but before its handler runs.

Decision Matrix

+----------------------------------------------------------+
| LAZY STACKING DECISION MATRIX |
+----------------------------------------------------------+
| System Profile | Recommendation |
+------------------------------------+---------------------+
| Hard real-time, safety-critical | EAGER (disable |
| (ISO 26262, IEC 61508) | lazy stacking) |
+------------------------------------+---------------------+
| Soft real-time, mixed workload | LAZY + force |
| (motor control + comms) | early save |
+------------------------------------+---------------------+
| FPU rarely used (<5% interrupts) | LAZY (default) |
| (sensor polling, UI) | |
+------------------------------------+---------------------+
| Deeply nested interrupts (>3) | EAGER |
| (complex priority scheme) | |
+------------------------------------+---------------------+
| RTOS with FPU-aware context switch | LAZY (with |
| (FreeRTOS, Zephyr) | RTOS support) |
+------------------------------------+---------------------+

Practical Implementation Checklist

  1. Audit your interrupt handlers — identify which use FPU instructions
  2. Profile interrupt latency — measure worst-case with/without lazy stacking
  3. Configure FPCCR at startup — eager or lazy based on decision matrix
  4. Verify RTOS FPU support — ensure context switch handles LSPACT correctly
  5. Add dummy FP instruction to thread init if using lazy + early save
  6. Test nested interrupt scenarios — inject high-priority IRQ during low-priority FPU use

Summary

Lazy stacking is a powerful hardware optimization that eliminates FPU register memory write overhead for the common case where interrupts don’t use floating-point. However, it introduces a non-preemptible latency window when a low-priority handler’s first FP instruction defers the save, potentially blocking higher-priority interrupts for ~17 cycles.

For hard real-time systems, disable lazy stacking via FPCCR.LSPEN=0 and FPCCR.ASPEN=1 to achieve deterministic interrupt latency. For mixed workloads, keep lazy stacking enabled but force early FPU state activation during thread initialization to eliminate the deferral window. Always verify your RTOS context switch code correctly handles FPCCR.LSPACT — this is where lazy stacking bugs most often surface.

The FPU is a tool, not a free lunch. Configure its exception behavior to match your system’s timing requirements, not the hardware defaults.

  • Zero-Copy DMA Patterns on ARM Cortex-M
  • Reducing ARM Cortex-M Interrupt Latency with Tail-Chaining
  • Fixing Cortex-M Vector Table Relocation Bugs in Startup Code

References

  1. ARM, “ARMv7-M Architecture Reference Manual”, Section B3.3 (Floating-Point Support), 2014
  2. ARM, “Cortex-M4 Devices Generic User Guide”, Section 3.2.3 (Lazy Stacking), 2013
  3. STMicroelectronics, “STM32F4 Series Reference Manual (RM0090)”, Section 4.2.3 (FPU Lazy Stacking)
  4. FreeRTOS Kernel, “ARM Cortex-M4F Port”, portable/GCC/ARM_CM4F/port.c, FPU context handling
  5. Zephyr RTOS, “ARM Cortex-M FPU Support”, arch/arm/core/aarch32/cortex_m/fpu.c
  6. Joseph Yiu, “The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors”, 3rd Ed., Chapter 14

Frequently Asked Questions

What is FPU lazy stacking on Cortex-M processors?

Lazy stacking defers the saving of FPU caller-saved registers (S0-S15, FPSCR, and a reserved word) during exception entry until the first floating-point instruction executes inside the handler. This avoids the 18-word memory write overhead for interrupts that never use the FPU, reducing worst-case interrupt latency by ~17 cycles on Cortex-M4/M7.

When does the Cortex-M hardware automatically enable lazy stacking?

Lazy stacking is enabled by default when the FPU is enabled via CPACR (bits 20-23 set to 0b11). The processor automatically skips FPU register memory writes on exception entry if the FPU hasn't been used since the last context switch. No additional software configuration is required beyond enabling the FPU.

How can lazy stacking increase interrupt latency in nested interrupt scenarios?

If a high-priority interrupt preempts a low-priority handler that has already triggered lazy stacking (by executing an FP instruction), the processor must complete the deferred FPU state save before entering the high-priority handler. This adds ~17 cycles of non-preemptible latency, potentially violating timing constraints for hard real-time tasks.

What is the recommended approach to mitigate lazy stacking latency spikes?

Execute a dummy FPU instruction (e.g., VMOV.F32 S0, S0) early in the interrupt handler or during thread initialization to force immediate FPU state saving. Alternatively, disable lazy stacking by setting FPCCR.ASPEN=1 and FPCCR.LSPEN=0, forcing eager stacking on every exception entry for deterministic latency.

Does lazy stacking affect thread context switching in an RTOS?

Yes. During a context switch, the RTOS must save the FPU state if the outgoing thread used the FPU. With lazy stacking, the FPU state may already be partially saved on the stack from a previous exception. The RTOS context switch code must check FPCCR.LSPACT to detect lazy state and handle it correctly, adding complexity to the scheduler.

Tags

cortex-mfpulazy-stackinginterrupt-latencyarmfloating-point

Share


Previous Article
Managing Technical Debt in Embedded Firmware: Incremental Refactoring Strategies
Jithin Tom

Jithin Tom

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

Related Posts

Cortex-M Cache Maintenance for DMA Coherency
Cortex-M Cache Maintenance for DMA Coherency
August 19, 2026
3 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media