Zápis funkcí obsahujících instrukce Thumb a Thumb-2 v MicroPythonu (dokončení)

1. 2. 2024
Doba čtení: 27 minut

Sdílet

Ilustrační snímek Autor: Depositphotos
Ilustrační snímek
V závěrečném článku si popíšeme zbývající podporované instrukce i způsob jejich použití. Taktéž si porovnáme rychlost výpočtů realizovaných přímo strojovými instrukcemi v porovnání s Pythonem.

Obsah

1. Neférový benchmark na úvod: rychlost realizace vnořených programových smyček

2. Vylepšený způsob zobrazení strojového kódu přeložené funkce

3. Počítaná programová smyčka s testem provedeným na začátku

4. Zbývající instrukce ze sady Thumb implementované a podporované MicroPythonem

5. Zpracování osmibitových a šestnáctibitových dat

6. Bitové operace

7. Ukázka použití vybraných bitových operací

8. Bitové posuny a rotace

9. Ukázka použití operací posunu a rotace

10. Operace součinu

11. Rychlost instrukce mul

12. Porovnání rychlosti výpočtu součinu MicroPythonem

13. Instrukce pro porovnání dvou hodnot

14. Zbylé instrukce pro práci na úrovni jednotlivých bitů

15. Prefix IT v instrukční sadě Thumb-2

16. Realizace prefixu IT v MicroPythonu

17. Speciální instrukce

18. Popsané instrukce a způsob jejich zápisu v MicroPythonu

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Neférový benchmark na úvod: rychlost realizace vnořených programových smyček

Hned na začátku tohoto článku se pokusíme porovnat rychlost dvou implementací vnořených programových smyček. První implementaci jsme si již ukázali minule – je založena na podmíněných skocích. Připomeňme si, že se v tomto případě jedná o smyčky, jejichž počitadla jsou uložena v registrech R1 a R2. Tělo vnitřní smyčky se vykoná 100×100=10000 krát, což znamená, že návratovou hodnotou této funkce bude taktéž hodnota 10000 (návratová hodnota je předána v pracovním registru R0):

@micropython.asm_thumb
def loop():
    mov(r0, 0)
    mov(r1, 100)        # počáteční hodnota počitadla vnější smyčky
    label(outer_loop)   # označení začátku vnější programové smyčky
    mov(r2, 100)        # počáteční hodnota počitadla vnitřní smyčky
    label(inner_loop)   # označení začátku vnitřní programové smyčky
    add(r0, r0, 1)      # tělo smyčky
    sub(r2, r2, 1)      # snížení hodnoty počitadla + nastavení příznaků
    bne(inner_loop)     # opakování vnitřní smyčky
    sub(r1, r1, 1)      # snížení hodnoty počitadla + nastavení příznaků
    bne(outer_loop)     # opakování vnější smyčky

Dobu trvání si necháme změřit v mikrosekundách:

import utime
t1 = utime.ticks_us()
loop()
t2 = utime.ticks_us()
print(utime.ticks_diff(t2, t1))

S výsledkem:

4704

Druhá verze benchmarku je naprogramována v čistém a idiomatickém Pythonu, a to bez použití optimalizací:

def loop():
    x = 0
    for i in range(100):
        for j in range(100):
            x+=1
    return x
 
 
import utime
t1 = utime.ticks_us()
loop()
t2 = utime.ticks_us()
print(utime.ticks_diff(t2, t1))

Výsledkem je v tomto případě kód, který je přibližně 12× pomalejší:

59022
Poznámka: to je kupodivu velmi dobrý výsledek, který ukazuje kvalitu překladu MicroPythonu. Musíme si totiž uvědomit, že výpočty v Pythonu obecně probíhají s neomezenými celočíselnými hodnotami, takže ve skutečnosti je druhý benchmark realizován mnohem obecnějším kódem. Podrobnosti si vysvětlíme v samostatném článku věnovaném Viperu.

2. Vylepšený způsob zobrazení strojového kódu přeložené funkce

V předchozím článku jsme si taktéž ukázali funkci sloužící k zobrazení strojového kódu přeložené funkce, což má význam zejména pro funkce s dekorátorem @micropython.asm_thumb. Připomeňme si, že takové funkce vždy začínají instrukcí:

push {r1, r4, r5, r6, r7, lr}

a končí instrukcí:

pop {r1, r4, r5, r6, r7, pc}

Důležitá je druhá instrukce, která je zakódována do dvojice bajtů:

f2 bd

Tyto dva bajty budeme hledat v paměťovém bloku se strojovým kódem. Nemáme totiž k dispozici jinou informaci o tom, na které adrese strojový kód končí (tato informace bude existovat, ovšem s verzemi Pythonu se mění). Upravená varianta pomocné funkce sloužící pro analýzu a výpis funkce přeložené do strojového kódu tedy může vypadat například takto (realizací je samozřejmě větší množství):

import machine
import array
 
 
def inspect(f):
    baddr = bytes(array.array("O", [f]))
    addr = int.from_bytes(baddr, "little")
    print("function object at: 0x%08x" % addr)
    print("number of args:     %u" % machine.mem32[addr + 4])
    code_addr = machine.mem32[addr + 8]
    print("machine code at:    0x%08x" % code_addr)
 
    previous = -1
    size = 0
    while True:
        current = machine.mem8[code_addr + size]
        size += 1
        if current == 0xbd and previous == 0xf2:
            break
        previous = current
 
    print(f"machine code size:  {size} bytes")
 
    print("-- code --")
    for i in range(size):
        print(f"{machine.mem8[code_addr + i]:02x}", end=" ")
    print("\n----------")
