Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

MicroPython for Microcontrollers  Module 7 – Memory limits, speed, native and viper code

Inline assembler: asm_thumb and asm_rv32

Write @micropython.asm_thumb functions for the Pico with registers, labels and loops, know what its Cortex-M0+ lacks, and where asm_rv32 and Xtensa fit.

  • Advanced
  • 15 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 6 of 15 minutes free
Free: An assembler function is a list of instructions · The Pico runs the ARMv6-M subset · Check yourself. With a pass: 2 in-depth parts and 3 interview answers, about 9 minutes.

What you will learn

  • Write a small @micropython.asm_thumb function that takes arguments in r0 to r3 and returns r0
  • Loop over a buffer with labels, conditional branches and byte loads, and count its cycles
  • Identify which Thumb instructions the RP2040's Cortex-M0+ cannot run, and what to write instead
  • Decide when hand-written assembler is worth its maintenance cost

Before you start

What you’ll learn · Before you start

What you will learn

  • Write a small @micropython.asm_thumb function that takes arguments in r0 to r3 and returns r0
  • Loop over a buffer with labels, conditional branches and byte loads, and count its cycles
  • Identify which Thumb instructions the RP2040's Cortex-M0+ cannot run, and what to write instead
  • Decide when hand-written assembler is worth its maintenance cost

Before you start

On this page

add(r0, r0, r1) is a whole MicroPython function body: under @micropython.asm_thumb it becomes one machine instruction, and asm_add(40, 2) returns 42. The inline assembler: MicroPython's way of writing a function in the processor's own instructions, one Python-style call per instruction, under @micropython.asm_thumb, asm_rv32 or asm_xtensa. The compiler turns the calls into machine code when it compiles the module. lets a few lines of a hot loop be written in the processor’s own instructions, each one a Python-style call, but a Pico runs only part of the instruction set the documentation’s examples use.

Every assembler function below runs on a model of that part, written from MicroPython’s instruction definitions and the RP2040 datasheet. It refuses what the Pico cannot run, with MicroPython’s own message, and counts the cycles of what it can.

An assembler function is a list of instructions

An assembler function takes up to four arguments, which must be named r0 to r3 in that order, and returns whatever is in r0 at the end, read as a signed integer. Each argument arrives in the register: One of the small, fast storage places inside a processor that instructions work on directly, 32 bits wide on the RP2040. An asm_thumb function receives its arguments in r0 to r3 and returns the value left in r0. of its name: a small integer as its value, a bytes, bytearray or array as the address of its data, a list or a tuple as the address of its items. A return type -> uint makes Python read r0 as unsigned.

The functions here are written for the board, with @micropython.asm_thumb. On a computer, run_adders.py puts the model, thumb_model.py, in place of the micropython module and calls them:

Three adders on the model of the Pico's instructions

Python · thumb/run_adders.py

"""Run adders.py on the Thumb model, then try an instruction from a Pyboard tutorial."""
import sys
import types

import thumb_model

sys.modules["micropython"] = types.SimpleNamespace(asm_thumb=thumb_model.asm_thumb)
from adders import asm_add, sadd, uadd  # noqa: E402  (after the stand-in is in place)

print("asm_add(40, 2) =", asm_add(40, 2), "in", asm_add.instructions, "instruction")
print("sadd(0x40000000, 0x40000000) =", sadd(0x40000000, 0x40000000))
print("uadd(0x40000000, 0x40000000) =", hex(uadd(0x40000000, 0x40000000)))

try:
    @thumb_model.asm_thumb
    def forty_two():
        movw(r0, 42)  # noqa: F821  (an instruction name, read by the assembler)
except SyntaxError as e:
    print("movw(r0, 42) on a Pico: SyntaxError:", e)

Output

asm_add(40, 2) = 42 in 1 instruction
sadd(0x40000000, 0x40000000) = -2147483648
uadd(0x40000000, 0x40000000) = 0x80000000
movw(r0, 42) on a Pico: SyntaxError: unsupported Thumb instruction 'movw' with 2 arguments

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 run_adders.py

thumb/adders.py

"""Assembler functions for a Pico: written for the board, run on a computer through thumb_model.py."""
import micropython


@micropython.asm_thumb
def asm_add(r0, r1):
    add(r0, r0, r1)


