Chris Collins

Tools · embedded

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, msr and the barriers; the M3 adds Thumb-2. A .cpu cortex-m0 line 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_BSRR sets and clears bits), the switches are GPIOA_IDR (0x40020010), the UART is USART1 (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 adds sets 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/str in every size with immediate, negative, pre- and post-indexed and shifted-register addressing, ldm/stm/push/pop with any registers, mul mla mls umull smull umlal smlal udiv sdiv clz rbit rev ubfx sbfx bfi bfc, it blocks, cbz/cbnz, mrs/msr, cpsie/cpsid, svc, wfi and 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, =constant into a mov.w or movw on 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: with 1b/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 svc go through it with the eight-word stacking and EXC_RETURN. Without one, main is called with sp at the top of SRAM and returning from it stops the program; so do b ., bkpt and a wfi with nothing to wake it. Faults (a bus error, an unaligned access on the M0, a Thumb-2 instruction on the M0, bx to 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.

Browser support

Any current browser, desktop or phone.

Privacy

Nothing leaves the browser. The program is saved in this browser’s local storage so it is still there next time; nothing is downloaded from anywhere else.

Runs entirely in your browser; nothing you type leaves this page.