Poznámka: minimální délka strojového kódu by měla být rovna čtyřem bajtům.

3. Počítaná programová smyčka s testem provedeným na začátku

Navažme nyní na téma, kterému jsme se věnovali na konci předchozího článku. Ukážeme si další počítanou programovou smyčku, v níž se ovšem test na ukončení bude pro změnu provádět na jejím začátku ihned po odečtení jedničky od počitadla (a samozřejmě se otočí podmínka ve skoku – nyní bude skok proveden při nulovém počitadle). Tento příklad je vlastně v mnoha ohledech totožný s původní smyčkou s testem na konci:

@micropython.asm_thumb
def loop():
    mov(r0, 0)
    mov(r1, 100)    # počáteční hodnota počitadla
    label(loop)     # označení začátku programové smyčky
    add(r0, r0, 4)  # tělo smyčky
    sub(r1, r1, 1)  # snížení hodnoty počitadla + nastavení příznaků
    bne(loop)       # skok v případě že se nedosáhlo nuly

Ovšem vzhledem k tomu, že změna stavu počitadla (snížení jeho hodnoty o jedničku) a následný test je proveden na začátku smyčky, je nutné při inicializaci počitadla do něj vložit hodnotu 101 a nikoli 100:

@micropython.asm_thumb
def loop():
    mov(r0, 0)
    mov(r1, 101)    # počáteční hodnota počitadla + 1
    label(loop)     # označení začátku programové smyčky
    sub(r1, r1, 1)  # snížení hodnoty počitadla + nastavení příznaků
    beq(break_loop) # skok v případě že se dosáhlo nuly
    add(r0, r0, 4)  # tělo smyčky
    b(loop)
    label(break_loop)

Pokud si jsme jisti, že hodnota počitadla před vstupem do smyčky nebude nulová, lze dokonce i snížení hodnoty počitadla (s nastavením příznaků) přenést na konec smyčky. Smyčka v tomto případě tedy skutečně začíná podmíněným skokem:

@micropython.asm_thumb
def loop():
    mov(r0, 0)
    mov(r1, 101)    # počáteční hodnota počitadla + 1
    label(loop)     # označení začátku programové smyčky
    beq(break_loop) # skok v případě že se dosáhlo nuly
    add(r0, r0, 4)  # tělo smyčky
    sub(r1, r1, 1)  # snížení hodnoty počitadla + nastavení příznaků
    b(loop)
    label(break_loop)

Výsledek si ověříme přímo v REPLu MicroPythonu. Měla by se vypsat hodnota 400, protože se stokrát zvýší hodnota v registru R0 o 4:

>>> loop()
400

Překlad do strojového kódu vypadá takto:

>>> inspect(loop)
function object at: 0x20008f20
number of args:     0
machine code at:    0x200090e0
machine code size:  16 bytes
-- code --
f2 b5 00 20 65 21 49 1e 01 d0 00 1d fb e7 f2 bd
----------

Jedná se o strojový kód s osmi instrukcemi Thumb uloženými v šestnácti bajtech.

4. Zbývající instrukce ze sady Thumb implementované a podporované MicroPythonem

V navazujících kapitolách si popíšeme všechny další instrukce ze sady Thumb, které jsou podporované MicroPythonem. U některých instrukcí si ukážeme demonstrační příklady, další instrukce budou pouze popsány (v případě, že se podobají instrukcím jiným). Na tomto místě je vhodné doplnit, že mapování mezi strojovými instrukcemi sady Thumb a MicroPythonem není ve všech případech stoprocentní, protože se například odlišuje způsob zápisu operandů apod. Na tyto rozdíly pochopitelně upozorníme.

5. Zpracování osmibitových a šestnáctibitových dat

Poměrně často se v programech manipuluje s osmibitovými či šestnáctibitovými hodnotami (ASCII znaky, zvukové vzorky atd.). To je pro čistě 32bitové čipy, mezi něž patří i mikrořadiče Cortex-M, poměrně problematické, protože většina operací je provedena s 32bitovými registry. Ovšem máme k dispozici alespoň specializované instrukce určené pro načtení osmibitové či šestnáctibitové hodnoty, přičemž zbytek 32bitového registru je vynulován. Podobně existují i instrukce pro uložení pouze osmi či šestnácti bitů původně 32bitového registru:

Instrukce Stručný popis
ldrb(Rt, [Rn, imm5]) načtení bajtu, zbylých 24 bitů se vynuluje
ldrh(Rt, [Rn, imm6]) načtení 16bitového slova, zbylých 16 bitů se vynuluje
ldr(Rt, [Rn, imm7]) načtení celého 32bitového slova (již známe)
   
strb(Rt, [Rn, imm5]) uložení bajtu (spodních 8 bitů registru)
strh(Rt, [Rn, imm6]) uložení 16bitového slova (spodních 16 bitů registru)
str(Rt, [Rn, imm7]) uložení celého 32bitového slova

Vyzkoušejme si tedy, jak se načte jediný bajt, nezávisle na původním obsahu registru (tj. do osmi nejnižších bitů registru se načte hodnota bajtu a zbylých 24 bitů bude vynulováno):

from array import array
 
