Skip to content
BoKSA

Assembly Language

Assembly Language

Introduction

At the lowest level, a CPU does only a handful of things: move numbers between registers and memory, add, subtract, compare, and jump to a different instruction. Machine code encodes those actions as binary. Assembly language gives each opcode a short name (mnemonic) humans can read: add, mov, ldr, bne.

You will not rewrite your robot firmware in assembly. But when the debugger stops in a HardFault, when you wonder why -O2 broke your timing, or when you read the vector table in a datasheet, assembly is the language those tools speak. This article builds intuition — not replace your C compiler.


Three levels of the same program

Level What "Hello" looks like Who reads it
C printf("Hello\n"); You
Assembly mov, bl, ldr Experienced engineers, tools
Machine 1110101100… CPU only

The compiler is a translator from C down the ladder. GCC can stop halfway and show you assembly (gcc -S). The linker finishes the job to machine code.

In plain terms

C is like giving directions in full sentences. Assembly is like shorthand notes: "R0 = 5", "jump if equal". Machine code is the GPS coordinates only the car understands.


Why assembly still appears in embedded careers

Reason Real situation
Boot & vectors Reset handler jumps to Reset_Handler — assembly label in startup file
Debugging Disassembly shows exact instructions executed before crash
Timing Count cycles for ISR when microsecond jitter matters
Hardware quirks Barriers (DSB, ISB), disable interrupts — sometimes intrinsics/asm
Size Tiny bootloader may hand-write a few dozen instructions

Application logic stays in C. Assembly is for edges where the machine model is visible.


Structure of an assembly program

Assembly source is divided into sections:

  • .text — instructions (program code)
  • .data — initialized constants and variables
  • .bss — reserved zeroed space (often)

Execution flows top to bottom unless a branch jumps elsewhere. Interrupts preempt normal flow — save registers, run handler, restore, return.

On Cortex-M, startup startup_stm32f4xx.s sets stack pointer, copies .data, zeroes .bss, calls main — all before your C runs.


One instruction, two parts

Every instruction has an opcode (what to do) and operands (what to do it to):

1
add   r1, r2, r3     @ r1 = r2 + r3   (ARM style)

Categories you will see everywhere:

Category Examples Purpose
Arithmetic / logic add, sub, and, cmp Calculate, set flags
Load / store ldr, str Move between memory and registers
Branches b, beq, bl Jump, call function
Special nop, wfi Delay slot, sleep until interrupt

cmp subtracts invisibly and sets condition flags (equal, less than, …). beq label branches if equal — how while and if become machine code.


Addressing modes — where operands live

Mode Example Meaning
Register add r0, r1, r2 All values in CPU registers
Immediate mov r0, #42 Constant built into instruction
Memory (offset) ldr r0, [r1, #4] Load from address r1 + 4
PC-relative b loop Jump relative to current instruction

In plain terms

Registers are the CPU's hands — only a few, very fast. Memory is the warehouse — huge but slower. RISC machines insist you carry values from warehouse to hands before doing math. CISC machines sometimes let you add directly in the warehouse.


CISC vs RISC — two design philosophies

CISC (e.g. x86) RISC (e.g. ARM Cortex-M, RISC-V)
Instruction count Large, powerful Smaller set
Instruction size Variable length Often fixed (Thumb-2 mixes 16/32)
Memory in ALU ops? Often yes No — load/store separate
Pipelining Harder Designed for it

Your STM32 / nRF / ESP32 (Xtensa) firmware is mostly RISC Thumb. Laptop lab tools might compile for CISC x86 — same C, different assembly when you use gcc -S.

Same C loop, different assembly flavour

C:

1
2
3
i = 0;
while (a[i] == k)
    i += 1;

CISC (x86) may compare memory directly:

1
cmpl    (%rsi), %edi      /* compare k with a[0] in memory */

RISC (ARM) loads first, then compares registers:

1
2
ldr     r3, [r1]          /* load a[0] into register */
cmp     r0, r3            /* compare k with register */

Neither is "smarter" — architecture choice. See Computer architecture.


Other ISA families (awareness)

Type Typical use
DSP Audio filtering, motor control — multiply-accumulate in one instruction
VLIW Very long instruction words — multiple parallel ops (some TI DSPs)

Cortex-M4 DSP extensions blur the line with SIMD-ish instructions for signal work.


Mixing C and assembly

When C is enough

The compiler optimizes loops, register allocation, and calling conventions. Trust it until measurement says otherwise.

Inline assembly (GCC)

1
2
3
4
5
6
7
8
9
int num = 3, res;
asm volatile (
    "mov %1, %%eax\n"
    "add $5, %%eax\n"
    "mov %%eax, %0"
    : "=r" (res)
    : "r" (num)
    : "%eax"
);

Operands and clobbers tell GCC which registers change. Easy to get wrong.

Prefer vendor intrinsics on ARM: __disable_irq(), __NOP(), __DSB() — same effect, compiler-aware.

Reading what GCC produced

1
2
arm-none-eabi-gcc -S -O2 firmware.c
arm-none-eabi-objdump -d firmware.elf

In GDB: disassemble main, then si (step instruction). Map a surprising delay to extra loads or a branch in a tight loop.


Calling convention — who owns which register

When main calls foo, rules decide which registers foo may overwrite and where arguments go. On ARM AAPCS, first arguments in r0r3, return in r0, link register lr holds return address.

If an ISR clobbers registers without saving them, C variables mysteriously corrupt — disassembly + ABI docs explain why.


Relevant topics


Starting points

  1. gcc -S a function with an if and a while — draw arrows for each branch label.
  2. Compare x86 vs ARM listings for the same tiny C file (online godbolt.org is excellent).
  3. In debugger, break in main, switch to disassembly view, step one instruction.
  4. Find Reset_Handler in your startup .s file — trace path to main.

Focus points

  • Assembly is CPU-specific — skills do not port verbatim.
  • Do not optimize until you profile — compiler is good at register allocation.
  • ISRs must respect calling convention or save context.
  • Use intrinsics before raw inline asm on Cortex-M.

Key points

  • Assembly maps mnemonics to machine opcodes; readable face of the CPU.
  • Addressing modes say whether operands live in registers, constants, or memory.
  • RISC uses load/store + register ops; CISC allows more memory operands.
  • Disassembly and startup files connect C abstractions to what the silicon actually runs.