Embedded Systems ProgrammingUnit 412 min read
ARM Thumb Instruction Set: Compact Coding for Efficiency
Unit 4 of Embedded Systems Programming: explores the ARM Thumb instruction set, its 16-bit compact encoding, conditional execution, and optimizations for low-power and high-performance embedded applications, with practical examples and code traces.
TAKEAWAYS:
- Thumb is ARM’s 16-bit instruction set that reduces code size by 50% compared to 32-bit ARM instructions while maintaining near-full functionality.
- It uses conditional execution (e.g.,
ADDNE) to skip branches and save cycles, critical for real-time systems like NTC’s base station firmware. - Thumb-2 extends Thumb with mixed 16/32-bit instructions, enabling seamless transitions between compact and powerful operations (e.g., Daraz’s inventory management).
- Register usage in Thumb is optimized for small footprints, ideal for constrained devices like Pathao’s GPS-enabled dispatch units.
- Thumb optimizations (e.g.,
MOVW/MOVTfor 16-bit constants) cut memory usage by 30-40%, vital for Ncell’s IoT sensors. - Debugging Thumb code requires understanding thumb-mode entry/exit and branch prediction, common in eSewa’s transaction processors.
1. Introduction to ARM Thumb: Why Compact Matters
Embedded systems often run on 8-bit or 16-bit microcontrollers (e.g., STM32L series) where code size and execution speed are critical. The ARM Thumb instruction set addresses this by offering a 16-bit alternative to the 32-bit ARM instruction set, reducing memory usage while keeping performance close to ARM.
Thumb vs. ARM: A Size vs. Power Tradeoff
| Feature | ARM (32-bit) | Thumb (16-bit) |
|---|---|---|
| Instruction Size | 4 bytes | 2 bytes |
| Code Density | Lower | Higher (50% smaller) |
| Registers Used | r0–r15 (16) | r0–r7 (8) + r8–r15 (optional) |
| Conditional Execution | Limited (e.g., ADDS) |
Rich (e.g., ADDNE) |
| Thumb-2 Extension | No | Yes (mixed 16/32-bit) |
Why Thumb?
- Ncell’s IoT gateways use Thumb to fit firmware in <128KB while processing 5G signals.
- Pathao’s dispatch units rely on Thumb to reduce flash memory usage in GPS modules.
2. Thumb Instruction Encoding: 16-Bit Efficiency
Thumb instructions are encoded in 16 bits, divided into opcode fields and operand fields. Unlike ARM’s fixed 32-bit format, Thumb uses variable-length encoding for operands.
Thumb Instruction Format
Thumb instructions follow a fixed 16-bit layout with opcode bits determining the operation and operand bits specifying registers or immediate values.
Example: ADD Instruction
flowchart TD
A["Thumb ADD Instruction (16-bit)"] --> B["Opcode: 0b11000000"]
B --> C["Registers: rN (4 bits), rM (4 bits)"]
C --> D["Immediate/Shift: 8 bits (optional)"]Code Example:
// ARM (32-bit)
__asm__("ADD r0, r1, r2");
// Thumb (16-bit)
__asm__("ADD r0, r1, r2"); // Still 16-bit encoded
Trace:
| Step | ARM (4B) | Thumb (2B) | Registers |
|---|---|---|---|
| 1 | 0xE0800002 | 0x18C2 | r0 = r1 + r2 |
| 2 | 4 bytes | 2 bytes | Same result |
Key Insight: Thumb halves the code size without sacrificing functionality. For NEPSE’s trading terminals, this means faster firmware updates over slow cellular links.
3. Conditional Execution: Skipping Branches
Thumb introduces conditional execution (e.g., ADDNE, MOVGT), allowing operations to execute only if a condition is met. This eliminates unnecessary branches, improving performance.
Example: Conditional Add (ADDNE)
flowchart TD
A["Thumb ADDNE"] --> B["Check Flags (Z, N, C, V)"]
B -->|"If Not Equal (NE)"| C["Execute ADD r0, r1, r2"]
B -->|"Else"| D["Skip ADD"]Code Example:
int a = 5, b = 3, result;
if (a != b) {
result = a + b; // Compiles to Thumb `ADDNE`
}
Trace:
| Condition | Flags (Z/N/C/V) | Instruction Executed |
|---|---|---|
a != b |
NE=1 | ADDNE r0, r1, r2 |
a == b |
NE=0 | Skipped |
Real-World Use:
- eSewa’s fraud detection uses
ADDNEto add transaction fees only if the amount exceeds a threshold, reducing unnecessary computations.
4. Thumb-2: The Best of Both Worlds
Thumb-2 extends Thumb with 32-bit instructions where needed, allowing seamless transitions between 16-bit and 32-bit operations.
Thumb-2 Instruction Set Overview
| Instruction Type | Example | Use Case |
|---|---|---|
| 16-bit | ADD r0, r1, #5 |
Small constants |
| 32-bit | MOVW r0, #0x1234 |
Large constants (16-bit) |
| Mixed | MOVT r0, #0x5678 |
32-bit constants (Thumb-2 only) |
Example: Loading a 16-bit Constant
flowchart TD
A["Thumb-2 MOVW/MOVT"] --> B["MOVW r0, #0x1234 (16-bit)"]
B --> C["MOVT r0, #0x5678 (upper 16-bit)"]
C --> D["r0 = 0x56781234"]Code Example:
uint16_t value = 0x1234;
uint32_t full_value = (value << 16) | 0x5678;
// Thumb-2: MOVW r0, #0x1234; MOVT r0, #0x5678
Trace:
| Step | Instruction | Register Value (Hex) |
|---|---|---|
| 1 | MOVW r0, #0x1234 |
0x00001234 |
| 2 | MOVT r0, #0x5678 |
0x56781234 |
Why Thumb-2?
- Daraz’s inventory systems use
MOVW/MOVTto load 16-bit IDs efficiently, reducing flash wear.
5. Register Usage in Thumb
Thumb reduces register count to 8 general-purpose registers (r0–r7) in basic Thumb, with r8–r15 available in Thumb-2.
Thumb Register Allocation
| Register | Purpose |
|---|---|
| r0–r7 | General-purpose (Thumb) |
| r8–r15 | General-purpose (Thumb-2) |
| r13 | Stack pointer (SP) |
| r14 | Link register (LR) |
| r15 | Program counter (PC) |
Example: Stack Operations in Thumb
flowchart TD
A["Thumb PUSH {r0, r1}"] --> B["Decrement SP by 8 bytes"]
B --> C["Store r0 at SP+4, r1 at SP"]
C --> D["SP = SP - 8"]Code Example:
__asm__("PUSH {r0, r1}");
Trace:
| Step | SP (Hex) | Stack Contents (Top to Bottom) |
|---|---|---|
| Initial | 0x20000000 | ... |
| After PUSH | 0x200000F8 | r0 (0x1234), r1 (0x5678) |
Real-World Impact:
- NTC’s base station firmware uses
PUSH/POPto save/restore registers during interrupt handling, ensuring deterministic execution.
6. Thumb vs. ARM: Performance Comparison
While Thumb reduces code size, ARM’s 32-bit instructions often execute faster for complex operations.
| Metric | Thumb (16-bit) | ARM (32-bit) |
|---|---|---|
| Instruction Fetch | 2 cycles | 1 cycle |
| Decode Latency | 1 cycle | 1 cycle |
| Execution Speed | Slower for complex ops | Faster |
| Code Density | Higher (50% smaller) | Lower |
Example: Multiplication (MUL)
flowchart TD
A["Thumb MUL"] --> B["2 cycles (16-bit)"]
C["ARM MUL"] --> D["1 cycle (32-bit)"]Code Example:
int result = a * b; // Thumb: 2 cycles; ARM: 1 cycle
Trace:
| Operation | Thumb Cycles | ARM Cycles |
|---|---|---|
MUL r0, r1, r2 |
2 | 1 |
When to Use Thumb?
- Low-memory devices (e.g., Ncell’s IoT sensors).
- Real-time systems where code size > speed (e.g., Pathao’s dispatch units).
7. Thumb Optimizations for Embedded Systems
Thumb provides special instructions to optimize common tasks:
| Optimization | Thumb Instruction | Use Case |
|---|---|---|
| 16-bit Constant Load | MOVW/MOVT |
Loading small constants |
| Conditional Branches | BNE, BEQ |
Loop optimizations |
| Stack Manipulation | PUSH/POP |
Interrupt handling |
Example: Optimized Loop with ADD and CMP
flowchart TD
A["Thumb Loop"] --> B["ADD r0, r0, #1 (16-bit)"]
B --> C["CMP r0, #10"]
C -->|"NE"| D["BNE loop_start"]
C -->|"EQ"| E["Exit loop"]Code Example:
int i = 0;
while (i < 10) {
i++; // Thumb: ADD r0, r0, #1; CMP r0, #10; BNE loop_start
}
Trace:
| Iteration | r0 (i) | Instruction Executed |
|---|---|---|
| 0 | 0 | ADD r0, r0, #1 |
| ... | 9 | CMP r0, #10 → BNE |
| 10 | 10 | CMP r0, #10 → EQ (exit) |
Real-World Example:
- Khalti’s payment processing uses optimized loops in Thumb to validate transactions in <1ms, critical for low-latency payments.
8. Debugging Thumb Code
Debugging Thumb requires understanding:
- Thumb-mode entry/exit (via
BXorBLX). - Branch prediction (Thumb’s pipeline is sensitive to mispredicted branches).
- Register usage (Thumb-2 allows
r8–r15, but basic Thumb restricts tor0–r7).
Example: Thumb-to-ARM Transition
flowchart TD
A["Thumb Mode"] --> B["BX LR (Branch to ARM)"]
B --> C["ARM Mode"]
C --> D["Return to Thumb via BX"]Code Example:
__asm__("BX lr"); // Switches to ARM mode if LR is ARM
Trace:
| Mode | PC (Hex) | Registers Affected |
|---|---|---|
| Thumb | 0x08000000 | - |
| After BX | 0x08000002 | Switches to ARM |
Debugging Tip:
- Use ARM’s
DWT(Data Watchpoint and Trace) to monitor Thumb/ARM transitions.
In the Real World
Ncell’s IoT Sensors
- Idea: Thumb’s 16-bit compact instructions reduce firmware size from 256KB to 128KB, extending battery life in 5G signal repeaters.
- Worked Example: A sensor node processes 1000 packets/day using Thumb’s
MOVW/MOVTto load 16-bit IDs, cutting flash wear by 30%.
Pathao’s Dispatch Units
- Idea: Thumb’s conditional execution (
ADDNE) skips unnecessary GPS updates when the vehicle is stationary, saving 20% battery in a day. - Worked Example: A dispatch unit checks if the vehicle is moving (
if (speed > 0)) before updating its location. Thumb’sADDNEensures the update only runs when needed.
- Idea: Thumb’s conditional execution (
eSewa’s Transaction Processor
- Idea: Thumb’s mixed 16/32-bit instructions (
MOVW/MOVT) load 16-bit transaction IDs in 2 cycles, reducing latency from 5ms to 2ms for high-frequency payments. - Worked Example: A transaction ID
0xABCDis loaded as:
This is faster than ARM’s 32-bit load for small constants.__asm__("MOVW r0, #0xABCD"); // 16-bit load
- Idea: Thumb’s mixed 16/32-bit instructions (
Exam Tip
Compare Thumb and ARM:
- Always highlight the 50% code size reduction and conditional execution advantages in exams.
- Example answer:
"Thumb reduces code size by 50% compared to ARM while supporting conditional execution (e.g.,
ADDNE), making it ideal for low-memory embedded systems like Ncell’s IoT sensors."
Show Code Traces:
- For Thumb-2 optimizations (e.g.,
MOVW/MOVT), always trace the register changes step-by-step. - Example:
"After
MOVW r0, #0x1234; MOVT r0, #0x5678,r0holds0x56781234in 2 cycles, unlike ARM’s 32-bit load which takes 1 cycle but uses 4 bytes."
- For Thumb-2 optimizations (e.g.,
Real-World Applications:
- Link Thumb’s optimizations to NEPSE’s trading terminals (low-latency) or Pathao’s dispatch units (battery efficiency).
- Example:
"Thumb’s
PUSH/POPinstructions are critical in NTC’s base station firmware to handle interrupts deterministically, ensuring no packet loss during signal processing."
Debugging Focus:
- Emphasize Thumb-mode entry/exit and branch prediction in debugging questions.
- Example:
"When debugging a Thumb-to-ARM transition, use
BX lrto switch modes and ensure the link register (lr) points to the correct ARM address."
Performance Tradeoffs:
- Always compare Thumb vs. ARM in terms of code size vs. speed.
- Example:
"While ARM’s 32-bit
MULexecutes in 1 cycle, Thumb’s 16-bitMULtakes 2 cycles but reduces code size by 50%, making it suitable for Daraz’s inventory systems where memory is constrained."
Final Note: Thumb is not just a smaller ARM—it’s a specialized instruction set for low-power, high-density embedded systems. Master its conditional execution, Thumb-2 optimizations, and register usage, and you’ll excel in real-world applications like Ncell’s IoT, Pathao’s dispatch, and eSewa’s payments. Always trace register changes and compare with ARM in exams!
Based on the TU BSc CSIT syllabus for Embedded Systems Programming, unit 4.
Discussion
Loading…