control = array('B', [255, 255, 100, 255, 255])
 
 
@micropython.asm_thumb
def load_byte(r0):
    mov(r1, 0)         # vynulujeme R1
    mvn(r1, r1)        # nyni bude R1 obsahovat same jednicky
    ldrb(r1, [r0, 2])  # nacteme jediny bajt z pole (ten s hodnotou 100)
    mov(r0, r1)        # navratova hodnota
 
 
load_byte(control)

Tento prográmek po svém spuštění vypíše hodnotu 100. To znamená, že z pole, které obsahuje bajty s různými hodnotami, se načte skutečně pouze jediný bajt, a to konkrétně bajt s offsetem 2 (schválně jsou okolo tohoto bajtu další hodnoty 255 a nikoli nulové hodnoty).

Podobně lze realizovat načtení šestnáctibitového slova:

from array import array
 
control = array('B', [255, 255, 100, 0, 255, 255])
 
 
@micropython.asm_thumb
def load_halfword(r0):
    mov(r1, 0)         # vynulujeme R1
    mvn(r1, r1)        # nyni bude R1 obsahovat same jednicky
    ldrh(r1, [r0, 2])  # nacteme jedine 16bitove slova z pole (to s hodnotou 100 + 0*256)
    mov(r0, r1)
 
 
load_halfword(control)

6. Bitové operace

Další skupinou instrukcí, které nalezneme v instrukční sadě Thumb a které jsou podporovány i MicroPythonem, jsou instrukce určené pro provádění základních bitových operací. Všechny tyto instrukce mají jednotný formát s dvojicí vstupních registrů, přičemž jeden z těchto registrů bude obsahovat i výsledek výpočtu – nejedná se tedy o „tříadresový kód“, který jsme mohli vidět zejména u instrukcí ADD a SUB:

 15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 0 | 0 | 0 | 0 |    operace    |    Rs     |    Rd     |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

Jedná se o tyto instrukce:

Kód Operace Ekvivalent ARM Význam
0000 AND Rd, Rs ANDS Rd, Rd, Rs Rd:= Rd AND Rs
1100 ORR Rd, Rs ORRS Rd, Rd, Rs Rd := Rd OR Rs
0001 EOR Rd, Rs EORS Rd, Rd, Rs Rd:= Rd EOR Rs (EOR=XOR)
       
1000 TST Rd, Rs TST Rd, Rs Nastavení příznaků podle operace Rd AND Rs
1110 BIC Rd, Rs BICS Rd, Rd, Rs Rd := Rd AND NOT Rs
1111 MVN Rd, Rs MVNS Rd, Rs Rd := NOT Rs

Způsob zápisu těchto instrukcí v MicroPythonu je následující:

Instrukce Zápis v MicroPythonu
AND Rd, Rs and_(Rd, Rs)
ORR Rd, Rs orr(Rd, Rs)
EOR Rd, Rs eor(Rd, Rs)
TST Rd, Rs tst(Rd, Rs)
BIC Rd, Rs bic(Rd, Rs)
MVN Rd, Rs mvn(Rd, Rs)
Poznámka: povšimněte si, že u instrukce AND je nutné v MicroPythonu použít pseudofunkci and_ s podtržítkem na konci. Je tomu tak z toho důvodu, že and je v Pythonu klíčovým slovem.

7. Ukázka použití vybraných bitových operací

Některé z výše popsaných bitových operací si otestujeme na jednoduchých příkladech.

Instrukce AND:

@micropython.asm_thumb
def and_operation(r0, r1):
    and_(r0, r1)
 
 
print(hex(and_operation(0x00, 0x00)))
print(hex(and_operation(0xff, 0xaa)))
print(hex(and_operation(0x18, 0xf0)))
print(hex(and_operation(0x18, 0x0f)))

Výsledky:

0x0
0xaa
0x10
0x8

Instrukce ORR:

@micropython.asm_thumb
def or_operation(r0, r1):
    orr(r0, r1)
 
 
print(hex(or_operation(0x00, 0x00)))
print(hex(or_operation(0xff, 0xaa)))
print(hex(or_operation(0x18, 0xf0)))
print(hex(or_operation(0x18, 0x0f)))

Výsledky:

0x0
0xff
0xf8
0x1f

Instrukce BIC:

@micropython.asm_thumb
def bic_operation(r0, r1):
    bic(r0, r1)
 
 
print(hex(bic_operation(0x00, 0x00)))
print(hex(bic_operation(0xff, 0xaa)))
print(hex(bic_operation(0x18, 0xf0)))
print(hex(bic_operation(0x18, 0x0f)))

Výsledky:

0x0
0x55
0x8
0x10

A konečně instrukce MVN:

@micropython.asm_thumb
def mvn_operation(r0):
    mvn(r0, r0)
 
 
print(hex(mvn_operation(0x00)))
print(hex(mvn_operation(0xff)))
print(hex(mvn_operation(0x18)))
print(hex(mvn_operation(0x0f)))

Výsledky:

-0x1
-0x100
-0x19
-0x10

8. Bitové posuny a rotace

Do další skupiny instrukcí patří instrukce provádějící bitové posuny a rotace. Nalezneme zde čtveřici instrukcí, a to konkrétně bitový posun doleva i doprava, dále aritmetický posun doprava (zachová se hodnota znaménka) a taktéž bitovou rotaci. Všechny čtyři zmíněné instrukce existují ve variantě s jedním cílovým registrem a dvěma zdrojovými registry, přičemž jeden ze zdrojových registrů je současně i registrem cílovým. Tento formát instrukcí již známe:

 15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 0 | 0 | 0 | 0 |    operace    |    Rs     |    Rd     |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

