Elective Embedded Systems Programming

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/MOVT for 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 ADDNE to 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/MOVT to 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/POP to 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:

  1. Thumb-mode entry/exit (via BX or BLX).
  2. Branch prediction (Thumb’s pipeline is sensitive to mispredicted branches).
  3. Register usage (Thumb-2 allows r8–r15, but basic Thumb restricts to r0–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

  1. 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/MOVT to load 16-bit IDs, cutting flash wear by 30%.
  2. 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’s ADDNE ensures the update only runs when needed.
  3. 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 0xABCD is loaded as:
      __asm__("MOVW r0, #0xABCD"); // 16-bit load
      
      This is faster than ARM’s 32-bit load for small constants.

Exam Tip

  1. 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."

  2. 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, r0 holds 0x56781234 in 2 cycles, unlike ARM’s 32-bit load which takes 1 cycle but uses 4 bytes."

  3. 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/POP instructions are critical in NTC’s base station firmware to handle interrupts deterministically, ensuring no packet loss during signal processing."

  4. Debugging Focus:

    • Emphasize Thumb-mode entry/exit and branch prediction in debugging questions.
    • Example:

      "When debugging a Thumb-to-ARM transition, use BX lr to switch modes and ensure the link register (lr) points to the correct ARM address."

  5. Performance Tradeoffs:

    • Always compare Thumb vs. ARM in terms of code size vs. speed.
    • Example:

      "While ARM’s 32-bit MUL executes in 1 cycle, Thumb’s 16-bit MUL takes 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…