ARMv8 A Cheat Sheet - MarekBykowski/readme GitHub Wiki

HOME

ARMv8-A / A-profile β€” Interview Handbook

A readable walkthrough of the ARMv8-A application profile (the "A-profile" that runs Linux, hypervisors and secure firmware). AArch64 is the focus; AArch32 is covered where it differs. Every abbreviation is spelled out the first time. Source of truth: Arm Architecture Reference Manual (A-profile) + Arm Learn the architecture guides β€” https://www.arm.com/architecture/learn-the-architecture/a-profile. Use this for recall; verify exact bit fields in the ARM ARM.


1. Execution States

An Execution state is the fundamental "width" the CPU runs in. ARMv8-A has two:

  • AArch64 β€” the 64-bit state. Runs the A64 instruction set. This is what modern Linux/Android, KVM and TF-A use.
  • AArch32 β€” the 32-bit state, backwards-compatible with ARMv7-A. Runs A32 ("ARM") and T32 ("Thumb") instruction sets. Kept for legacy 32-bit apps and OSes.
AArch64 AArch32
Reg width 64-bit 32-bit
GPRs 31 Γ— X0–X30 (+ SP, PC, XZR) 16 Γ— R0–R15, banked per mode
ISA A64 A32 + T32 (Thumb)
Privilege EL0–EL3 (Exception levels) PL0/PL1/PL2 + processor modes

Why "banked" matters: in AArch32 the CPU does not have one flat set of 16 registers. Several registers (notably the stack pointer R13/SP, link register R14/LR, and the saved status SPSR) are duplicated per processor mode β€” so switching mode silently swaps in a different SP. AArch64 abandons that: it has 31 flat general-purpose registers and only the stack pointer is banked per Exception level.

How state changes. The Execution state is tied to the Exception level and can only change when the Exception level changes:

  • Taking an exception (moving to a higher EL): the CPU may switch AArch32 β†’ AArch64.
  • Returning from an exception (moving to a lower EL, via ERET): it may switch AArch64 β†’ AArch32.

You never flip state mid-stream inside one EL.

Key rule: a lower EL cannot be AArch64 while a higher EL is AArch32 β€” you can't run a wider world underneath a narrower one. The width of each level is chosen by the level above it: SCR_EL3.RW fixes EL2/EL1's width, HCR_EL2.RW fixes EL1/EL0's width.


2. Exception Levels, Privilege & Security

ARMv8-A splits software into four Exception levels (EL). "Exception level" is just Arm's word for a privilege ring β€” the name comes from the fact that you move up a level only by taking an exception.

EL Typical software What it can do
EL0 Applications (user space) least privilege; no system registers
EL1 OS kernel (Linux) manages EL0, owns stage-1 MMU
EL2 Hypervisor (KVM, Xen) virtualizes EL1 guests, stage-2 MMU
EL3 Secure Monitor / firmware (TF-A = BL31) owns the Secure/Non-secure switch

You go up only via an exception (interrupt, system call, fault) and down only via ERET (exception return). EL0 has no system-register access at all β€” it asks EL1 for everything through SVC system calls.

The AArch32 equivalent: PL0/PL1/PL2 and processor modes