Konkrétně se jedná o tyto instrukce:

Kód Operace Ekvivalent ARM Význam
0010 LSL Rd, Rs MOVS Rd, Rd, LSL Rs Rd := Rd << Rs (bitový posun)
0011 LSR Rd, Rs MOVS Rd, Rd, LSR Rs Rd := Rd >> Rs (bitový posun)
0100 ASR Rd, Rs MOVS Rd, Rd, ASR Rs Rd := Rd ASR Rs (aritmetický posun)
0111 ROR Rd, Rs MOVS Rd, Rd, ROR Rs Rd := Rd ROR Rs (rotace)
Instrukce Zápis v MicroPythonu
LSL Rd, Rs lsl(Rd, Rs)
LSR Rd, Rs lsr(Rd, Rs)
ASR Rd, Rs asr(Rd, Rs)
ROR Rd, Rs ror(Rd, Rs)

Aritmetické a bitové posuny (ovšem nikoli rotace) existují taktéž ve vlastní variantě, kdy je počet bitů 0..31 zakódován přímo v instrukčním slovu:

 15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 |operace| posun 0-31 bitů   |    Rs     |    Rd     |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

Konkrétně se jedná o tyto instrukce:

Operace Význam
LSL Rd, Rs, #shift Rd := Rs << #shift (bitový posun)
LSR Rd, Rs, #shift Rd := Rs >> #shift (bitový posun)
ASR Rd, Rs, #shift Rd := Rs ASR #shift (aritmetický posun)

9. Ukázka použití operací posunu a rotace

Opět si ukažme, jaký vliv mají výše popsané instrukce na hodnoty předané do testovacích funkcí.

Logický posun doleva:

@micropython.asm_thumb
def lsl_operation(r0, r1):
    lsl(r0, r1)
 
 
print(hex(lsl_operation(0x01, 0)))
print(hex(lsl_operation(0x01, 1)))
print(hex(lsl_operation(0x01, 2)))
print(hex(lsl_operation(0x01, 3)))

Výsledky (jedná se o násobení mocninami dvojky):

0x1
0x2
0x4
0x8

Logický posun doprava:

@micropython.asm_thumb
def lsr_operation(r0, r1):
    lsr(r0, r1)
 
 
print(hex(lsr_operation(0x100, 0)))
print(hex(lsr_operation(0x100, 1)))
print(hex(lsr_operation(0x100, 2)))
print(hex(lsr_operation(0x100, 3)))

Výsledky (jedná se o dělení mocninami dvojky):

0x100
0x80
0x40
0x20

Aritmetický posun doprava:

@micropython.asm_thumb
def asr_operation(r0, r1):
    asr(r0, r1)
 
 
print(hex(asr_operation(-0x100, 0)))
print(hex(asr_operation(-0x100, 1)))
print(hex(asr_operation(-0x100, 2)))
print(hex(asr_operation(-0x100, 3)))

Výsledky (jedná se o dělení mocninami dvojky se zachováním znaménka):

-0x100
-0x80
-0x40
-0x20

A konečně rotace doprava či doleva (podle znaménka druhého operandu):

@micropython.asm_thumb
def ror_operation(r0, r1):
    ror(r0, r1)
 
 
print(hex(ror_operation(1, -3)))
print(hex(ror_operation(1, -2)))
print(hex(ror_operation(1, -1)))
print(hex(ror_operation(1, 0)))
print(hex(ror_operation(1, 1)))
print(hex(ror_operation(1, 2)))
print(hex(ror_operation(1, 3)))

Výsledky (první tři rotace jsou de facto doleva, poslední tři doprava):

0x8
0x4
0x2
0x1
-0x80000000
0x40000000
0x20000000

10. Operace součinu

V instrukční sadě Thumb se nachází i instrukce provádějící operaci součinu. Tato instrukce se jmenuje MUL a je zajímavá ve třech ohledech:

  1. násobí se dvě 32bitové hodnoty, ovšem výsledkem je taktéž pouze 32bitová hodnota (horních 32 bitů je tedy useknuto, 64bitové výsledky produkuje instrukce, která na jádrech Cortex-M0+ atd. není dostupná)
  2. cílový registr Rd je totožný se druhým zdrojovým registrem (to platí i pro mnoho dalších ALU operací kromě součtu a rozdílu)
  3. nastavují se příznaky N a Z, nikoli C a V (přitom C by měl svůj význam)

Pozor ovšem na to, že instrukce MUL se v MicroPythonu musí zapsat jen se dvěma operandy, protože první registr je současně registrem zdrojovým i cílovým:

@micropython.asm_thumb
def mul(r0, r1):
    mul(r0, r1)

Způsob překladu této funkce do strojového kódu vypadá následovně:

push {r1, r4, r5, r6, r7, lr}
muls r0, r1, r0
pop {r1, r4, r5, r6, r7, pc}

Ověřme si, jaké výsledky instrukce MUL produkuje.

Násobení nulou:

>>> mul(0, 0)
0
 
>>> mul(0, 1000)
0
 
>>> mul(1000, 0)
0

Kladné a záporné činitele:

 
>>> mul(2, 3)
6
 
>>> mul(-2, 3)
-6
 
>>> mul(2, -3)
-6
 
>>> mul(-2, -3)
6

Přetečení výsledků přes 32 bitů:

>>> mul(1<<29, 2)
1073741824
 
>>> mul(1<<30, 2)
-2147483648
 
>>> mul(1<<31, 2)
0
>>> mul(1<<29, -2)
-1073741824
 