@micropython.asm_thumb
def sadd(r0, r1):
    add(r0, r0, r1)


@micropython.asm_thumb
def uadd(r0, r1) -> uint:
    add(r0, r0, r1)

thumb/thumb_model.py

"""A model of @micropython.asm_thumb for CPython, with the ARMv6-M subset a Raspberry Pi Pico runs.

Decorate a function with asm_thumb from here and call it. Its body is read as a list of
instructions with the meanings MicroPython's documentation gives them, and run on eight 32-bit
registers and the N, Z, C and V flags; r0 is returned as a signed integer, or unsigned with
-> uint. A bytes or bytearray argument arrives as the address of its data, as on the board.
Instructions the RP2040 lacks are refused with MicroPython's own message. The model also counts
the instructions run and their cycles by Table 81 of the RP2040 datasheet (zero wait states,
without MicroPython's own call overhead). It does not check branch ranges.
"""
import annotationlib
import types

MASK = 0xFFFFFFFF
ARMV7M_ONLY = {"clz", "rbit", "mrs", "movw", "movt", "movwt", "ldrex", "strex", "sdiv", "udiv",
               "it", "ite", "itt", "itte", "ittt", "itee", "ittee", "itttt"}
LOADS = {"ldrb": (1, 0x1F), "ldrh": (2, 0x3E), "ldr": (4, 0x7C)}
STORES = {"strb": (1, 0x1F), "strh": (2, 0x3E), "str": (4, 0x7C)}
TWO_REGS = {"mov", "cmp", "cmn", "tst", "and_", "orr", "eor", "bic", "mvn", "mul", "neg", "adc", "sbc",
            "lsl", "lsr", "asr", "ror"}
CONDITIONS = {
    "eq": lambda n, z, c, v: z, "ne": lambda n, z, c, v: not z,
    "cs": lambda n, z, c, v: c, "cc": lambda n, z, c, v: not c,
    "mi": lambda n, z, c, v: n, "pl": lambda n, z, c, v: not n,
    "vs": lambda n, z, c, v: v, "vc": lambda n, z, c, v: not v,
    "hi": lambda n, z, c, v: c and not z, "ls": lambda n, z, c, v: not c or z,
    "ge": lambda n, z, c, v: n == v, "lt": lambda n, z, c, v: n != v,
    "gt": lambda n, z, c, v: not z and n == v, "le": lambda n, z, c, v: z or n != v,
}


class Reg:
    def __init__(self, n):
        self.n = n


class Name:
    """Any other name in the body: an instruction when it is called, a label when it is passed."""

    def __init__(self, name, record):
        self.name, self.record = name, record

    def __call__(self, *args):
        self.record(self.name, args)


class Names(dict):
    def __init__(self, record):
        super().__init__({"r%d" % i: Reg(i) for i in range(8)})
        self.record = record

    def __missing__(self, key):
        return Name(key, self.record)


def _reg(op, x, top=7):
    if not isinstance(x, Reg) or x.n > top:
        raise SyntaxError("'%s' expects at most r%d" % (op, top))
    return x


def _imm(op, x, mask):
    if not isinstance(x, int) or x & ~mask:
        raise SyntaxError("'%s' integer 0x%x doesn't fit in mask 0x%x" % (op, x, mask))
    return x


def _check(op, a):
    """The operands of one instruction, checked as MicroPython's assembler checks them."""
    if op in ("mov", "cmp", "add", "sub") and len(a) == 2 and isinstance(a[1], int):
        return (_reg(op, a[0]), _imm(op, a[1], 0xFF))
    if op in ("add", "sub") and len(a) == 3:
        third = _imm(op, a[2], 0x7) if isinstance(a[2], int) else _reg(op, a[2])
        return (_reg(op, a[0]), _reg(op, a[1]), third)
    if op in ("lsl", "lsr", "asr") and len(a) == 3:
        return (_reg(op, a[0]), _reg(op, a[1]), _imm(op, a[2], 0x1F))
    if op in LOADS or op in STORES:
        base, offset = a[1]
        return (_reg(op, a[0]), _reg(op, base), _imm(op, offset, (LOADS.get(op) or STORES.get(op))[1]))
    if op in TWO_REGS and len(a) == 2:
        return (_reg(op, a[0]), _reg(op, a[1]))
    if op == "b" or (op.startswith("b") and op[1:] in CONDITIONS):
        if len(a) != 1 or not isinstance(a[0], Name):
            raise SyntaxError("'%s' expects a label" % op)
        return (a[0].name,)
    if op == "nop" and not a:
        return ()
    raise NotImplementedError("'%s' with %d arguments is not in this model" % (op, len(a)))


