ARM Thumb assembly playground
Write Cortex-M assembly and step through it on a simulated microcontroller: a GNU-syntax assembler for Thumb and Thumb-2 with literal pools and IT blocks, every 16- and 32-bit encoding split into its fields bit by bit, registers and NZCV with changes highlighted, memory and stack views, breakpoints, LEDs and switches on GPIO, a UART terminal, SysTick interrupts through a vector table, and Cortex-M0 or M3 cycle counts.
Added
How to use it
An assembler and a simulator for the instruction set of ARM’s microcontroller cores: Thumb on the Cortex-M0 (ARMv6-M) and Thumb-2 on the Cortex-M3 (ARMv7-M), written for this page and running entirely in it. The simulated chip has flash, SRAM, a GPIO port with eight LEDs and eight switches, a UART and the SysTick timer, at the addresses an STM32 would use, so the programs look like real firmware: load an address, write a register, wait for a flag.
How to use it
Pick an example or type in the Source pane (GNU as unified syntax); it assembles as you
type and errors appear under the source (click one to jump to the line). The Machine code
pane lists every instruction with its address, its halfwords and its cycle count, and the
literal pools with the constants in them.
-
Step (F10) runs one instruction, Back undoes it, Run (F5) runs until the program stops or hits a breakpoint. Click the gutter to the left of an address for a breakpoint. Speed runs flat out, in real time at 1 or 16 MHz (so a blink loop blinks at the rate it would), or ten steps a second.
-
Cortex-M0 / M3 chooses the core. The M0 has the 16-bit Thumb instructions plus
bl,mrs,msrand the barriers; the M3 adds Thumb-2. A.cpu cortex-m0line in the source chooses too. Code for the wrong core gets an error that says why. -
Registers shows r0–r12, sp, lr, pc and xPSR, the N Z C V flags, the IT state and whether the core is in an exception handler; what the last step changed is highlighted.
-
Devices: the LEDs are bits 0–7 of
GPIOA_ODR(0x40020014;GPIOA_BSRRsets and clears bits), the switches areGPIOA_IDR(0x40020010), the UART isUSART1(status at 0x40011000, data at 0x40011004) with a terminal and an input box, and the SysTick registers are shown as they count. -
Encoding splits the selected instruction into its fields bit by bit — both halfwords of a 32-bit one — and explains them: how a branch offset is rebuilt from S, J1, J2 and the immediate pieces, what a Thumb-2 "modified immediate" expands to, how an IT mask encodes then and else, and why a 16-bit
addssets the flags outside an IT block but not inside. -
Memory shows SRAM, the stack around sp (an exception’s eight-word frame appears there), the flash and the peripheral registers.
The examples: blinking an LED with a delay loop, summing an array, strlen and printing on the
UART, function calls under the ARM procedure call standard (AAPCS), a UART echo, a SysTick
interrupt with a proper vector table and wfi, and a tour of Thumb-2 (IT blocks, bit fields,
divide, 64-bit multiply, cbz).
The source is kept in this browser; the share link carries it.
What is supported
-
All 16-bit Thumb instructions, and the 32-bit Thumb-2 ones most code uses:
bl,b.w,b<cond>.w, data processing with modified immediates and shifted registers (add.w,orn,teq,rrx…),movw/movt,addw/subw,ldr/strin every size with immediate, negative, pre- and post-indexed and shifted-register addressing,ldm/stm/push/popwith any registers,mul mla mls umull smull umlal smlal udiv sdiv clz rbit rev ubfx sbfx bfi bfc,itblocks,cbz/cbnz,mrs/msr,cpsie/cpsid,svc,wfiand the barriers. Not included:tbb/tbh, the exclusive loads and stores,ldrd/strd, the DSP and floating-point instructions. -
The assembler chooses the 16-bit form whenever one fits, as LLVM’s does, and turns
ldr r0, =constantinto amov.wormovwon the M3 when one can make the value; otherwise the value goes in a literal pool at the end of the code or at.ltorg. -
Directives:
.text .data .bss .section .word .hword .byte .ascii .asciz .space .fill .align .balign .equ .set .ltorg .global .thumb_func .type .syntax .thumb .cpu,name = value, numeric local labels (1:with1b/1f) and expressions. -
Memory: flash at 0x00000000 (256 KB), SRAM at 0x20000000 (64 KB). If the program starts with a vector table, the core starts the way a real one does — sp from word 0, pc from word 1 — and SysTick and
svcgo through it with the eight-word stacking andEXC_RETURN. Without one,mainis called with sp at the top of SRAM and returning from it stops the program; so dob .,bkptand awfiwith nothing to wake it. Faults (a bus error, an unaligned access on the M0, a Thumb-2 instruction on the M0,bxto an even address) stop it with the reason. -
Cycle counts follow the Cortex-M0 and M3 technical reference manuals with a two-cycle pipeline refill after a taken branch; flash wait states are not modelled.
The encodings were checked against LLVM’s assembler (clang -target thumbv7m and thumbv6m):
about 190 instructions on the M3 and 100 on the M0 encode identically, the M0 rejects the
Thumb-2 forms LLVM rejects (it does accept adds r0, #-1, as subs r0, #1), and the examples
assemble to the same bytes. For other processors
there are the RISC-V, 8086, 68000 and
6502 tools; for the chip-level arithmetic of real STM32 and ESP32 parts
see microcontroller timing.
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser; nothing you type leaves this page.