>>> mul(1<<30, -2)
-2147483648
 
>>> mul(1<<31, -2)
0

11. Rychlost instrukce mul

V úvodním článku o MicroPythonu a instrukční sadě Thumb jsme si mj. řekli, že v instrukční sadě Thumb nalezneme i instrukci MULS určenou pro násobení dvou 32bitových operandů, přičemž výsledek je taktéž 32bitový (zapamatuje se jen spodních 32bitů výsledku – to jsme si ostatně již prakticky ukázali před chvílí). Při implementaci mikroprocesoru je možné zvolit, jakým typem násobičky se tato instrukce bude provádět. V případě, že se má jednat o výkonnější čip (a aplikace operaci násobení skutečně využije), může se použít rychlá násobička, která celou operaci dokáže provést v jediném taktu (samozřejmě se měření provádí při postupně zaplňované pipeline). Pokud se ovšem má jednat o levnější a méně výkonný čip, lze násobení implementovat po krocích, což sice trvá celých 32 taktů, ovšem potřebná plocha čipu a i energetická náročnost je mnohem menší, než v případě jednocyklové násobičky.

Zkusme si tedy změřit rychlost instrukce MULS. Víme již, že dvojice vnořených smyček, v jejichž těle se provádí stotisíckrát součet, trvá přibližně 4704 mikrosekund. Nahraďme tedy instrukci ADDS za MULS (s tím, že měříme i dobu trvání instrukcí zajišťujících iteraci atd.):

@micropython.asm_thumb
def loop_mul():
    mov(r0, 10)
    mov(r1, 100)        # počáteční hodnota počitadla vnější smyčky
    label(outer_loop)   # označení začátku vnější programové smyčky
    mov(r2, 100)        # počáteční hodnota počitadla vnitřní smyčky
    label(inner_loop)   # označení začátku vnitřní programové smyčky
    mul(r0, r0)         # tělo smyčky
    sub(r2, r2, 1)      # snížení hodnoty počitadla + nastavení příznaků
    bne(inner_loop)     # opakování vnitřní smyčky
    sub(r1, r1, 1)      # snížení hodnoty počitadla + nastavení příznaků
    bne(outer_loop)     # opakování vnější smyčky
 
 
import utime
t1 = utime.ticks_us()
loop_mul()
t2 = utime.ticks_us()
print(utime.ticks_diff(t2, t1))

V případě běhu na RP2040 bude výsledek prakticky totožný (v rámci přesnosti měření času):

4676

Z toho plyne, že operace MULS je na tomto konkrétním čipu provedena stejně rychle jako instrukce ADDS. Pokud by MULS trvala významně déle, bylo by to patrné na výsledku měření, a to i přesto, že měříme i čas trvání SUBS a podmíněného skoku BNE.

12. Porovnání rychlosti výpočtu součinu MicroPythonem

Opět se pokusme přepsat opakující se operaci násobení do MicroPythonu. Nejedná se o příliš férové srovnání, protože kód není optimalizován. Na druhou stranu je však napsán idiomaticky:

def loop_mul():
    x = 10
    for i in range(100):
        for j in range(100):
            x*=1
    return x
 
 
import utime
t1 = utime.ticks_us()
loop_mul()
t2 = utime.ticks_us()
print(utime.ticks_diff(t2, t1))

Výsledek bude získán za 63364 časových jednotek:

63364

To vlastně odpovídá srovnání rychlosti operací součtu: 4704 vs 59022.

Poznámka: Pythonní verze ve skutečnosti provádí i další operace, například podporuje čísla s de facto neomezeným rozsahem atd. Ostatně pokud změníte operaci násobení na x*=2, nejenže se nemusíte dočkat výsledku, ale může dojít i k zaplnění celé operační paměti.

13. Instrukce pro porovnání dvou hodnot

S instrukcí CMP určenou pro porovnání dvou hodnot jsme se již setkali. Tato instrukce ve skutečnosti existuje ve dvou variantách – porovnání dvou registrů a porovnání registru s krátkou osmibitovou konstantou (což se velmi často využije, protože se typicky porovnává s nulou či jedničkou). A zajímavá je i existence instrukce pro součet obsahu dvou registrů a nastavení všech příznaků:

Instrukce Zápis v MicroPythonu Význam
CMP Rd, #konstanta cmp(Rd, konstanta) porovnání dvou hodnot s nastavením příznaků
CMP Rd, Rs cmp(Rd, Rs) porovnání dvou hodnot s nastavením příznaků
CMN Rd, Rs cmn(Rd, Rs) součet dvou 32bitových hodnot s nastavením příznaků

14. Zbylé instrukce pro práci na úrovni jednotlivých bitů

V MicroPythonu nalezneme i dvě instrukce ze sady Thumb-2, které pracují na úrovni jednotlivých bitů. Jedná se o tyto instrukce:

Instrukce Zápis v MicroPythonu Význam
CLZ Rd, Rn clz(Rd, Rn) detekce délky sekvence nulových bitů v registru (hledáno od nejvyššího bitu)
RBIT Rd, Rn rbit(Rd, Rn) otočení bitů v předaném vstupním registru
Poznámka: tyto instrukce nebudou dostupné na čipech s jádrem Cortex-M0(+) či Cortex-M1.

15. Prefix IT v instrukční sadě Thumb-2