def asm_thumb(func):
    code = func.__code__
    params = code.co_varnames[:code.co_argcount]
    if len(params) > 4:
        raise SyntaxError("can only have up to 4 parameters to Thumb assembly")
    if list(params) != ["r%d" % i for i in range(len(params))]:
        raise SyntaxError("parameters must be registers in sequence r0 to r3")
    program, labels = [], {}

    def record(op, args):
        if op == "label":
            labels[args[0].name] = len(program)
        elif op in ARMV7M_ONLY or op.endswith("_w"):
            raise SyntaxError("unsupported Thumb instruction '%s' with %d arguments" % (op, len(args)))
        else:
            program.append((op, _check(op, args)))

    types.FunctionType(code, Names(record))(*[Reg(i) for i in range(len(params))])
    hints = annotationlib.get_annotations(func, format=annotationlib.Format.STRING)
    return Function(func.__name__, program, labels, hints.get("return") == "uint")


class Function:
    """An assembled function: call it like the board would, then read instructions and cycles."""

    def __init__(self, name, program, labels, unsigned):
        self.__name__, self.program, self.labels, self.unsigned = name, program, labels, unsigned
        self.instructions = self.cycles = 0

    def __call__(self, *args):
        regs, memory = [0] * 8, {}
        for i, arg in enumerate(args):
            if isinstance(arg, (bytes, bytearray)):
                base = 0x20000000 + 0x10000 * (len(memory) + 1)
                memory[base] = arg
                regs[i] = base
            else:
                regs[i] = int(arg or 0) & MASK  # None and False arrive as 0, True as 1
        flag = {"n": False, "z": False, "c": False, "v": False}

        def val(x):
            return regs[x.n] if isinstance(x, Reg) else x

        def find(addr, size):
            for base, buf in memory.items():
                if base <= addr and addr + size <= base + len(buf):
                    return buf, addr - base
            raise IndexError("address 0x%08x is outside the buffers passed in" % addr)

        def nz(r):
            flag["n"], flag["z"] = bool(r >> 31), r == 0
            return r

        def add(x, y, carry=0):
            r = (x + y + carry) & MASK
            flag["c"], flag["v"] = x + y + carry > MASK, bool((x ^ r) & (y ^ r) & 0x80000000)
            return nz(r)

        def shift(op, x, s):
            if s == 0:
                return nz(x)
            if op == "lsl":
                flag["c"] = s <= 32 and bool(x >> (32 - s) & 1)
                return nz(x << s & MASK)
            if op == "lsr":
                flag["c"] = s <= 32 and bool(x >> (s - 1) & 1)
                return nz(x >> s & MASK)
            signed = x - (1 << 32) if x >> 31 else x
            flag["c"] = bool(signed >> (min(s, 32) - 1) & 1)
            return nz(signed >> min(s, 32) & MASK)

        pc = self.instructions = self.cycles = 0
        while pc < len(self.program):
            op, a = self.program[pc]
            pc += 1
            self.instructions += 1
            self.cycles += 1
            if self.instructions > 10_000_000:
                raise RuntimeError("more than 10,000,000 instructions: a loop that never ends?")
            if op in ("add", "sub", "cmp", "cmn"):
                x, y = (val(a[0]), val(a[1])) if len(a) == 2 else (val(a[1]), val(a[2]))
                r = add(x, y) if op in ("add", "cmn") else add(x, ~y & MASK, 1)  # x - y is x + ~y + 1
                if op in ("add", "sub"):
                    regs[a[0].n] = r
            elif op in ("adc", "sbc"):
                x, y = val(a[0]), val(a[1])
                regs[a[0].n] = add(x, y if op == "adc" else ~y & MASK, int(flag["c"]))
            elif op == "neg":
                regs[a[0].n] = add(0, ~val(a[1]) & MASK, 1)
            elif op == "mov":
                regs[a[0].n] = val(a[1])
                if isinstance(a[1], int):  # mov of a number sets N and Z; between registers, no flag
                    nz(regs[a[0].n])
            elif op in ("and_", "orr", "eor", "bic", "mvn", "mul", "tst"):
                x, y = val(a[0]), val(a[1])
                r = nz({"and_": x & y, "orr": x | y, "eor": x ^ y, "bic": x & ~y, "mvn": ~y,
                        "mul": x * y, "tst": x & y}[op] & MASK)
                if op != "tst":
                    regs[a[0].n] = r
            elif op in ("lsl", "lsr", "asr"):
                if len(a) == 3:
                    s = a[2] if a[2] or op == "lsl" else 32  # an immediate 0 means 32 for lsr and asr
                    regs[a[0].n] = shift(op, val(a[1]), s)
                else:
                    regs[a[0].n] = shift(op, val(a[0]), val(a[1]) & 0xFF)
            elif op == "ror":
                x, s = val(a[0]), val(a[1]) & 31
                regs[a[0].n] = nz((x >> s | x << (32 - s)) & MASK)
            elif op in LOADS:
                size = LOADS[op][0]
                buf, i = find(val(a[1]) + a[2], size)
                regs[a[0].n] = int.from_bytes(buf[i:i + size], "little")
                self.cycles += 1  # a load from RAM takes 2 cycles
            elif op in STORES:
                size = STORES[op][0]
                buf, i = find(val(a[1]) + a[2], size)
                buf[i:i + size] = (val(a[0]) & ((1 << 8 * size) - 1)).to_bytes(size, "little")
                self.cycles += 1  # a store to RAM takes 2 cycles
            elif op == "b" or op[1:] in CONDITIONS:
                if op == "b" or CONDITIONS[op[1:]](flag["n"], flag["z"], flag["c"], flag["v"]):
                    pc = self.labels[a[0]]
                    self.cycles += 1  # a taken branch takes 2 cycles, one not taken 1
        r0 = regs[0]
        return r0 if self.unsigned or not r0 >> 31 else r0 - (1 << 32)