AArch32 doesn't use the EL0–EL3 names directly. Instead it has Privilege Levels (PL) and, underneath them, classic ARM processor modes:

  • PL0 = unprivileged = user space (the image's "PL0 user-space"). Maps to EL0.
  • PL1 = privileged / kernel. Maps to EL1.
  • PL2 = hypervisor. Maps to EL2.

The "+ modes" in the table are the AArch32 processor modes β€” a ARMv7 concept ARMv8 still honours in 32-bit state. Each mode is a different operating context, and most of them bank their own SP, LR and SPSR:

Mode Abbr Priv / EL Entered when / used for
User usr PL0 / EL0 normal application code (the only unprivileged mode)
System sys PL1 / EL1 privileged code that shares the User registers
Supervisor svc PL1 / EL1 after reset and after an SVC β€” the OS kernel
IRQ irq PL1 / EL1 taking a normal interrupt
FIQ fiq PL1 / EL1 fast interrupt β€” also banks R8–R12 for low latency
Abort abt PL1 / EL1 data or prefetch abort (memory fault)
Undefined und PL1 / EL1 undefined / trapped instruction
Hyp hyp PL2 / EL2 hypervisor
Monitor mon (Secure) / EL3 secure monitor, the TrustZone gateway

So "PL0/PL1/PL2 + modes" means: three privilege levels, with PL1 further split into several banked modes (svc, irq, fiq, abt, und, sys). This banking is exactly what AArch64 throws away in favour of flat registers + a single vector table.

Security states (TrustZone)

Orthogonal to the Exception levels is the Security state:

  • Secure vs Non-secure β€” chosen by SCR_EL3.NS. The Non-secure world (Linux, apps) cannot see Secure memory or peripherals; the Secure world (trusted OS, firmware) can see both.
  • EL3 is the gatekeeper: only the Secure Monitor at EL3 flips NS and brokers calls between the two worlds (via the SMC instruction).
  • Newer cores (Armv9 / v8.5 RME β€” Realm Management Extension) extend this to four worlds: Root, Secure, Realm, Non-secure (SCR_EL3.NSE:NS), adding confidential-compute "Realms".

3. Registers & Processor State (AArch64)

The general-purpose register file is deliberately simple:

  • X0–X30 β€” 31 flat 64-bit registers. W0–W30 are the low 32-bit views (writing a W register zeroes the top 32 bits).
  • X30 is the Link Register (LR) β€” bl/blr write the return address here.
  • XZR/WZR β€” the zero register: reads as 0, writes are discarded. (Register number 31 decodes as either XZR or SP depending on the instruction.)
  • PC β€” the program counter is not a general register in AArch64 (unlike AArch32's R15). You can't mov it around; you change it with branches and ERET.
  • SP β€” the stack pointer is banked per Exception level: SP_EL0, SP_EL1, SP_EL2, SP_EL3. The SPSel bit chooses whether the current EL uses its own SP_ELx (written ELxh) or shares SP_EL0 (ELxt).

PSTATE β€” the current processor state

PSTATE is the live collection of status bits (the AArch64 replacement for the AArch32 CPSR). The ones worth knowing:

  • N Z C V β€” the condition flags (Negative, Zero, Carry, oVerflow), set by compares and used by B.cond/CSEL.
  • DAIF β€” four interrupt masks: Debug, A = SError (async abort), I = IRQ, F = FIQ. Set a bit to disable that exception.
  • EL (current level), SP (which stack pointer), nRW (current width).
  • Security/hardening bits: PAN (Privileged Access Never), UAO, SSBS, BTYPE (for BTI).

You read/write these with the MRS (read) and MSR (write) instructions.

Procedure Call Standard (AAPCS64) β€” who owns which register

Registers Role
X0–X7 function arguments and return values
X8 indirect result location (big struct return)
X9–X15 caller-saved scratch (temporaries)
X16/X17 IP0/IP1 β€” intra-procedure-call scratch (linker veneers)
X18 platform register (reserved by the OS/ABI)
X19–X28 callee-saved (a function must preserve these)
X29 Frame Pointer (FP)
X30 Link Register (LR)

Key system registers (suffix _ELx, x = 1/2/3)

Register What it controls
SCTLR_ELx System Control β€” MMU on (M), caches (C,I), alignment (A), endianness (EE)
VBAR_ELx Vector Base Address β€” where the exception table lives
ELR_ELx Exception Link Register β€” where to return to
SPSR_ELx Saved PSTATE β€” processor state at the moment of exception
ESR_ELx Exception Syndrome β€” why the exception happened
FAR_ELx Fault Address β€” the VA that faulted
TTBR0/1_ELx, TCR_ELx, MAIR_ELx translation table bases, control, memory attributes

Cross-level control registers:

  • SCR_EL3 β€” Secure Configuration: NS (world), RW (lower-EL width), EA/IRQ/FIQ routing to EL3, HCE/SMD (enable HVC/disable SMC).
  • HCR_EL2 β€” Hypervisor Configuration: RW, TGE (trap EL0 to EL2), AMO/IMO/FMO (route SError/IRQ/FIQ to EL2), VM (enable stage-2), E2H (VHE).
  • CPACR_EL1 / CPTR_ELx β€” enable or trap FP/SIMD/SVE.

4. Floating-Point & NEON (Advanced SIMD)

AArch64 has one 128-bit vector register file shared by scalar floating-point and SIMD. SIMD = Single Instruction, Multiple Data β€” one instruction operates on several "lanes" at once. A scalar add computes A0+B0 per instruction; a SIMD add computes A0+B0, A1+B1, A2+B2, A3+B3 in a single instruction β€” e.g. ADD V2.4S, V0.4S, V1.4S adds four 32-bit lanes in parallel:

  • V0–V31, 128-bit each. Scalar FP math and NEON (Advanced SIMD, parallel operations on multiple lanes) use the same registers.
  • Each register has narrower views by name: Q = 128-bit (whole), D = 64, S = 32, H = 16, B = 8. For SIMD the register is sliced into lanes (e.g. V0.4S = four 32-bit floats).
  • Control/status: FPCR (rounding mode, flush-to-zero, default-NaN) and FPSR (cumulative exception flags).
  • Must be enabled: CPACR_EL1.FPEN (and CPTR_EL2/CPTR_EL3 must not trap). If disabled, the first FP/SIMD instruction takes a trap (syndrome EC=0x07). Kernels exploit this for lazy FP context save.
  • ABI (AAPCS): V0–V7 pass FP args/returns; V8–V15 are callee-saved (but only the low 64 bits D8–D15 are preserved); V16–V31 are caller-saved.
  • Data types: FP16/FP32/FP64, BF16 (machine learning), integer SIMD, saturating and polynomial arithmetic.

SVE / SVE2 (Scalable Vector Extension) is the modern successor to NEON for HPC/ML. Instead of a fixed 128-bit width it uses vector-length-agnostic registers Z0–Z31 whose width is implementation-defined (128–2048 bits) β€” the same binary runs on any width. It adds predicate registers P0–P15 (per-lane masks) and the first-fault register FFR. Enabled separately via CPACR.ZEN.


5. Instruction Sets β€” A64 vs A32/T32

A64 (AArch64) A32 / T32 (AArch32)
Width fixed 32-bit A32 fixed 32-bit; T32 mixed 16/32-bit
PC access not a register R15 = PC (reads as +8/+4)
Conditional only B.cond, CSEL, CCMP… most instructions conditional
Registers 31 flat GPRs 16, banked per mode
Addressing PC-relative ADRP, clean imm barrel shifter, literal pools
  • A32 ("ARM") β€” the classic 32-bit fixed-width encoding.
  • T32 ("Thumb"/Thumb-2) β€” mixes 16- and 32-bit encodings for code density; conditional execution comes via IT (If-Then) blocks.
  • A64 is a clean redesign: it drops per-instruction conditional execution (you use CSEL/CSINC/B.cond instead), drops load/store-multiple (LDM/STM), drops register banking, and makes PC and SP special rather than general. The payoff is simpler decoding, 31 registers, and native 64-bit operations.

6. LPAE (AArch32 only)

LPAE = Large Physical Address Extension β€” the 64-bit page-table descriptor format for 32-bit CPUs. It exists to break the 4 GB wall: a 32-bit VA can only name 4 GB, but LPAE lets the physical address reach 40 bits so the system can have more RAM, and it's required for hardware virtualization (stage-2) on AArch32.

Short descriptor (classic) Long descriptor (LPAE)
Enable default TTBCR.EAE = 1
Levels 2 3
Descriptor 32-bit 64-bit
VA 32-bit (4 GB) 32-bit, split TTBR0/TTBR1
PA 32-bit (40-bit via supersections) up to 40-bit

In AArch64 there is no "LPAE switch" β€” the 64-bit long-descriptor format is the only translation format, so this chapter is really an AArch32 compatibility note.


7. Exception Handling

An exception is any event that diverts the CPU to a higher (or same) Exception level: a system call, an interrupt, a memory fault. Handling them is the core job of EL1/EL2/EL3.

Kinds of exception

  • Synchronous β€” caused by an instruction, precisely at that instruction:
    • SVC (Supervisor Call) β†’ trap to EL1 (a Linux syscall).
    • HVC (Hypervisor Call) β†’ trap to EL2.
    • SMC (Secure Monitor Call) β†’ trap to EL3 (firmware/PSCI).
    • plus data/instruction aborts (MMU miss, permission fault), undefined instruction, alignment, misaligned PC/SP.
  • IRQ / FIQ β€” asynchronous interrupts delivered by the GIC. FIQ is conventionally the higher-priority/secure one.
  • SError β€” a System Error: an asynchronous external abort (e.g. a bus error reported late).

What the hardware does on entry

  1. Saves PSTATE into SPSR_ELx and the return address into ELR_ELx.
  2. Records the cause in ESR_ELx (the syndrome: which instruction class, which fault) and, for faults, the address in FAR_ELx.
  3. Masks DAIF (so the handler isn't immediately re-interrupted), selects the target EL and its stack pointer, and jumps to VBAR_ELx + a fixed offset.

Your handler runs, then executes ERET, which atomically restores PSTATE from SPSR_ELx and jumps to ELR_ELx β€” dropping back to the interrupted code (possibly at a lower EL).

The vector table (VBAR_ELx)

Unlike AArch32's one-entry-per-exception table, AArch64 has 16 entries arranged as 4 groups of 4, each entry 0x80 bytes apart (room for a short handler/branch):

Offset Exception came from… Sync IRQ FIQ SError
0x000 Current EL, using SP_EL0 000 080 100 180
0x200 Current EL, using SP_ELx 200 280 300 380
0x400 Lower EL, in AArch64 400 480 500 580
0x600 Lower EL, in AArch32 600 680 700 780

The source (current vs lower EL, which SP, which state) picks the group; the type (Sync/IRQ/FIQ/SError) picks the slot. Total table = 0x800 bytes. VBAR_ELx is the only thing that tells the CPU where your handlers live β€” set it up early in boot; until then exceptions jump to whatever VBAR resets to.

Worked example β€” a system call from EL0 to EL1

A user program calls write(). The C library puts the syscall number in X8, arguments in X0–X2, and executes SVC #0. Follow the registers (this is Figure 10-4):

  1. EL0 runs SVC #0 at, say, VA 0x4007c0. That's a synchronous exception targeting EL1.
  2. Hardware, in one atomic step (going "up" in the figure):
    • return address (the instruction after SVC) β†’ ELR_EL1,
    • current processor state PSTATE β†’ SPSR_EL1,
    • cause β†’ ESR_EL1 (EC = 0x15, "SVC from AArch64", + the #imm),
    • mask DAIF, switch to EL1, select SP_EL1,
    • PC = VBAR_EL1 + 0x400 β€” the "lower EL / AArch64 / synchronous" slot.
  3. The kernel handler at VBAR_EL1 + 0x400 saves the user registers, reads ESR_EL1 to confirm it's an SVC, calls sys_write, and leaves the result in X0.
  4. The kernel executes ERET (going "down"): hardware restores SPSR_EL1 β†’ PSTATE and ELR_EL1 β†’ PC, so the app resumes at the instruction right after SVC, back at EL0, with the return value in X0.

A data abort (page fault) works identically, except it's taken on the current instruction (which re-executes after the handler fixes the mapping), FAR_EL1 holds the faulting address, and ESR_EL1.EC says "data abort". An IRQ is the same shape too, but it lands in the IRQ slot (VBAR_EL1 + 0x480) and the interrupted instruction boundary β€” not the instruction itself β€” is what ELR_EL1 points back to.

Two-level handler: assembly top-half + a C subroutine

In practice the vector slot holds only a tiny assembly top-half that stacks the volatile registers and then calls a C subroutine to do the real work. Two return mechanisms nest:

  • Outer (exception) pair β€” the CPU entered via PC β†’ ELR_EL1 and PSTATE β†’ SPSR_EL1; ERET restores both and drops back to the interrupted code. This is the same "going up / going down" as the worked example above.
  • Inner (procedure-call) pair β€” the C routine is an ordinary AAPCS64 call: the top-half does PC β†’ LR (X30) on the BL, and RET branches back through LR. Pure register save/restore, no exception-level change.

So the handler is a V-into-a-V: ERET for the EL transition on the outside, RET for the function call on the inside.

Routing

Which EL an exception lands in is configurable: HCR_EL2.{AMO,IMO,FMO} can route SError/IRQ/FIQ to EL2 (and TGE makes EL0 traps go to a host at EL2); SCR_EL3.{EA,IRQ,FIQ} route them to EL3. This is how a hypervisor or secure monitor intercepts interrupts.


8. Caches

Caches hide slow memory β€” on this class of SoC the L3/system cache is only ~24 MB against ~32 GB of DDR, yet a cache hit is roughly a dozen times quicker than a DDR access. The closer to the core, the faster and smaller: L1 < L2 < L3.

Organisation (Cortex-A53 class):

  • L1 is split (Harvard): a separate I-cache (instructions) and D-cache (data) per core.
  • L2 is unified β€” holds both code and data β€” per cluster.
  • Inner vs outer: inner caches live inside the CPU cluster (L1, L2); outer caches are L3/L4 (L4 is rare in practice). The LLC (Last-Level Cache) is usually L3 β€” e.g. part of ARM's CCN (Cache-Coherent Network), ~24 MB, 12-way set-associative (this is the SYSCACHE L3).
  • Inclusive caches (like the A53 data side): the effective cache size is the size of the LLC β€” inner-cache lines are also held in the LLC, so you don't count them twice.
  • A cache line is usually 64 bytes β€” the smallest loadable unit, a contiguous block copied from memory on a miss.
  • The data cache is normally PIPT (Physically-Indexed, Physically-Tagged) so there's no aliasing; the I-cache may be VIPT.
  • Whether a region is cacheable, and how widely it's shared, comes from the page tables (AttrIndx β†’ MAIR for the type, SH for shareability), not from the cache itself. Caches are globally turned on with SCTLR_ELx.{M,C,I}.

Direct-mapped vs set-associative

In a direct-mapped cache each memory address maps to exactly one cache line. Simple, but addresses one cache-size apart collide: looping over 0x0, 0x100, 0x200, … keeps evicting and re-filling the same line β€” cache thrashing, and every access pays the miss cost.

A set-associative cache divides storage into equal ways. An address now maps to a set β€” one candidate line in each way β€” instead of a single line. So 0x0 and 0x100 land in the same set but can occupy different ways, and both stay cached: no thrashing. The cost: the address tells you the set but not which way the line sits in, so on every lookup you must check the tag in all ways of that set.

Terminology (ARM's definitions)

  • Line β€” the smallest loadable unit: a block of contiguous words from main memory (64 B here).
  • Way β€” a subdivision of the cache; each way is equal in size and indexed the same way.
  • Set β€” the lines at one index value, one taken from each way, grouped together.
  • Index β€” the part of the address that selects which line(s)/set the address may live in.
  • Tag β€” the part of the address stored with the line, identifying which main-memory address it holds.

Worked example β€” splitting an address into TAG / INDEX / OFFSET

64-bit addressing, 256 B D-cache, 64 B line, 2-way set-associative (deliberately tiny β€” you'd never ship a 256-byte cache). Each field is as wide as logβ‚‚(how many things it selects): every bit doubles the count (a bit is 0 or 1 β†’ Γ—2), so bits to number N things = logβ‚‚N, and it counts bytes / lines / sets β€” never the 8 bits inside a byte, because we address bytes.

  1. OFFSET β€” which byte within the line? 64 bytes β†’ logβ‚‚64 = 6 bits β†’ [5:0].
  2. INDEX β€” which set? Each way is 256 / 2 = 128 B = 2 lines β†’ 2 sets β†’ logβ‚‚2 = 1 bit β†’ [6].
  3. TAG β€” the remainder: 64 βˆ’ 6 βˆ’ 1 = 57 bits β†’ [63:7].
 Tag = [63:7]  (57 bits)  β”‚  Index = [6]  (1 bit)  β”‚  Offset = [5:0]  (6 bits)

[hi:lo] is inclusive, so it holds hi βˆ’ lo + 1 bits: [5:0] = 6, [63:7] = 57. On a 64-bit address the top bit is 63, not 64.

The lookup

To find an address the hardware:

  1. extracts the index ([6]) and reads the stored tag from that line in every way (both ways here) β€” this is "looking up a set";
  2. compares each way's tag against the address's tag β†’ a hit if one matches;
  3. on a hit, uses the offset ([5:0]) to pick the right byte/word from the line;
  4. on a miss where the line is valid but holds a different address β†’ evict that line, then linefill from the requested address.

Two important "points"

  • PoU β€” Point of Unification: the point where a single core's I-cache, D-cache and TLB see the same copy of memory. Relevant when you write code and then execute it.
  • PoC β€” Point of Coherency: the point where all observers (every core, DMA) see the same copy. Relevant for sharing with other masters.

Maintenance operations

Scope Operations
by address DC CVAC/CVAU/CIVAC/IVAC (clean/invalidate data), IC IVAU (invalidate instr)
by set/way DC CSW/CISW/ISW (used to flush a whole cache level)

Running freshly written/loaded code (JITs, bootloaders copying the next stage): the I-cache is not automatically coherent with the D-cache. You must: DC CVAU (push the code out of D-cache to PoU) β†’ DSB β†’ IC IVAU (drop stale instructions) β†’ DSB β†’ ISB (flush the pipeline). This was the root of the SYSCACHE cache-coherency bug.


9. Memory Management (MMU) β€” EL1 & EL0

The MMU translates virtual addresses (VA) to physical addresses (PA) and enforces permissions, using in-memory translation tables walked by hardware.

EL0 and EL1 share one translation regime (EL1&0) with two VA windows, so user and kernel can have separate page tables without reprogramming on every syscall:

Window VA looks like Base register Size set by
Low 0x0000_… TTBR0_EL1 TCR.T0SZ
High 0xFFFF_… TTBR1_EL1 TCR.T1SZ

The top VA bits decide which TTBR is used β€” conventionally user space lives in the low half (TTBR0) and the kernel in the high half (TTBR1). TCR_EL1 (Translation Control) holds the granule sizes, the physical address size (IPS), and the cacheability/shareability of the page-table walks.

ASID (Address Space ID): so the TLB doesn't have to be flushed on every context switch, user translations are tagged with an ASID carried in the TTBR. Entries marked nG (non-global) are ASID-specific; global kernel mappings are shared across all ASIDs.

Granules, levels, descriptors

Granule Reach / levels
4 KB 48-bit VA via levels L0–L3 (52-bit with LPA2)
16 KB up to 48/52-bit
64 KB 42/48-bit, 3 levels

A table walk descends through descriptors: a table descriptor points to the next level; a block descriptor maps a large region (huge page); a page descriptor is the final leaf. Each leaf carries attributes: AP (read/write permission), UXN/PXN (execute-never for user/privileged), AF (access flag), SH (shareability) and AttrIndx (which MAIR memory type).

Other regimes and two-stage translation

  • EL2: uses TTBR0_EL2 (and TTBR1_EL2 once VHE is on).
  • EL3: uses TTBR0_EL3 only β€” a single VA window.
  • Stage-2 (virtualization): under a hypervisor, a guest's translation is done twice. The guest's own tables map VA β†’ IPA (Intermediate Physical Address, stage-1); the hypervisor's tables (VTTBR_EL2/VTCR_EL2) then map IPA β†’ PA (stage-2). The guest thinks it owns physical memory; EL2 really controls it.

10. Memory Ordering

ARMv8-A has a weakly-ordered (relaxed) memory model: to go fast, the hardware may reorder, merge and speculate memory accesses. It is other-multi-copy-atomic (once any observer sees a write, all observers that see it see it in a consistent order). Program order between two accesses is guaranteed only when there's a dependency or you insert a barrier.

Memory types (set by the page tables)

  • Normal memory β€” for RAM/ROM. Freely cacheable, reorderable, speculatable. This is where you need barriers for ordering.
  • Device memory β€” for MMIO peripherals, with four strengths spelling out what's allowed: Device-nGnRnE (strongest, nothing allowed) Β· nGnRE Β· nGRE Β· GRE. The letters mean Gathering (merge accesses), Re-ordering, Early write acknowledgement β€” each n forbids one.

Ordering you get for free: data/address/control dependencies, accesses to the same location (coherence), and Device-nGnRnE.

Barriers

The weak model lets the core reorder, merge and speculate accesses; barriers claw ordering back where it matters. Two families: DMB/DSB act on data accesses (and cache/TLB/branch-predictor maintenance); ISB acts on instruction fetch (context).

Barrier Acts on Guarantee
DMB data accesses Orders memory-before vs memory-after: every load/store before it is visible to the shareability domain before any after it. Visibility ordering only β€” it does not wait for completion, and non-memory instructions may still reorder across it.
DSB data accesses + maintenance Everything DMB does plus completion: no instruction after it (not just loads/stores) runs until all prior accesses and cache/TLB/branch-predictor maintenance have completed.
ISB instruction fetch Flushes the pipeline and re-fetches the following instructions, so they are fetched and permission-checked against the current MMU/system-register config.

DMB β€” order, don't wait:

LDR x0, [x1]      // must be visible to the memory system before the STR below
DMB ISHLD
ADD x2, x2, #1    // non-memory β†’ may run before OR after the LDR is seen
STR x3, [x4]      // must be visible after the LDR above

DSB β€” order and wait for completion. Used after cache/TLB maintenance, and before signalling other cores (e.g. before SEV, so the event only arrives once the data has really landed):

DC ISW, x5        // must COMPLETE before the DSB can complete
STR x0, [x1]      // must COMPLETE before the DSB can complete
DSB ISH
ADD x2, x2, #3    // cannot execute until the DSB completes

ISB β€” refetch under new rules. After writing a system register that affects fetch or translation (SCTLR, TTBR, TCR, VBAR), pair DSB (let the write complete / become visible) then ISB (refetch) β€” otherwise the next instructions may still run under the old config.

Scope parameter (DMB/DSB; ISB takes none) = access Γ— domain:

  • access: LD (order earlier loads before later loads+stores), ST (order stores only), or full (both) β€” e.g. ISHLD, ISHST, ISH.
  • domain: SY (full system) βŠƒ OSH (outer-shareable) βŠƒ ISH (inner-shareable) βŠƒ NSH (non-shareable). Narrower = cheaper; pick the smallest that still covers the other observers you care about.

Quick mental model:

  • DMB β€” "A before B in the order other observers see" β€” ordering of visibility only.
  • DSB β€” "A finished before B even starts" β€” completion, and it stalls the core until then.
  • ISB β€” "reload the pipeline under the new MMU / system-register state" β€” a refetch.

Worked example β€” publishing freshly written code. A JIT, or a bootloader copying the next stage, writes instructions through the data side, but the core fetches through the instruction side β€” the two are not automatically coherent. The fix needs completion (DSB, not just the ordering a DMB would give) and then a refetch (ISB):

DC CVAU, Xn   // push the written code out of D-cache to the PoU
DSB ISH       // WAIT until the clean has completed (DMB would only order, not wait)
IC IVAU, Xn   // invalidate the stale instructions in the I-cache
DSB ISH       // WAIT until the invalidate has completed
ISB           // flush the pipeline β†’ fetch the fresh code

This is exactly the shape of the SYSCACHE cache-coherency fix (see Β§8) β€” and why DMB alone would have been wrong there: you needed the maintenance to complete, not merely be ordered.

Ordered accesses and atomics

  • Acquire / Release (one-way barriers): LDAR (load-acquire β€” nothing after it floats before) and STLR (store-release β€” nothing before it floats after). The natural building blocks for locks.
  • Exclusives: LDXR/STXR with a local exclusive monitor implement compare-and-swap loops (the classic LL/SC pattern).
  • LSE (Large System Extensions, v8.1): single-instruction atomics β€” LDADD, SWP, CAS, STADD… β€” which scale better than LL/SC under contention.

Extras worth knowing

Virtualization (EL2)

A hypervisor at EL2 runs guest OSes at EL1 using two-stage translation (Β§9) and traps: HCR_EL2 can trap guest wait-for-interrupt, certain register writes, etc., so EL2 can emulate hardware. VHE (Virtualization Host Extensions, HCR_EL2.E2H=1) lets a host kernel (like Linux+KVM) run directly at EL2 with an EL2&0 regime and TTBR1_EL2, avoiding costly EL2↔EL1 bounces. Guests receive virtual interrupts (vIRQ/vFIQ/vSError) via GICv3 virtualization.

TrustZone / world switch

SCR_EL3.NS selects Secure vs Non-secure; the EL3 Secure Monitor owns the switch. Normal-world software reaches firmware through the SMCCC (SMC Calling Convention): an SMC traps to EL3, which runs PSCI (Power State Coordination Interface β€” CPU_ON, CPU_OFF, SYSTEM_RESET), SiP (silicon-provider) and TRNG services. Secure memory is fenced off by the interconnect / TZASC. Typical boot chain: BL1 β†’ BL2 β†’ BL31 (EL3 monitor) β†’ BL33 (U-Boot/UEFI) β†’ OS.

Interrupts, timers, power

  • GIC (Generic Interrupt Controller, v3/v4): classifies interrupts as SGI (software), PPI (per-core private), SPI (shared peripheral) and LPI (message-based). The per-core CPU interface is accessed through system registers ICC_*_EL1.
  • Generic Timer: an always-on counter CNTPCT_EL0 at frequency CNTFRQ_EL0, with per-EL comparators (CNTP physical, CNTV virtual, CNTHP hyp) that raise interrupts.
  • Power hints: WFI (wait for interrupt β€” idle the core) and WFE/SEV (wait-for-event / send-event β€” used to park spinning cores cheaply).

Interconnect: AMBA ↔ flits ↔ CCN/CMN

Two layers. At the edge, masters speak a protocol from the AMBA family; inside, the interconnect carries everything as flits over a ring (CCN) or mesh (CMN).

  • Edge protocols (AMBA):
    • AXI4 (AMBA 4) β€” high-performance, 5 channels (AW/W/B, AR/R), no coherency.
    • ACE (AMBA 4) β€” AXI + coherency: adds snoop channels AC/CR/CD β†’ fully-coherent CPU clusters.
    • ACE-Lite (AMBA 4) β€” one-way / IO coherency (can snoop into caches, isn't itself snooped) β†’ DMA, GPU, accelerators.
    • CHI (AMBA 5) β€” packet/flit-native coherent protocol β†’ the mesh CMN (CCN's successor).
  • Transport (flits): a protocol message ("read line X") β†’ packet β†’ flits (flow-control units, where buffering/credits apply) β†’ phits (what physically moves per clock). A 64 B line = 512 bits β†’ e.g. 2 flits on a 256-bit data path. CHI defines four flit classes: REQ / RSP / SNP / DAT.
  • Nodes (CCN/CMN): RN-F / RN-I (request node, fully/IO-coherent) Β· HN-F (home node + L3 β€” the SYSCACHE HN-F nodes 0x20–0x26, modes OFF/SFONLY/HALF/FULL) Β· SN-F (slave node β†’ DMC β†’ DDR).

In one line: AMBA is the protocol at the edge; flits are how it's carried inside. An ACE AC snoop becomes an internal SNP flit; clearing CPUECTLR.SMPEN drops a core from the ACE coherency domain, so the HN-F stops sending it SNP flits. (CCN-512 is the one with the arm-ccn perf driver / DTS binding.)

Architecture versions & headline features

Version Added
v8.1 LSE atomics, VHE, PAN
v8.2 FP16, RAS (reliability), 52-bit addressing
v8.3 PAuth β€” pointer authentication (ROP defence)
v8.4 Secure-EL2, nested virtualization
v8.5 BTI (branch-target ID), MTE (memory tagging)
v9.0 SVE2 as baseline
v9.2+ RME/CCA β€” Realms, confidential compute (4 worlds)

Endianness is selectable (SCTLR.EE/E0E; data can be LE or BE, instructions are always little-endian). Alignment checking is SCTLR.A.

TLB & context maintenance β€” the sequences to remember

  • After editing page tables: write the entries β†’ DSB ISH β†’ TLBI (invalidate; e.g. VAE1IS by VA, ASIDE1IS by ASID, VMALLE1IS all) β†’ DSB ISH β†’ ISB.
  • After changing a system register that affects fetch or translation (SCTLR, TTBR, TCR, VBAR): issue an ISB so it takes effect before the next instructions.
  • Break-before-make: when changing a live block/page mapping, first install an invalid entry (+ TLBI) before writing the new one, to avoid the CPU seeing two conflicting translations.