Tvůrci instrukční sady Thumb-2 se taktéž snažili nějakým způsobem nahradit kdysi populární kombinaci příznakových bitů, které byly součástí většiny instrukcí klasické RISCové instrukční sady A32. Z tohoto důvodu do instrukční sady Thumb-2 přidali jednu z nejzajímavějších instrukcí, které kdy pro RISCové mikroprocesory vznikly (a otázkou je, do jaké míry se jedná o RISCovou instrukci).

Jedná se vlastně o instrukční prefix nazvaný IT podle sousloví if-then. Tento prefix může být aplikován na jednu až čtyři instrukce následující za prefixem. Ihned za prefixem IT se (bez mezery) udává, zda má být daná instrukce provedena při splnění podmínky (T – then) či naopak při jejím nesplnění (E – else). U první instrukce je automaticky předpokládáno T, tudíž se uvádí maximálně tři kombinace znaků T/E. Samozřejmě je taktéž nutné zapsat i testovanou podmínku – může se jednat o kódy používané jak u podmíněných skoků (Thumb, AArch64), tak i v podmínkových bitech (ARM32):

Kód Význam Předchozí operace porovnání
EQ Z==1 (rovno) signed i unsigned
NE Z==0 (nerovno) signed i unsigned
CS C==1 (větší nebo rovno) unsigned
CC C==0 (menší než) unsigned
MI N==1 (záporný výsledek) signed
PL N==0 (kladný nebo nulový výsledek) signed
VS V==1 (přetečení) signed
VC V==0 (nedošlo k přetečení) signed
HI C==1 & Z==0 (vetší než) unsigned
LS C==0 | Z==1 (menší nebo rovno) unsigned
GE N==V (větší nebo rovno) signed
LT N!=V (menší než) signed
GT Z==0 & N==V (větší než) signed
LE Z==1 N!=V (menší nebo rovno) signed

V praxi to může znamenat, že zápis v assembleru:

ITEEE EQ

značí, že pokud je nastaven příznak zero (rovnost), je provedena jen první instrukce následující za prefixem, kdežto další tři instrukce nebudou provedeny (třikrát „else“).

Pokud by se měly provést tři instrukce v případě kladného výsledku předchozího porovnání, použil by se zápis:

ITTT PL

Porovnejme si nyní tři identické algoritmy. První z nich je implementovaný s využitím instrukcí A32 s podmínkovými bity:

        LDREQ r0,[r1]      ; if EQ then LDR
        LDRNE r0,[r2]      ; if NE then LDR
        ADDEQ r0, r3, r0   ; if EQ then ADD
        ADDNE r0, r4, r0   ; if NE then ADD

V případě použití instrukční sady Thumb musíme využít podmíněné skoky se všemi nepříjemnostmi, které z toho plynou:

        BNE L1             ; opačná podmínka - přeskočení instrukce
        LDR r0, [r1]
        ADD r0, r3, r0     ; máme štěstí: můžeme prohodit pořadí operandů
        B L2
L1
        LDR r0, [r2]
        ADD r0, r4, r0
L2

U instrukční sady Thumb-2 lze v tomto případu s výhodou použít prefixovou instrukci IT:

        ITETE EQ
        LDR r0, [r1]
        LDR r0, [r2]
        ADD r0, r3, r0
        ADD r0, r4, r0

Na závěr si ještě všechny tři implementace pro zajímavost porovnáme, a to jak z hlediska velikosti programového kódu, tak i z hlediska celkové doby trvání výpočtu:

Instrukční sada Velikost kódu Počet cyklů
ARM (RISC) 16 bajtů 4 cykly
Thumb 12 bajtů 4–20 cyklů
Thumb-2 10 bajtů 4–5 cyklů

Výsledek: pokud máme kvalitní překladač, bude instrukční sada Thumb-2 s velkou pravděpodobností lepší, než snaha o implementaci A32. Tento rozdíl bude ještě více patrný na mikrořadičích a tedy i jádrech Cortex-M, protože zde může být program uložen v relativně pomalé Flash ROM.

16. Realizace prefixu IT v MicroPythonu

MicroPython prefix IT podporuje, a to ve všech variantách, tedy například:

ite
itt
itee
itete
iteee
ittte

atd.

Do zvoleného prefixu se zapíše podmínka:

ite(eq)
ite(lt)
ittte(ne)

A potom již následuje jedna až čtyři instrukce vykonané v případě, že podmínka je či naopak není splněna. Například můžeme vrátit výsledek 100 či 200 na základě porovnání dvou argumentů funkce:

cmp(r0, r1)
ite(eq)
mov(r0, 100) # if r0 == r1
mov(r0, 200) # if r0 != r1
Poznámka: je ovšem nutné mít na paměti, že populární mikrořadiče s jádry Cortex-M0+ tuto instrukci nepodporují.

17. Speciální instrukce

A konečně nám zbývá několik speciálních instrukcí, resp. instrukcí, které nespadají do žádné výše uvedené kategorie:

bitcoin školení listopad 24

Instrukce Zápis v MicroPythonu Význam
NOP nop() pouze zvýšení PC (pseudoinstrukce)
WFI wfi() pozastavení MCU dokud nepřijde přerušení
CPSID příznaky cpsid(příznaky) zákaz přerušení
CPSIE příznaky cpsie(příznaky) povolení přerušení
MRS(Rd, APRS/PSR) mrs(Rd, special_reg) přesun obsahu speciálního registru do pracovního registru
Poznámka: poslední instrukci lze použít například pro sledování, jak aritmetické a logické instrukce nastavují příznakové registry. Ty jsou totiž uloženy ve speciálním registru PSR.