asm_add(40, 2) took one instruction. The same addition of 0x40000000 and 0x40000000 reads as −2,147,483,648 without a return type, because bit 31 is set, and as 0x80000000 with -> uint, the case the documentation’s hints use. The last line is the documentation’s own first example, movw(r0, 42), refused by the model as a Pico refuses it.

The model knows the instructions of this lesson: moves, additions and subtractions, comparisons, the logical and shift operations, multiplication, byte, halfword and word loads and stores, labels and branches. It runs them with the meanings MicroPython’s documentation gives, on eight registers and four condition flags: Four bits a processor sets from the result of most arithmetic: N for negative, Z for zero, C for carry and V for overflow. Conditional branches such as bne or bgt test them to decide whether to jump.. It is not the board: it skips the call overhead and checks no branch ranges, so a function that works there still needs a run on a Pico before it ships.

Passing a float to an assembler function

A float argument is converted to a whole number before it reaches the register, so asm_add(1.5, 2) returns 3 with no error. Assembler sees only 32-bit words: convert readings to integers, or pass an array of floats by address.

Programmer Calculator (Bitwise) See a register's 32 bits as signed and unsigned values.

Checkpoint

Check: Arguments in r0 to r3, the result in r0

  1. Question 1 of 2 An asm_thumb function ends with r0 = 0xFFFFFFFE and has no return type. What does Python get?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: -2

    The value left in r0 is the result, and MicroPython reads it as a signed integer unless the function is declared -> uint. 0xFFFFFFFE has bit 31 set, so it reads as −2.

  2. Question 2 of 2 What arrives in r0 when a bytearray is passed as the first argument of an asm_thumb function?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The address of its data

    MicroPython converts each argument to one 32-bit word: a small integer to its value, a buffer such as a bytearray to the address of its data, a list or tuple to the address of its items.

The Pico runs the ARMv6-M subset

The RP2040’s two Cortex-M0+ cores implement the ARMv6-M architecture, which the datasheet describes as all of the 16-bit Thumb instructions of ARMv7-M except CBZ, CBNZ and IT, plus six 32-bit ones. The documentation’s assembler pages describe the larger Thumb-2 set of ARMv7-M, with the Pyboard’s STM32 in mind, and MicroPython’s assembler checks which set it is building for: on a Pico it refuses the ARMv7-M instructions, movw among them, with “unsupported Thumb instruction”. Examples that use them fail at compile time, and their module with them.

