Saturated math instructions in Arm Cortex-M series refer to arithmetic instructions that saturate the result instead of overflowing. This means if the mathematical result exceeds the data type’s range, it will be capped at the maximum or minimum value rather than wrapping around. Saturated math prevents unexpected behavior from overflows and improves reliability in digital signal processing.
How Arithmetic Overflow Happens in Digital Systems
In digital systems, values are stored in registers and memory with fixed widths. For example, a 32-bit register can store integers from -2,147,483,648 to 2,147,483,647. If an arithmetic operation attempts to exceed these limits, the system encounters an overflow condition.
For example, adding two 16-bit integers 65,535 and 65,535 normally results in 131,070. But this exceeds the 16-bit range of -32,768 to 32,767. The system has no way to represent 131,070 in just 16 bits, so the result wraps around to -32,766 instead. This unexpected wrapping can lead to errors and bizarre program behavior.
Examples of Overflow
Here are some examples of how overflow can happen with different data types in C:
unsigned char a = 255;
unsigned char b = 1;
unsigned char c = a + b; // c contains 0 instead of 256
int x = 1000000;
int y = 1000000;
int z = x * y; // z contains -727379968 instead of 1,000,000,000
short accum = 32767;
accum += 10; // accum now contains -32768 instead of 32777
As these examples show, overflow causes logical values to wrap around unexpectedly. Programs encountering overflow may crash, output incorrect data, or enter unstable states.
Saturated Math in Arm Cortex-M
To prevent the problems caused by overflow, Arm Cortex-M CPUs implement saturated arithmetic. Saturated math confines results to the data type’s range instead of allowing wrap around. When an operation exceeds the bounds, the result is clamped to the maximum or minimum value.
For example, if two 16-bit integers are added and the true sum is 130,000, the saturated result would be 32,767. This caps the output but avoids the instability of wraparound.
Saturated Math Instructions
The Arm Thumb instruction set includes variants of basic math operations that saturate:
- Saturated Add: QADD, QDADD
- Saturated Subtract: QSUB
- Saturated Negative: QNEG
- Saturated Absolute: QABS
The CPU executes these in place of regular math instructions to avoid overflow. Code performing filtering, gains, signal processing, and other mathematical operations should use saturated forms to improve robustness.
Enabling Saturation
Saturated math must be enabled by setting control bits in the Application Program Status Register (APSR). There are flags to choose between saturating or wrapping behavior for addition, subtraction, and negation individually.
// Enable saturated addition and subtraction
APSR |= 0x00000088;
// Disable saturated negation
APSR &= 0xFFFFFF77;
This allows tailoring saturation to the specific needs of an application. The flags are cleared on reset, so saturation must be enabled explicitly.
Benefits of Saturated Math
Using saturated arithmetic provides several benefits compared to relying on default wrap-around overflow behavior:
- Avoids unexpected values: Results will never exceed the data type range. This prevents confusing bugs from overflow wraparound.
- Improves system stability: Wraparound overflow can put systems into unstable or unrecoverable states. Saturated math remains bounded.
- Easier debugging: Bizarre program behavior from overflow is eliminated. Arithmetic acts “normally” within limits.
- Better signal processing: Digital filters, gains, and signal processing behave properly when results saturate.
- Matches analog systems: Analog signals saturate naturally. Saturated digital math better models this behavior.
The cost is a small increase in code size and instruction count to use the saturated operations. In most cases, the benefits far outweigh the minor performance overhead.
Examples of Saturated Math
Here are some examples of saturated operations in C code:
// Initialize variables
int16_t x = 5000;
int16_t y = 6000;
int16_t z;
// Saturated add caps at 16-bit max
z = QADD16(x, y); // z = 32,767
// Saturated subtract caps at min
z = QSUB16(x, y); // z = -32,768
// Saturated negation flips sign
z = -5000;
z = QNEG16(z); // z = 32,767
The saturated results remain within the int16_t range instead of overflowing. This avoids bugs that could occur if wraparound produced nonsensical values.
Digital Signal Processing Example
Here is an example of using saturated arithmetic in an audio filtering routine:
const int NUM_TAPS = 16;
int32_t filterTaps[NUM_TAPS] = {...};
int32_t delayLine[NUM_TAPS] = {...};
void applyFilter(int16_t input) {
// Shift delay line
for (int i = NUM_TAPS-1; i >= 0; i--) {
delayLine[i] = delayLine[i-1];
}
// Store new input
delayLine[0] = input;
// Apply filter
int64_t acc = 0;
for (int i = 0; i < NUM_TAPS; i++) {
acc += QADD(filterTaps[i], delayLine[i]);
}
// Saturate accumulation
int16_t output = QADD16(acc);
return output;
}
This filter sums each tap using saturated addition. The final output is saturated to avoid overflow distortion. This keeps the filter output bounded and maintains proper behavior even with overflow.
Hardware Support in Cortex-M
Several hardware features help Cortex-M CPUs efficiently implement saturated arithmetic:
- Status register flags: Overflow and saturation flags are set to detect overflow and saturation events.
- Shifter optimizations: Saturation is integrated into the barrel shifter logic.
- DSP extensions: DSP instructions use native saturated math for filters and transforms.
- SIMD saturation: Vector processing units saturate SIMD parallel arithmetic.
This hardware saturation support accelerates saturated operations with minimal impact on clock speed or cycle counts. Only a few extra cycles are needed for saturation compared to regular math.
Checking Saturation Flags
The Q flag in the APSR status register indicates when saturation occurs. Code can check this to count saturation events or detect overflow conditions:
int32_t sum = QADD16(x, y);
if (APSR & 0x00000008) {
// Saturation occurred
} else {
// No saturation
}
This flag checking allows taking specific actions based on saturation, such as logging events or modifying algorithms.
Differences Across Cortex-M Generations
Saturated arithmetic capabilities differ slightly among Cortex-M variants:
- Cortex-M0+ has no saturated instructions.
- Cortex-M1 includes basic saturated add/subtract for 16-bit values.
- Cortex-M3 expands support to 32-bit integers.
- Cortex-M4 adds saturated 64-bit accumulation for DSP.
- Cortex-M7 further extends saturation to SIMD and floating point.
Newer cores add saturated forms of more complex arithmetic operations. This allows efficient saturated math across larger data types and parallel processing units.
To use saturated math in older Cortex-M cores, library functions can emulate saturation. But native hardware support provides better performance.
Conclusion
Saturated arithmetic is a key feature of Arm Cortex-M processors. It caps mathematical results at data type limits to avoid overflow issues. Using the QADD, QSUB, and other saturated instructions can significantly improve code stability and robustness. There are minimal costs for significant gains in reliability.
With hardware saturation built into the Cortex-M CPU pipeline, developers can efficiently utilize saturated math. This helps ensure digital signal processing, control loops, filters, and other arithmetic operations remain bounded as expected. By preventing wrap-around overflow, saturated math avoids a common source of problems in embedded systems.