18. Doposud popsané instrukce a způsob jejich zápisu v MicroPythonu

Prozatím jsme se seznámili se zápisem následujících instrukcí v MicroPythonu. I když se to nezdá, jedná se o většinu instrukcí ze sady Thumb:

Instrukce Zápis v MicroPythonu Stručný popis
MOV r0, #0 mov(r0, 0) uložení krátké konstanty do pracovního registru
MOVW r0, #1000 movw(r0, 1000) uložení 16bitového slova do pracovního registru
LDR r0, [r0, 0] či ldr r0, =adresa ldr(r0, [r0, 0]) přečtení 32bitového slova ze zadané adresy
LDRB r0, [r0, 0] či ldrb r0, =adresa ldrb(r0, [r0, 0]) přečtení bajtu ze zadané adresy
LDRW r0, [r0, 0] či ldrw r0, =adresa ldrw(r0, [r0, 0]) přečtení 16bitového slova ze zadané adresy
     
ADDS r1, r1, #1 add(r0, r0, 1) součet registru s konstantou, zápis do obecně jiného registru
SUBS r1, r1, #1 sub(r1, r1, 1) rozdíl, zde konkrétně snížení hodnoty registru R1 o jedničku s nastavením příznaků
CMP Rd, #konstanta cmp(Rd, konstanta) porovnání dvou hodnot s nastavením příznaků
CMP Rd, Rs cmp(Rd, Rs) porovnání dvou hodnot s nastavením příznaků
CMN Rd, Rs cmn(Rd, Rs) součet dvou 32bitových hodnot s nastavením příznaků
     
AND Rd, Rs and_(Rd, Rs) operace logického součinu bit po bitu
ORR Rd, Rs orr(Rd, Rs) operace logického součtu bit po bitu
EOR Rd, Rs eor(Rd, Rs) operace nonekvivalence bit po bitu
TST Rd, Rs tst(Rd, Rs) nastavení příznaků podle operace logického součinu bit po bitu
BIC Rd, Rs bic(Rd, Rs) operace AND NOT
MVN Rd, Rs mvn(Rd, Rs) negace všech bitů
     
LSL Rd, Rs lsl(Rd, Rs) bitový posun doleva
LSR Rd, Rs lsr(Rd, Rs) bitový posun doprava
ASR Rd, Rs asr(Rd, Rs) aritmetický posun doprava
ROR Rd, Rs ror(Rd, Rs) rotace
     
CLZ Rd, Rn clz(Rd, Rn) detekce délky sekvence nulových bitů v registru (hledáno od nejvyššího bitu)
RBIT Rd, Rn rbit(Rd, Rn) otočení bitů v předaném vstupním registru
     
B cil_skoku b(cil_skoku) nepodmíněný skok na zadanou adresu (omezený rozsah, typicky pro jednu funkci/subrutinu)
BNE cil_skoku či b.ne cil_skoku bne(cil_skoku) podmíněný skok za podmínky, že příznak Z (zero) není nastaven
BEQ cil_skoku či b.ne cil_skoku bne(cil_skoku) podmíněný skok za podmínky, že příznak Z (zero) je nastaven
     
PUSH {registry} push({registry}) uložení vybraných pracovních registrů na zásobník
POP {registry} push({registry}) obnovení vybraných pracovních registrů ze zásobníku
     
NOP nop() pouze zvýšení PC (pseudoinstrukce)
WFI wfi() pozastavení MCU dokud nepřijde přerušení
CPSID příznaky cpsid(příznaky) zákaz přerušení
CPSIE příznaky cpsie(příznaky) povolení přerušení
MRS(Rd, APRS/PSR) mrs(Rd, special_reg) přesun obsahu speciálního registru do pracovního registru

19. Repositář s demonstračními příklady

Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro MicroPython běžící na čipech s architekturou Cortex-M0+, popř. Cortex-M3/M4 (a otestovaných na RP2040) byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs:

# Demonstrační příklad Stručný popis příkladu Cesta
1 return_constant.py návratová hodnota z funkce s Thumb instrukcemi https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/return_constant.py
2 return_big_constant1.py pokus o vrácení příliš velké konstanty (nekorektní) https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/return_big_constant1.py
3 return_big_constant2.py pokus o vrácení příliš velké konstanty (nekorektní na Cortex-M0+) https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/return_big_constant2.py
4 return_big_constant3.py vrácení 32bitové konstanty https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/return_big_constant3.py
       
5 inc1.py předání argumentu do funkce s Thumb instrukcemi https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/inc1.py
6 inc2.py pokus o předání argumentu v parametru špatného jména https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/inc2.py
       
7 add.py součet dvou předaných argumentů https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/add.py
8 add_four.py součet čtyř předaných argumentů (nekorektní) https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/add_four.py
9 add_five.py součet pěti předaných argumentů https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/add_five.py
       
10 inspect_function.py získání strojového kódu přeložených funkcí https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/inspect_function.py
11 no_op.py funkce bez příkazů, která se má přeložit do strojového kódu https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/no_op.py
12 branch1.py využití instrukce nepodmíněného skoku https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/branch1.py
13 branch2.py skok dopředu o několik instrukcí https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/branch2.py
14 branch3.py skok vzad https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/branch3.py
15 loop1.py programová smyčka s počitadlem a podmíněným skokem https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/loop1.py
16 loop2.py zjednodušená varianta programové smyčky https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/loop2.py
17 loop3.py vnořené programové smyčky https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/loop3.py
18 loop4.py benchmark: vnořené smyčky naprogramované v assembleru https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/loop4.py
19 loop5.py benchmark: vnořené smyčky naprogramované v Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/loop5.py
       