Two other assemblers follow the same idea. @micropython.asm_rv32 writes 32-bit RISC-V code: added in 1.25, it is enabled on the rp2 port when an RP2350 runs in RISC-V mode, and its arguments arrive in a0 to a3. A routine written for asm_thumb cannot run there: asm_rv32 has its own registers and instructions, so each needs its own code. The Xtensa assembler, with arguments in a2 to a5, is built into the ESP8266’s standard firmware; since 1.28 it also supports windowed cores such as the ESP32’s, but the esp32 port’s standard build leaves it switched off.

Assembler Decorator Arguments Where the standard firmware has it
Thumb @micropython.asm_thumb r0 to r3 rp2 in Arm mode (ARMv6-M on the RP2040), and stm32
RISC-V @micropython.asm_rv32 a0 to a3 rp2 with an RP2350 in RISC-V mode
Xtensa @micropython.asm_xtensa a2 to a5 the esp8266 port; on the ESP32 only in a custom build

Checkpoint

Check: The ARMv6-M subset of the Pico

  1. Question 1 of 2 A function with movw(r0, 42), copied from the Pyboard tutorial, is imported on a Pico. What happens?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: SyntaxError when the module compiles: unsupported Thumb instruction 'movw' with 2 arguments

    MicroPython's Thumb assembler checks which instruction set it builds for. On the RP2040 it refuses ARMv7-M instructions such as movw, so the module does not compile at all.

  2. Question 2 of 2 Which decorator and argument names does an assembler function need on an RP2350 running in RISC-V mode?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: @micropython.asm_rv32, with arguments a0 to a3

    An assembler function is written for one instruction set. On an RP2350 in RISC-V mode that is @micropython.asm_rv32, whose arguments are named a0 to a3.

Check yourself

Arguments, return values and the Pico’s subset: each question explains the choice you make.

Practice that adapts to you

Practise this lesson

6 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 6 How many arguments can an asm_thumb function take, and how must they be named?

    Choose one answer.

    Show the answer: Question 1 of 6

    Answer: Up to four, named r0, r1, r2 and r3 in that order

    MicroPython 1.29.0 accepts up to four arguments, which must be named r0 to r3 in order; the value in r0 at the end is the result.

  2. Question 2 of 6 What does asm_add(2.9, 3) return, where asm_add is add(r0, r0, r1)?

    Type a number.

    Show the answer: Question 2 of 6

    Answer: 5

    A float argument is converted to a whole number before it reaches the register, so 2.9 becomes 2, and 2 + 3 = 5.

  3. Question 3 of 6 A routine returns a 32-bit hardware register that often has bit 31 set. What makes Python see it correctly?

    Choose one answer.

    Show the answer: Question 3 of 6

    Answer: Declare the function -> uint

    r0 holds the right 32 bits either way. Declared -> uint, Python sees them as an unsigned number; otherwise a set bit 31 makes the result negative.

  4. Question 4 of 6 Which architecture do the RP2040's Cortex-M0+ cores implement?

    Choose one answer.

    Show the answer: Question 4 of 6

    Answer: ARMv6-M, a subset of the Thumb instructions

    The RP2040 has two Cortex-M0+ cores, which implement ARMv6-M: the 16-bit Thumb instructions of ARMv7-M except CBZ, CBNZ and IT, and six 32-bit instructions.

  5. Question 5 of 6 A team copies its tested asm_thumb routine, unchanged, to a board whose RP2350 runs in RISC-V mode. What should they expect?

    Choose one answer.

    Show the answer: Question 5 of 6

    Answer: It cannot run there; the routine needs a version of its own written for asm_rv32

    An assembler routine is written for one instruction set. On an RP2350 in RISC-V mode the firmware builds @micropython.asm_rv32, with arguments in a0 to a3, so the Thumb routine has to be written again for it.

  6. Question 6 of 6 A team wants inline Xtensa assembler on ESP32 boards running the standard MicroPython firmware. What do they find?

    Choose one answer.

    Show the answer: Question 6 of 6

    Answer: The standard esp32 build leaves the Xtensa assembler off, so they need a custom firmware build

    MicroPython 1.28 added inline Xtensa assembly for windowed cores such as the ESP32's, but the esp32 port's standard configuration leaves it off. The ESP8266's standard firmware has it.