20 inspect_function2.py získání strojového kódu přeložených funkcí https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/inspect_function2.py
21 loop6.py programová smyčka s testem na začátku https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/loop6.py
22 loop7.py programová smyčka s testem na začátku (další varianta) https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/loop7.py
23 mul.py instrukce součinu https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/mul.py
24 loop_mul1.py benchmark: rychlost násobení (strojové instrukce) https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/loop_mul1.py
25 loop_mul2.py benchmark: rychlost násobení (Python) https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/loop_mul2.py
26 ldrb.py načtení jediného bajtu s rozšířením hodnoty na 32 bitů https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/ldrb.py
27 ldrh.py načtení jediného 16bitového slova s rozšířením hodnoty na 32 bitů https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/ldrh.py
       
28 and.py ukázka použití strojové instrukce AND https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/and.py
29 or.py ukázka použití strojové instrukce OR https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/or.py
30 bic.py ukázka použití strojové instrukce BIC https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/bic.py
31 mvn.py ukázka použití strojové instrukce MVN https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/mvn.py
32 lsl.py ukázka použití strojové instrukce LSL https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/lsl.py
33 lsr.py ukázka použití strojové instrukce LSR https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/lsr.py
34 asr.py ukázka použití strojové instrukce ASR https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/asr.py
35 ror.py ukázka použití strojové instrukce ROR https://github.com/tisnik/most-popular-python-libs/blob/master/micropython-thumb/ror.py

20. Odkazy na Internetu

  1. Online ARM converter
    https://armconverter.com/?disasm
  2. Fast Filters for the Pyboard
    https://github.com/peterhin­ch/micropython-filters
  3. How to load 32 bit constant from assembler with @micropython.asm_thumb
    https://forum.micropython­.org/viewtopic.php?f=21&t=12931&sid=25­de8871fa9cfcf8cafb6318f9d8ba3a
  4. Pi pico, micropython.asm_thumb: ADR Rd, <label> and LDR Rd, <label> not implemented?
    https://github.com/orgs/mi­cropython/discussions/12257
  5. MicroPython documentation
    https://docs.micropython.or­g/en/latest/index.html
  6. Inline assembler for Thumb2 architectures
    https://docs.micropython.or­g/en/latest/reference/asm_thum­b2_index.html
  7. Inline assembler in MicroPython
    https://docs.micropython.or­g/en/latest/pyboard/tutori­al/assembler.html#pyboard-tutorial-assembler
  8. MCU market turns to 32-bits and ARM
    http://www.eetimes.com/do­cument.asp?doc_id=1280803
  9. Cortex-M0 Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0.php
  10. Cortex-M0+ Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0plus.php
  11. ARM Processors in a Mixed Signal World
    http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world
  12. RISCové mikroprocesory s komprimovanými instrukčními sadami
    https://www.root.cz/clanky/riscove-mikroprocesory-s-komprimovanymi-instrukcnimi-sadami/
  13. RISCové mikroprocesory s komprimovanými instrukčními sadami (2)
    https://www.root.cz/clanky/riscove-mikroprocesory-s-komprimovanymi-instrukcnimi-sadami-2/
  14. ARM Architecture (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_architecture
  15. Cortex-M0 (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_Cortex-M0
  16. Cortex-M0+ (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_Cortex-M#Cortex-M0.2B
  17. Improving ARM Code Density and Performance
    New Thumb Extensions to the ARM Architecture Richard Phelan
  18. The ARM Processor Architecture
    http://www.arm.com/produc­ts/processors/technologies/in­struction-set-architectures.php
  19. Thumb-2 instruction set
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.ddi0344c/Beiiegaf.html
  20. Introduction to ARM thumb
    http://www.eetimes.com/dis­cussion/other/4024632/Intro­duction-to-ARM-thumb
  21. ARM, Thumb, and ThumbEE instruction sets
    http://www.keil.com/suppor­t/man/docs/armasm/armasm_CEG­BEIJB.htm
  22. An Introduction to ARM Assembly Language
    http://dev.emcelettronica­.com/introduction-to-arm-assembly-language
  23. Processors – ARM
    http://www.arm.com/produc­ts/processors/index.php
  24. The ARM Instruction Set
    http://simplemachines.it/doc/ar­m_inst.pdf
  25. The Thumb instruction set
    http://apt.cs.manchester.ac­.uk/ftp/pub/apt/peve/PEVE05/Sli­des/05_Thumb.pdf
  26. Why Learn Assembly Language?
    http://www.codeproject.com/Ar­ticles/89460/Why-Learn-Assembly-Language
  27. Is Assembly still relevant?
    http://programmers.stackex­change.com/questions/95836/is-assembly-still-relevant
  28. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/on­lamp/2004/05/06/writegreat­code.html
  29. Assembly language today
    http://beust.com/weblog/2004/06/23/as­sembly-language-today/
  30. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubri­ky/assembler/vyznam-assembleru-dnes-155960cz
  31. Assembler pod Linuxem
    http://phoenix.inf.upol.cz/li­nux/prog/asm.html
  32. AT&T Syntax versus Intel Syntax
    https://www.sourceware.or­g/binutils/docs-2.12/as.info/i386-Syntax.html
  33. Linux Assembly website
    http://asm.sourceforge.net/
ikonka

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.