Loops in Thumb assembler: labels, branches and loads, counted in cycles Pro: opens with a Pro, Premium or Ultimate pass

A loop is where assembler can pay, and where it is easiest to get wrong. This part builds a byte-sum loop from labels, flags and loads, steps through its registers, and counts its cycles before and after unrolling it.

In this part:

  • Labels, branches and the flags they test
  • A byte-sum loop, register by register
  • Unrolling, counted: 7 cycles a byte against 4

1 diagram · 1 runnable example (Python, sums.py) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

What the Cortex-M0+ lacks, and when assembler is worth its cost Pro: opens with a Pro, Premium or Ultimate pass

Code written for the larger ARMv7-M set meets a smaller one on the Pico, and every one of the gaps has a known way around it. This part lists them, checks one of the workarounds on all 65,536 inputs it must handle, and ends with the questions to ask before any assembler is written.

In this part:

  • Pyboard instructions a Pico refuses, and what to write instead
  • When hand-written assembler pays
  • Exercise: count the set bits of a word
  • Count the set bits of a word in Thumb assembler
    • Results of the sample tests

1 runnable example (Python) · 1 exercise with sample tests · about 7 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Mid to senior

    An inline assembler routine that ran on a Pyboard fails on a Pico at import with SyntaxError: unsupported Thumb instruction 'movwt' with 2 arguments. Why, and how do you port it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Senior to staff

    A teammate wants to rewrite a viper filter in inline assembler for a 20 % gain on the filter. How do you decide, and what must come with the change if you accept it?

    Model answer, Pro. First step and follow-ups

  3. Mid to senior

    Estimate how long a Thumb loop that sums 64 KB of bytes takes on a Pico at its default clock, and say what the estimate leaves out.

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. How does an @micropython.asm_thumb function take arguments and return a value?

    Up to four arguments, named r0 to r3 in that order, each in the register of its name; it returns what is in r0 at the end, read as signed unless the return type is uint.

  2. What reaches the register when an assembler function is given a bytearray?

    The address of its data. A list or a tuple gives the address of its items, and a small integer its value.

  3. What happens to a float passed to an assembler function?

    It is turned into a whole number before it reaches the register, with no error: asm_add(1.5, 2) returns 3.

  4. Why does movw(r0, 42) fail on a Pico?

    The RP2040's Cortex-M0+ cores run the ARMv6-M subset, and MicroPython's assembler refuses ARMv7-M instructions such as movw at compile time, as an unsupported Thumb instruction.

  5. Where do @micropython.asm_rv32 and the Xtensa assembler run?

    asm_rv32, added in 1.25 with arguments in a0 to a3, on an RP2350 in RISC-V mode. The Xtensa assembler, with arguments in a2 to a5, is in the ESP8266's standard firmware, while the standard ESP32 build leaves it off.

  6. Can an asm_thumb routine run on an RP2350 in RISC-V mode?

    No: asm_rv32 has its own registers and instructions, so each assembler needs code of its own.

Key takeaways

  • An @micropython.asm_thumb function takes up to four arguments in r0 to r3 and returns r0, signed unless the return type is uint; buffers arrive as the address of their data.
  • The RP2040 runs the ARMv6-M subset, and MicroPython’s assembler refuses ARMv7-M instructions such as movw there at compile time.
  • @micropython.asm_rv32 is the RISC-V assembler of an RP2350 in RISC-V mode, and the Xtensa assembler is standard on the ESP8266 only.

Terms in this lesson

condition flags
Four bits a processor sets from the result of most arithmetic: N for negative, Z for zero, C for carry and V for overflow. Conditional branches such as bne or bgt test them to decide whether to jump.
inline assembler
MicroPython's way of writing a function in the processor's own instructions, one Python-style call per instruction, under @micropython.asm_thumb, asm_rv32 or asm_xtensa. The compiler turns the calls into machine code when it compiles the module.
register
One of the small, fast storage places inside a processor that instructions work on directly, 32 bits wide on the RP2040. An asm_thumb function receives its arguments in r0 to r3 and returns the value left in r0.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.