Obsah
1. Zápis funkcí obsahujících instrukce Thumb a Thumb-2 v MicroPythonu
2. Mikroprocesory a mikrořadiče ARM
4. Podrobnější pohled na 32bitová jádra Cortex-M
5. Mikrořadiče Cortex-M0 a Cortex-M0+
6. Instrukční sada mikrořadičů Cortex-M0+
7. Od instrukční sady A32 k instrukční sadě Thumb-2
8. Malá odbočka: proč vznikla instrukční sada Thumb-2?
9. Instrukce rozpoznávané mikrořadiči Cortex-M0, M0+, M1 a M23
10. Aritmetické a logické instrukce
11. Skoky a vyvolání SW přerušení
12. Instrukce typu LOAD a STORE
14. Podpora instrukcí Thumb a Thumb-2 v MicroPythonu
15. Návratová hodnota z funkce se strojovými instrukcemi
16. Problematika načtení velkých konstant
17. Předání argumentu do funkce se strojovými instrukcemi
18. Realizace součtu argumentů
19. Repositář s demonstračními příklady
1. Zápis funkcí obsahujících instrukce Thumb a Thumb-2 v MicroPythonu
Jak již bylo napsáno v perexu článku, setkáme se na stránkách Roota tento týden již podruhé s projektem MicroPython. Zatímco v předchozím článku jsme si ukázali, jakým způsobem je možné MicroPython použít pro webový front end (jako náhradu za JavaScript), zaměříme se v dnešním článku na zcela opačnou stranu spektra programovacích technik a úrovní abstrakce. Řekneme si totiž, jakým způsobem MicroPython podporuje zápis funkcí obsahujících symbolicky zapsané strojové instrukce z instrukční sady Thumb a Thumb-2. Díky této technologii je možné zkombinovat vysokoúrovňový kód v Pythonu se strojovým kódem, a to bez nutnosti použití assembleru a linkeru (a vůbec znalosti předávání parametrů subrutinám atd.). A vzhledem k tomu, že MicroPython lze provozovat například na jednodeskových mikropočítačích s oblíbeným čipem RP2040 (Pico, Pico W, Nano RP2040 Connect atd.), se jedná podle mého názoru o velmi užitečnou pomůcku a taktéž o první krok pro seznámení se s instrukčními sadami ARMovských čipů.

Obrázek 1: Logo projektu MicroPython.
2. Mikroprocesory a mikrořadiče ARM
Instrukční sady Thumb a Thumb-2 jsou spojeny s mikroprocesory a především s mikrořadiči ARM. Tyto čipy za sebou mají velmi dlouhou dobu vývoje, takže není divu, že postupně vznikaly různé generace těchto čipů, které se odlišovaly jak svým výpočetním výkonem, tak i různými změnami v instrukční sadě, uspořádáním cache, počtem řezů (pipeline), použitými submoduly (hardwarová násobička, dělička, jednotka pro SIMD operace, FP koprocesor) atd. Čipy ARM je možné rozlišit podle architektury, přičemž základní dělení je naznačeno v následující tabulce:
| # | Architektura | Adresová/datová sběrnice | Jádro | Poznámka/profil (u Cortex) |
|---|---|---|---|---|
| 1 | ARMv1 | 26/32 bitů | ARM1 | první implementace, de facto technologické demo |
| 2 | ARMv2 | 26/32 bitů | ARM2, ARM250, ARM3 | přidána HW násobička a MMU |
| 3 | ARMv3 | 26/32 bitů | ARM6, ARM7 | |
| 4 | ARMv4 | 26/32 bitů | ARM8 | patří sem mj. i kdysi oblíbená řada čipů StrongARM |
| 5 | ARMv5 | 32 bitů | ARM7EJ, ARM9E, ARM10E | lze najít ve starších zařízeních |
| 6 | ARMv6 | 32 bitů | ARM11 | dodnes používaná jádra |
| 7 | ARMv6-M | 32 bitů | Cortex-M0, Cortex-M0+, Cortex-M1 | mikrořadiče (M v názvu) |
| 8 | ARMv7-M | 32 bitů | Cortex-M3 | mikrořadiče (M v názvu) |
| 9 | ARMv7E-M | 32 bitů | Cortex-M4, Cortex-M7 | mikrořadiče (M v názvu) |
| 10 | ARMv7-R | 32 bitů | Cortex-R4, Cortex-R5, Cortex-R7 | realtime aplikace |
| 11 | ARMv7-A | 32 bitů | Cortex-A5, Cortex-A7, Cortex-A8, Cortex-A9, Cortex-A12, Cortex-A15, Cortex-A17 | smartphony atd. |
| 12 | ARMv8-A | 32/64 bitů | Cortex-A35, A35, A57, A72 a A73 | smartphony atd. |
| 13 | ARMv8.2-A | 32/64 bitů | Cortex-A55, A75, A76 a A77 | smartphony, PC atd. |
| 14 | ARMv8.4-A | 32/64 bitů | ARM Neoverse V1 | produkty Apple |
| 15 | ARMv9.0 | 64 bitů | Cortex-A510, A710 atd. | |
| 16 | ARMv9.2-A | 64 bitů | Cortex-A520, A720 atd. |
Obrázek 2: Čipy StrongARM založené na architektuře ARMv4 byly v minulosti velmi oblíbené. Zajímavé je, že původně tyto čipy vyvinula společnost Digital Equipment Corporation (DEC) ve spolupráci s firmou ARM a později byla tato technologie prodána Intelu.
Poznámky k informacím uvedeným v předchozí tabulce:
- U čipů prvních dvou architektur ARMv1 a ARMv2 nebyly použity cache (ani paměťová ani datová), ovšem i samotné CPU byly relativně pomalé (hodinová frekvence 8MHz) v porovnání s rychlostmi přístupu k RAM, takže se tehdy nejednalo o úzké hrdlo systému.
- Můžeme zde vidět dělení na „klasická“ jádra a „Cortex“ jádra.
- V nových elektronických zařízeních se již s prvními čtyřmi generacemi čipů většinou nesetkáme. Na druhou stranu ARMv6 je stále používán.
- ARM11 je (poněkud zjednodušeně řečeno) poslední doposud masově používané „klasické“ jádro a současně i základ pro velké množství čipů používaných v různých mobilních zařízeních.
- Nové mikroprocesory a mikrořadiče ARM jsou děleny do třech skupin:Application, Realtime, Microcontroller (název první skupiny pravděpodobně vznikl ze snahy o její „napasování“ na zkratku ARM).
- Populární mikropočítač Raspberry ve svých prvních variantách používal čip s mikroarchitekturou ARM1176JZF-S, kde prefix ARM1176 značí třetí typ jádra ARM11.
- Právě ARM11 se některými svými vlastnostmi blíží čipům Cortex-M0 a Cortex-M0+, jimiž se dnes budeme zabývat (ARM11 je však určen pro výkonnější aplikace, opět si připomeňme Raspberry a podobná zařízení).
- Poslední dva řádky obsahují čistě 64bitové architektury.
Obrázek 3: Dnes již historický kousek: osobní mikropočítač vybavený mikroprocesorem s architekturou ARM.
3. Mikrořadiče Cortex-M
Již při letmém pohledu na druhou polovinu tabulky, kterou jsme si ukázali v předchozí kapitole, je patrné, že novější architektury ARM tvoří základ pro jádra třech navzájem odlišných typů čipů. Jedná se o řadu Cortex-M, kde většinou nalezneme mikroprocesorová jádra určená především pro použití v mikrořadičích (s různým výpočetním výkonem a taktéž různými energetickými nároky), dále pak o řadu Cortex-R určenou pro realtime aplikace (tato jádra jsou mnohdy vybavena instrukcemi typu SIMD) a konečně o řadu Cortex-A, kam patří jak relativně málo výkonné čipy použitelné například v set-top boxech, tak i čipy určené pro výkonné servery apod. (tato jádra jsou mnohdy vybavena i matematickým koprocesorem, podporou pro kooperaci většího množství jader, podporou pro adresování až jednoho terabajtu RAM atd.). Nejnovější Cortexy-A už dokonce opustily 32bitový režim a jedná se tak o čistě 64bitové architektury (těmi se však dnes nebudeme zabývat).
Pojďme se nyní zaměřit především na řadu Cortex-M, tedy na rodinu jader určených především pro použití v různě výkonných mikrořadičích. Takových jader existuje celá řada:
| # | Jádro | Architektura ARM | Architektura CPU/MCU | MPU |
|---|---|---|---|---|
| 1 | Cortex-M0 | ARMv6-M | Von Neumann | ne |
| 2 | Cortex-M0+ | ARMv6-M | Von Neumann | volitelná |
| 3 | Cortex-M1 | ARMv6-M | Von Neumann | ne |
| 4 | Cortex-M3 | ARMv7-M | Harvardská | volitelná |
| 5 | Cortex-M4 | ARMv7E-M | Harvardská | volitelná |
| 6 | Cortex-M7 | ARMv7E-M | Harvardská | volitelná |
| 7 | Cortex-M23 | ARMv8-M | Harvardská/Von Neumann | volitelná |
| 8 | Cortex-M33 | ARMv8-M | Harvardská | volitelná |
| 9 | Cortex-M35 | ARMv8-M | Harvardská | volitelná |
| 10 | Cortex-M52 | ARMv8.1-M | Harvardská | volitelná |
| 11 | Cortex-M55 | ARMv8.1-M | Harvardská | volitelná |
| 12 | Cortex-M85 | ARMv8.1-M | Harvardská | volitelná |
Vidíme, že do řady Cortex-M spadá několik odlišných typů jader, která jsou založena na architektuře ARMv6-M, ARMv7-M, ARMv7E-M, ARMv8-M a ARMv8.1-M. Důležité je si uvědomit, že každá z těchto architektur má odlišné vlastnosti a obecně i odlišnou instrukční sadu, z čehož také vyplývá, že například mikrořadiče postavené na jádru Cortex-M3 nebudou plně binárně kompatibilní s mikrořadiči s jádrem Cortex-M0 atd. To však v dnešní době nepředstavuje závažný problém, minimálně ne tak velký, jako je tomu (či ještě nedávno bylo) na platformě Wintel.
4. Podrobnější pohled na 32bitová jádra Cortex-M
Podívejme se nyní podrobněji na to, co mají jednotlivá jádra z řady Cortex-M společného a čím se od sebe naopak odlišují. Zaměříme se přitom na 32bitová jádra, s nimiž se můžeme setkat v mnoha jednodeskových mikropočítačích, ale samozřejmě i v průmyslovém nasazení. Pravděpodobně nejdůležitější společnou vlastností těchto jader je, že Cortex-M nepodporuje původní 32bitovou RISCovou instrukční sadu (v tomto článku označovanou A32, i když existují i další způsoby značení). Namísto toho je podporována instrukční sada Thumb a u některých jader i Thumb-2 (buď všechny instrukce nebo jen vybraná část instrukcí). Jednodušší jádra nemají hardwarovou děličku ani modul pro DSP operace. Všechny tyto vlastnosti jsou (doufejme že přehledně) vypsány v další tabulce:
| # | Jádro | Dělička | DSP | A32 | Thumb | Thumb-2 |
|---|---|---|---|---|---|---|
| 1 | Cortex-M0 | ne | ne | ne | kromě 3 instrukcí | jen částečně (6 instrukcí) |
| 2 | Cortex-M0+ | ne | ne | ne | kromě 3 instrukcí | jen částečně (6 instrukcí) |
| 3 | Cortex-M1 | ne | ne | ne | kromě 3 instrukcí | jen částečně (6 instrukcí) |
| 4 | Cortex-M3 | ano | ne | ne | kompletně | kompletně |
| 5 | Cortex-M4 | ano | ano | ne | kompletně | kompletně |
| 6 | Cortex-M7 | ano | ano | ne | kompletně | kompletně |
| 7 | Cortex-M23 | ano | ne | ne | kromě 1 instrukce | jen částečně (6 instrukcí) |
| 8 | Cortex-M33 | ano | výběr | ne | kompletně | kompletně |
| 9 | Cortex-M35 | ano | výběr | ne | kompletně | kompletně |
| 10 | Cortex-M52 | ano | ano | ne | kompletně | kompletně |
| 11 | Cortex-M55 | ano | ano | ne | kompletně | kompletně |
| 12 | Cortex-M85 | ano | ano | ne | kompletně | kompletně |
Jak je tomu se vzájemnou binární kompatibilitou? Program přeložený pro jádra Cortex-M0 či Cortex-M0+ bude možné provozovat i na vyšších jádrech bez nutnosti jeho modifikace (za předpokladu volby stejného pořadí bajtů – little či big endian). Podobně strojový kód určený pro Cortex-M3 lze spustit na čipech s jádrem Cortex-M4 či Cortex-M7. Žádný z těchto čipů ovšem pochopitelně nedokáže pracovat s kódem používajícím instrukce A32 či s kódem, který používá například rozšíření Neon (SIMD), Jazelle (Java bajtkód), ThumbEE (dtto) atd.
Obrázek 4: Čipy s jádrem ARM nalezneme i v některých herních konzolích.
5. Mikrořadiče Cortex-M0 a Cortex-M0+
Jádro Cortex-M0 tvoří základ pro čipy, u kterých je žádoucí dosáhnout co nejnižší výrobní ceny, malých rozměrů a v neposlední řadě i malého příkonu (s tím ovšem pochopitelně souvisí i příslušně nízký výpočetní výkon). Tato jádra by měla postupně nahrazovat některé aplikace, v nichž se nyní používají osmibitové mikrořadiče (PIC, starý „dobrý“ 8051, Motorola 68HC11 atd.), což se taktéž postupně děje.
Malé plochy čipu se skutečně podařilo dosáhnout, protože nejmenší vyráběný integrovaný obvod s jádrem Cortex-M0 má plochu přibližně 1,6×2 mm. I spotřeba je velmi nízká, protože dosahuje 12.5µW na každý MHz při použití napětí 1,2V a 64µW na každý MHz při napětí 1,8V (teoreticky roste spotřeba s hodinovou frekvencí lineárně a s napětím kvadraticky, mimochodem hodinová frekvence těchto jader dosahuje podle provedení až několika desítek až stovek MHz, typicky 48 MHz, 80 MHz či 120 MHz). Toto jádro je naprogramované ve Verilogu a po technologické stránce je zajímavé tím, že se interně používá pipeline se třemi řezy, na rozdíl od jádra Cortex-M0+ popsaného v navazujícím textu.
Obrázek 5: Čipy XMC4000 založené na jádru Cortex-M4.
Autor původní fotky: Davewave88.
Vzhledem k tomu, že jádra Cortex-M0 jsou používána v aplikacích, kde se požaduje velmi nízký příkon CPU (například různá zařízení s baterií či monočlánkem), podporují tyto čipy hned několik režimů „uspání“ (sleep mode). V základním režimu sleep se jednoduše sníží hodinová frekvence až na 0 Hz, ovšem kromě toho lze použít i režim nazvaný deep sleep, při jehož aktivaci se od napájení odpojí i flash paměť atd. Při práci s různými režimy CPU slouží i instrukce WFI (Wait For Interrupt) a WFE a (Wait for Event). První instrukce čeká na vznik výjimky, přerušení či signálu od debuggeru, druhá instrukce pak na nemaskovanou výjimku, událost přijatou od některého koprocesoru či opět na signál od debuggeru.
O tom, že jádro Cortex-M0 je skutečně navrženo takovým způsobem, aby mohlo spotřebou energie a částečně i cenou soutěžit s jednoduššími a levnějšími mikrořadiči, svědčí i způsob vyřešení násobičky. V instrukční sadě Thumb nalezneme mj. 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). Při implementaci mikroprocesoru je možné zvolit, jakým typem násobičky se tato instrukce bude provádět. Pokud 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.
Obrázek 6: Mezi další čipy založené na jádru Cortex-M0 patří integrované obvody STM32 F0.
Na mikrořadiče Cortex-M0+ se můžeme dívat jako na vylepšení původních jader Cortex-M0. Interně se ovšem jedná o odlišně navržená jádra, protože Cortex-M0+ obsahuje pipeline pouze se dvěma řezy a nikoli s řezy třemi (vlastně se tak vracíme na samotný začátek platformy ARM). Co je však pro případné uživatele důležitější – i díky zmenšenému počtu řezů pipeline se podařilo dále snížit spotřebu a přitom zachovat obousměrnou kompatibilitu s původními jádry Cortex-M0. Spotřeba klesá až na hodnoty 9.8µW na MHz, takže tato jádra mohou nalézt uplatnění i v chytrých hodinkách apod. Opět je možné si zvolit způsob implementace násobičky (rychlá versus energeticky nenáročná) a nově i to, zda se má použít jednotka MPU. Čipů s tímto jádrem existuje celá řada, například je vyrábí firmy Atmel, Freescale, STMicroelectronics, NXP Semiconductors apod. Na tomto jádru je založen i čip RP2040, na němž budeme testovat dnešní demonstrační příklady.
6. Instrukční sada mikrořadičů Cortex-M0+
Mikroprocesory ARM byly zpočátku vybaveny jedinou instrukční sadou, v níž se nacházely instrukce o konstantní šířce 32 bitů. Postupně však byly navrženy a implementovány i další instrukční sady, které bylo u mnoha čipů možné použít společně s původní 32bitovou instrukční sadou. Jedná se o rozšíření Thumb následované vylepšenou variantou nazvanou Thumb-2. Dále nesmíme zapomenout na 64bitovou architekturu ARM, která se nazývá AArch64 a která používá jak novou množinu pracovních registrů, tak i v mnoha ohledech změněnou instrukční sadu:
- Původní 32bitová instrukční sada ARM
- Rozšíření Thumb
- Rozšíření Thumb-2
- Nová instrukční sada používaná u mikroprocesorů s architekturou AArch64
Z předchozího textu již víme, že jádra Cortex-M0 nedokážou správně dekódovat instrukce z původní 32bitové instrukční sady ARM (dnes se označuje jako A32). Namísto toho je podporována instrukční sada Thumb, z níž byly odstraněny jen tři instrukce: CBZ, CBNZ a IT (zejména tato poslední instrukce by však byla velmi užitečná, nicméně její zařazení by pravděpodobně komplikovalo návrh čipu, popř. jeho plochu a příkon).
Instrukce Cortex-M0(+)Obrázek 7: Všechny instrukce jader s architekturou Cortex-M0(+). Instrukce Thumb-2 jsou vykresleny s dvojnásobnou šířkou, kvůli svému kódování.
Zajímavé je, že tato jádra navíc rozpoznají i šest instrukcí z instrukční sady Thumb-2. Konkrétně se jedná o instrukce BL, DMB, DSB, ISB, MRS a MSR (ještě se k nim později vrátíme). Důvod, proč se tvůrci přiklonili k instrukční sadě Thumb, je pochopitelný – je tak možné dosáhnout větší hustoty kódu, což je zejména na mikrořadičích, kde jsou kapacity RAM a (Flash)ROM relativně malé, poměrně kritická vlastnost. Díky relativně velké ortogonalitě instrukční sady Thumb se navíc může dosáhnout větší hustoty kódu, než je tomu u některých osmibitových mikrořadičů! (typické je to zejména v porovnání s řadou 8051, která je kupodivu stále používána).
7. Od instrukční sady A32 k instrukční sadě Thumb-2
Původní instrukční sada procesorů ARM používá převážně „RISCové“ instrukce o konstantní šířce 32 bitů. Vzhledem k tomu, že šířka externí datové sběrnice byla rovna taktéž 32 bitům a instrukce musely být zarovnané na celá slova, znamenalo to, že se celá instrukce vždy načetla jedinou operací, což je velký rozdíl oproti typickým mikroprocesorům s architekturou CISC, u nichž je délka instrukcí proměnná a mnohdy může přesahovat hranici slov (což ve svém důsledku vedlo k nutnosti vytvoření takzvané „fronty instrukcí“, jejíž vlastnosti se mj. využívaly či spíše zneužívaly při tvorbě virů, které nešlo odhalit debuggerem).
Obrázek 8: Kódování instrukcí u „klasické“ 32bitové RISCové architektury.
Tuto instrukční sadu lze na mnoha procesorech ARM používat dodnes (s výjimkou některých čipů Cortex-M – ty nás ovšem zajímají nejvíce, a taktéž s výjimkou čistě 64bitových čipů) a její největší předností je možnost uvést u každé instrukce podmínku, při jejímž splnění se má instrukce provést. Díky tomuto řešení je možné eliminovat velké množství skoků, jejichž provedení je samozřejmě problematické, a to nejenom na architektuře RISC, ale i na procesorech CISC.
Obrázek 9: Kódování instrukcí v sadě Thumb.
I přes mnohé přednosti 32bitové instrukční sady ARM se však v některých případech projevují její nevýhody. Jedná se především o to, že použití 32bitových instrukcí může zmenšovat „hustotu“ kódu, což se projevuje větší délkou binárních souborů, větší pravděpodobností výpadku stránky z vyrovnávací paměti a taktéž (obecně) vyšší cenou za zařízení v případě, že je mikroprocesor použit ve funkci mikrořadiče (zde se již může projevit cena za každý ušetřený kilobajt paměti ROM/EPROM/Flash s programovým kódem). Navíc nutnost vyhodnocení podmínky znamená zpoždění v pipeline – což zpočátku byl relativně malý problém, dnes se však jedná o závažný nedostatek.
Z tohoto důvodu jsou mikrořadiče Cortex-M vybaveny instrukční sadou pojmenovanou Thumb. Jedná se o instrukční sadu obsahující podmnožinu instrukcí vybranou na základě analýzy strojových programů generovaných překladači jazyků C a C++. Dále se v této instrukční sadě neobjevují bity určené pro podmíněné provádění instrukcí, což znamená, že je nutné se vrátit k použití klasických podmíněných skoků. Na druhou stranu se však délka všech instrukcí zkrátila na šestnáct bitů, což dovoluje dosažení větší „hustoty“ kódu.
Zavedení nové instrukční sady není v tomto případě tak složité, jak by se možná mohlo zdát, protože převod instrukce Thumb na původní instrukci ARM je záležitostí jednoduchého dekodéru, který může být například umístěn v interní paměti ROM či přímo „zadrátován“ na čipu. Jedná se tedy o řádově jednodušší technologii, než jaká je použita na procesorech x86 pro překlad CISC instrukcí do sekvence interních RISC-like instrukcí (zde se může jediná instrukce CISC rozložit na sekvenci několika instrukcí RISC, popř. dokonce na celý podprogram).
A ta nejlepší zpráva na konec – s využitím speciální instrukce skoku je možné se na některých čipech (ovšem nikoli na Cortex-M) přepínat mezi instrukční sadou Thumb a původní instrukční sadou ARM, a to dokonce i v rámci jednotlivých funkcí. Programátor či překladač tedy může využívat předností obou instrukčních sad. Poznámka: tato možnost u Cortex-M chybí a dále se jí nebudeme zabývat).
Třetí instrukční sada, kterou nalezneme u mnoha mikroprocesorů ARM, se jmenuje Thumb-2. Tato instrukční sada se v sobě snaží sdružit jak přednosti původní 32bitové „RISCové“ instrukční sady ARM, tak i přednosti šestnáctibitové instrukční sady Thumb. Sada Thumb-2 díky tomu na jedné straně dosahuje jak velké hustoty kódu (code density), tak i velkého výpočetního výkonu. Zmíněný výpočetní výkon dosahovaný v reálných aplikacích totiž byl u sady Thumb v některých případech menší, protože mnohé operace musely být provedeny pomocí většího množství instrukcí – instrukce Thumb totiž vždy vykonávaly jen jednu operaci, na rozdíl od instrukcí ARM, které obsahovaly jak podmínkové bity, tak i v mnoha případech „podoperaci“ pro bitový posun či rotaci jednoho z operandů vstupujícího do aritmeticko-logické jednotky.
Obrázek 10: Pracovní a řídicí registry: rozdíl mezi RISCovým režimem ARM a režimem Thumb. Ve skutečnosti není tento obrázek zcela korektní, protože i v režimu Thumb lze využít všechny pracovní registry, ovšem přístup k horním osmi registrům je více omezen (jen na některé instrukce).
8. Malá odbočka: proč vznikla instrukční sada Thumb-2?
Sami konstruktéři mikroprocesorů ARM se vyjádřili k tomu, z jakého důvodu vlastně instrukční sada Thumb-2 vznikla. Při jejím návrhu měli na mysli čtyři parametry, které se navzájem ovlivňují a vylepšení jednoho z parametrů většinou v důsledku vede ke zhoršení zbývajících třech parametrů (což není v IT nic nového – zde skutečně není žádné jídlo zdarma). Jedná se o následující parametry:
- Cenu vlastního mikroprocesoru nebo mikrořadiče, která je kromě jiných okolností ovlivněna i jeho složitostí (existence prediktorů skoků, spekulativního provádění instrukcí, …), počtem aritmeticko-logických jednotek (obecně zda jde o skalární či superskalární procesor), velikostí potřebných vyrovnávacích pamětí atd.
- Dosahovaný výpočetní výkon v reálných aplikacích. V praxi to znamená, že hodnota udávaná v jednotkách MIPS nebo MFLOPS nemusí být vždy směrodatná: je to právě příklad jednodušších instrukcí Thumb v porovnání s obecně výkonnějšími instrukcemi ARM (tedy lze mít mikroprocesor provádějící obrovské množství instrukcí za časovou jednotku, které ale nebudou provádět příliš mnoho reálných operací).
- Nutný energetický příkon procesoru (závisí na technologii výroby, napájecím napětí, hodinové frekvenci, počtu ALU, velikosti vyrovnávacích paměti atd.).
- Cenu za vývoj a optimalizaci aplikací (tu ovlivňuje složitost instrukční sady, nedostatky v instrukční sadě: například nutnost provádění neefektivních skoků, složitost při načítání konstant do pracovních registrů atd.).
Důvodů pro vznik nové instrukční sady tedy bylo více než dost, takže se nyní podívejme na to, co se jejím tvůrcům podařilo splnit a co naopak nikoli. V následujícím textu budou uvedeny výsledky měření prezentované samotnou společností ARM. V první tabulce je porovnána „hustota“ binárního strojového kódu měřená jeho délkou. Ve všech případech se jednalo o stejný algoritmus, který byl poprvé implementován s využitím instrukční sady ARM, podruhé s pomocí sady Thumb a potřetí byla použita instrukční sada Thumb-2. Za základ je přitom brána délka původního kódu používajícího instrukce ARM (tento kód odpovídá sto procentům, čím menší číslo, tím menší je i výsledný binární program):
| Instrukční sada | Délka kódu |
|---|---|
| ARM | 100% |
| Thumb | 70% |
| Thumb-2 | 74% |
Ve druhé tabulce je uveden relativní výpočetní výkon přeloženého binárního programu, přičemž 100% odpovídá nejrychlejší implementaci a 75% implementaci nejpomalejší:
| Instrukční sada | Relativní výpočetní výkon |
|---|---|
| ARM | 100% |
| Thumb | 75% |
| Thumb-2 | 98% |
Z výsledků, které jsou prezentovány v předešlých dvou tabulkách tedy vyplývá, že pro testovanou aplikaci se díky použití instrukční sady Thumb-2 podařilo zmenšit velikost kódu na tři čtvrtiny původní velikosti a přitom výpočetní výkon poklesl pouze o zhruba 2% (zde se samozřejmě projevila i nižší pravděpodobnost výpadku instrukční cache, která ovšem byla nižší i u implementace využívající instrukce Thumb).
9. Instrukce rozpoznávané mikrořadiči Cortex-M0, M0+, M1 a M23
Mikrořadiče řady Cortex-M0, M0+, M1 a M23 rozpoznávají stejné instrukce, mají tedy shodnou instrukční sadu. Jedná se zejména o instrukce Thumb, z nichž ovšem byla odstraněna trojice instrukcí CBZ, CBNZ a IT. Tyto tři instrukce jsou určený pro řízení běhu programu (podmíněný skok a podmíněné provedení až čtyř instrukcí). Naopak bylo přidáno šest instrukcí ze sady Thumb-2. Naprostá většina rozpoznávaných instrukcí je tedy uložena v 16bitových slovech.
Ovšem menší šířka instrukcí znamená větší i menší omezení instrukční sady, která tak přestala být elegantní a je ryze pragmatická (což většině programátorů vůbec nevadí, protože pracují v jazycích vyšší úrovně). Zcela zmizely podmínkové kódy, které zůstaly zachovány jen u instrukce podmíněného skoku. Taktéž se možnost použití barrel shifteru omezila jen na určitou skupinu instrukcí. Ovšem asi největší změnou bylo to, že se sada pracovních registrů R0-R15 rozdělila na spodní polovinu R0-R7 (Lo registers) a horní polovinu R8-R15 (Hi registers), přičemž většina instrukcí dokáže pracovat pouze s prvními osmi registry, zatímco některé registry z horní skupiny mají speciální význam (čítač instrukcí, ukazatel na vrchol zásobníku atd.).
V instrukční sadě Thumb existuje celkem devatenáct formátů instrukcí, tj. 19 způsobů, jak se do šestnáctibitového instrukčního slova zaznamená operační kód instrukce, indexy použitých registrů, příznaky podmínek, celočíselné konstanty atd. Některé z těchto formátů budou popsány v navazujících kapitolách.
10. Aritmetické a logické instrukce
Výše zmíněné rozdělení pracovních registrů na dvě poloviny o různých významech je patrné i na těch instrukcích, pomocí nichž se realizují základní aritmetické a logické operace. Tyto operace jsou zakódovány několika způsoby. Nejdříve se podívejme na instrukce pracující s dvojicí registrů: jednoho registru cílového a registru zdrojového, přičemž u většiny registrů je cílový registr současně použit i jako první vstupní operand.
Šestnáctibitové instrukční slovo je u těchto instrukcí rozděleno na čtyři části: šestibitový prefix mající binární hodnotu 010000, čtyřbitový kód prováděné aritmetické či logické operace, tříbitový index druhého zdrojového registru Rs a taktéž tříbitový index prvního zdrojového registru Rd, který je současně i registrem cílovým, tj. registrem, do nějž se uloží výsledek operace (výjimku tvoří instrukce komparace a testu, u nichž se výsledek porovnání nikam neukládá). Zde je ostatně patrné i další omezení šestnáctibitové instrukční sady Thumb, kdy do úzkého instrukčního slova není možné vložit indexy tří registrů, ale pouze registrů dvou, což potenciálně zvyšuje četnost přesunů dat mezi registry a navíc se i snižuje účinnost některých optimalizačních technik prováděných buď přímo programátory v assembleru nebo překladači:
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 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Tímto formátem instrukcí je podporováno celkem šestnáct aritmetických a logických operací (+ operací bitového posunu a rotace) určených bity 6 až 9 instrukčního slova. Tyto operace jsou vypsány v tabulce pod tímto odstavcem. Ve třetím sloupci tabulky je pro ilustraci uvedena ekvivalentní instrukce dostupná v režimu ARM):
| Kód | Operace | Ekvivalent ARM | Význam |
|---|---|---|---|
| 0000 | AND Rd, Rs | ANDS Rd, Rd, Rs | Rd:= Rd AND Rs |
| 0001 | EOR Rd, Rs | EORS Rd, Rd, Rs | Rd:= Rd EOR Rs (EOR=XOR) |
| 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) |
| 0101 | ADC Rd, Rs | ADCS Rd, Rd, Rs | Rd := Rd + Rs + C-bit |
| 0110 | SBC Rd, Rs | SBCS Rd, Rd, Rs | Rd := Rd – Rs – NOT C-bit |
| 0111 | ROR Rd, Rs | MOVS Rd, Rd, ROR Rs | Rd := Rd ROR Rs (rotace) |
| 1000 | TST Rd, Rs | TST Rd, Rs | Nastavení příznaků podle operace Rd AND Rs |
| 1001 | NEG Rd, Rs | RSBS Rd, Rs, #0 | Rd = -Rs |
| 1010 | CMP Rd, Rs | CMP Rd, Rs | Nastavení příznaků podle operace Rd – Rs |
| 1011 | CMN Rd, Rs | CMN Rd, Rs | Nastavení příznaků podle operace Rd + Rs |
| 1100 | ORR Rd, Rs | ORRS Rd, Rd, Rs | Rd := Rd OR Rs |
| 1101 | MUL Rd, Rs | MULS Rd, Rs, Rd | Rd := Rs * Rd |
| 1110 | BIC Rd, Rs | BICS Rd, Rd, Rs | Rd := Rd AND NOT Rs |
| 1111 | MVN Rd, Rs | MVNS Rd, Rs | Rd := NOT Rs |
Speciálně u instrukcí pro součet a rozdíl však existují ještě další možnosti zakódování, které kombinují trojici pracovních registrů R0 až R7. Zde se pomocí bitu číslo 9 rozlišuje jen mezi operací ADD a SUB:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 1 | 1 | 0 | A | Rm | Rs | Rd | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Varianta s krátkou konstantou 0 až 7 zakódovanou přímo v instrukčním slovu vypadá takto. Bit číslo 9 opět slouží pro rozpoznání operace ADD nebo SUB:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 1 | 1 | 1 | A | konstanta | Rs | Rd | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Existuje taktéž varianta s delší osmibitovou konstantou a jediným registrem. Tato varianta je používána jen instrukcemi ADD, SUB, CMP a MOV:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | 0 | 1 |operace| Rd/Rn | konstanta | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Aritmetické a bitové posuny 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 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
11. Skoky a vyvolání SW přerušení
Další důležitou skupinou instrukcí jsou skoky, resp. rozvětvení (branch). Skok bez podmínky je realizován instrukcí B, která v instrukčním slovu používá 11bitový offset (rozsah 2kB):
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 1 | 1 | 0 | 0 | 11bitový offset | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
I základní varianta skoku do subrutiny (branch and link) používá 11bitový offset, ovšem prvních pět bitů je odlišných (pokud je H==1, je použit skok odvozený od registru LR, tato instrukce je provedena ve dvou krocích, s H==0 a H==1):
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 1 | 1 | 1 | H | 11bitový offset | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Pro skoky s podmínku je rezervován jen osmibitový offset, protože celkem čtyři bity zabírají kódy podmínky:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 1 | 0 | 1 | podmínka | 8bitový offset | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
A konečně poslední de facto skoková instrukce slouží k vyvolání SW přerušení:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 8bitová konstanta | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
12. Instrukce typu LOAD a STORE
Poslední velmi důležitou skupinou instrukcí, o nichž se dnes zmíníme, jsou instrukce typu LOAD a STORE, tedy instrukce sloužící pro načtení hodnoty do pracovního registru či naopak pro uložení hodnoty z pracovního registru do operační paměti.
Poměrně často se setkáme s nutností načtení „velké“ konstanty (32 bitů), která je uložena přímo v programovém kódu. Taková konstanta může být načtena z adresy PC+offset. Příslušná instrukce má tento formát:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | 1 | 0 | 0 | 1 | Rd | osmibitový offset | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Osmibitový offset znamená, že konstanta bude muset být uložena blízko samotné instrukce, což však většinou nebývá problém. Taktéž z toho, že pro registr jsou rezervovány tři bity plyne, že lze pracovat jen se spodními registry.
Pro načtení či uložení hodnoty do oblasti paměti alokované pro zásobník (zásobníkový rámec) slouží instrukce, v níž jsou adresy vypočteny jako SP+offset. Podporovány jsou jak instrukce typu LOAD, tak i instrukce typu STORE. Pro rozpoznání, o kterou instrukci se jedná, slouží bit číslo 11:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 | 0 | 1 |L/S| Rd | osmibitový offset | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Instrukce LOAD a STORE taktéž mohou využít jeden z pracovních registrů, který bude obsahovat adresu, z níž či na níž bude proveden zápis. K tomuto registru lze přičíst offset, takže první z registrů obsahuje bázovou adresu a druhý posun. Opět se používá bit číslo 11 pro rozpoznání, o jakou operaci se jedná:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | 1 | 0 | 1 |L/S| B | 0 | Ro | Rb | Rd | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
V posledním formátu, který si ukážeme, se sice stále používá bázový registr, ovšem offset je specifikován pěti bity přímo v instrukci:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | 1 | 1 | B |L/S| pětibitový offset | Rb | Rd | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | 1 | 0 | 0 |L/S| pětibitový offset | Rb | Rd | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
První formát je použit pro práci se slovy a bajty, druhý pro práci s polovičními (16bitovými) slovy).
13. Šestice instrukcí Thumb-2
Již v předchozích kapitolách jsme si napsali, že mikrořadiče Cortex-M0, M0+, M1 a M23 rozpoznávají i šestici instrukcí z instrukční sady Tbumb-2. Tyto instrukce slouží pro práci se stavovým registrem, dále zde nalezneme trojici bariér (pro instrukce, paměť a pipeline) a poslední instrukcí je „dlouhý“ skok do subrutiny (podprogramu), což je velmi praktické (na rozdíl od pouhého skoku nebo skoku s podmínkou, které musí pracovat jen lokálně). Jedná se o následující instrukce:
| Instrukce | Stručný popis instrukce |
|---|---|
| DMB | bariéra zajišťující postupné provádění operací |
| DSB | synchronizace při přístupu k operační paměti |
| ISB | vyprázdnění pipeline a logiky pro předpovídání skoků |
| MRS | přenos hodnoty z PSR do zvoleného pracovního registru (PSR – Program Status Register) |
| MSR | přenos hodnoty ze zvoleného pracovního registru do PSR nebo APSR |
| BL | „dlouhý“ branch and link, tedy zavolání subrutiny v rozsahu &plusm;16MB |
14. Podpora instrukcí Thumb a Thumb-2 v MicroPythonu
Nyní se konečně dostáváme k MicroPythonu. Ten podporuje zápis vybraných instrukcí tak, jakoby se jednalo o volání funkcí Pythonu. Navíc umožňuje i specifikaci návěští (label), která většinou tvoří cíle skoků, i zápis dat, které se uloží společně s vygenerovaným kódem. Ovšem strojové instrukce není možné míchat s Pythonním kódem zcela libovolně. Funkci, ve které se nachází jen instrukce (a žádný další kód), je nutné označit dekorátorem @micropython.asm_thumb:
@micropython.asm_thumb
def funkce_s_instrukcemi():
...
...
...
Dále je umožněno do takové funkce předávat parametry (maximálně čtyři a jen celočíselné) a vracet nějakou návratovou hodnotu. Všechny podporované možnosti si ukážeme dále.
15. Návratová hodnota z funkce se strojovými instrukcemi
Návratová hodnota funkce s dekorátorem @micropython.asm_thumb musí být uložena do pracovního registru R0. Nepoužívá se tedy žádný příkaz return, pouze se při návratu z funkce získá 32bitová hodnota z registru R0 a ta se považuje za jedinou návratovou hodnotu (tedy zapomeňme na řetězce, slovníky atd.).
Pokusme se nyní z funkce vrátit konstantu 42. Jedná se o relativně malou konstantu, která se vejde do osmi bitů, takže můžeme použít instrukci mov. Povšimněte si, jak se zápis instrukce provádí – jakoby se jednalo o volání funkce a registr R0 je předán tak, jakoby se jednalo o proměnnou:
@micropython.asm_thumb
def return_constant():
mov(r0, 42)
Vše si lze ověřit při připojení k mikropočítači s MicroPythonem a čipem Cortex-M0, M1 či M0+:
... >>> return_constant() 42
16. Problematika načtení velkých konstant
I u takto jednoduchých příkladů velmi rychle narazíme na různá omezení instrukční sady Thumb. Například není možné přímo načíst konstantu větší než 255:
@micropython.asm_thumb
def return_big_constant():
mov(r0, 1000)
Interpret nahlásí chybu, že takto velká konstanta nelze použít (ale to již víme – taková instrukce neexistuje):
SyntaxError: 'mov' integer 0x3e8 doesn't fit in mask 0xff
Na čipech s plnou podporou Thumb-2 by bylo možné použít instrukci movw:
@micropython.asm_thumb
def return_big_constant():
movw(r0, 1000)
Ovšem opět platí, že u čipů s intsrukční sadou Thumb to není možné a interpret je dostatečně chytrý na to, aby tuto skutečnost rozpoznal:
SyntaxError: unsupported Thumb instruction 'movw' with 2 arguments
Jedno z možných řešení tohoto problému spočívá v použití instrukce LDR. Ta dokáže načíst hodnotu z adresy vypočtené součtem obsahu registru s offsetem. V první verzi (vysvětlené více příště) je ukázán trik s použitím datové struktury array, která byla nedávno popsána v článku Balíček array ze standardní knihovny Pythonu (pořadí článků tedy není úplně náhodné). Pokud pole obsahuje jediný 32bitový prvek a jeho adresa se předá v registru R0 (viz další kapitolu), můžeme konstantu přečíst z adresy [R0+0]:
from array import array
a = array('I', [100000])
@micropython.asm_thumb
def return_big_constant(r0):
ldr(r0, [r0, 0])
return_big_constant(a)
17. Předání argumentu do funkce se strojovými instrukcemi
Víme již, že návratová hodnota funkce je uložena v registru R0. Jak je tomu ovšem s argumenty funkcí? Ty mohou být maximálně čtyři a jsou předávány v pracovních registrech R0 až R3. Přitom je nutné dodržet jména parametrů, tedy například první parametr bude označen R0 atd.
Příkladem korektně napsané funkce je funkce inc, které se předá celočíselná hodnota. Funkce k této hodnotě přičte jedničku a vrátí výsledek v registru R0:
@micropython.asm_thumb
def inc(r0):
mov(r1, 1)
add(r0, r0, r1)
Základní otestování funkčnosti přímo v REPLu MicroPythonu:
>>> inc(41) 42
Ovšem jména parametrů skutečně musí být jména prvních čtyř pracovních registrů. Tento požadavek si můžeme snadno ověřit:
@micropython.asm_thumb
def inc(x):
mov(r1, 1)
add(r0, r0, r1)
Interpret tuto funkci nedokáže přeložit:
SyntaxError: parameters must be registers in sequence r0 to r3
18. Realizace součtu argumentů
Předchozí demonstrační příklad si nyní nepatrně rozšíříme. Vytvoříme funkci, která sečte své dva argumenty a vrátí výsledek (v R0):
@micropython.asm_thumb
def add(r0, r1):
add(r0, r0, r1)
Ověření korektnosti výpočtů:
>>> add(1, 2) 3 >>> add(1000000, 1000000) 2000000
Víme již, že je možné předat maximálně čtyři argumenty. Ověřme si to na dvojici funkcí. První bude vracet součet čtyř argumentů, druhá se bude snažit o součet pětice argumentů:
@micropython.asm_thumb
def add_four(r0, r1, r2, r3):
add(r0, r0, r1)
add(r0, r0, r2)
add(r0, r0, r3)
>>> add_four(1, 2, 3, 4)
10
@micropython.asm_thumb
def add_four(r0, r1, r2, r3, r4):
add(r0, r0, r1)
add(r0, r0, r2)
add(r0, r0, r3)
add(r0, r0, r4)
SyntaxError: can only have up to 4 parameters to Thumb assembly
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:
20. Odkazy na Internetu
- MicroPython documentation
https://docs.micropython.org/en/latest/index.html - Inline assembler for Thumb2 architectures
https://docs.micropython.org/en/latest/reference/asm_thumb2_index.html - Inline assembler in MicroPython
https://docs.micropython.org/en/latest/pyboard/tutorial/assembler.html#pyboard-tutorial-assembler - MCU market turns to 32-bits and ARM
http://www.eetimes.com/document.asp?doc_id=1280803 - Cortex-M0 Processor (ARM Holdings)
http://www.arm.com/products/processors/cortex-m/cortex-m0.php - Cortex-M0+ Processor (ARM Holdings)
http://www.arm.com/products/processors/cortex-m/cortex-m0plus.php - ARM Processors in a Mixed Signal World
http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world - RISCové mikroprocesory s komprimovanými instrukčními sadami
https://www.root.cz/clanky/riscove-mikroprocesory-s-komprimovanymi-instrukcnimi-sadami/ - RISCové mikroprocesory s komprimovanými instrukčními sadami (2)
https://www.root.cz/clanky/riscove-mikroprocesory-s-komprimovanymi-instrukcnimi-sadami-2/ - ARM Architecture (Wikipedia)
https://en.wikipedia.org/wiki/ARM_architecture - Cortex-M0 (Wikipedia)
https://en.wikipedia.org/wiki/ARM_Cortex-M0 - Cortex-M0+ (Wikipedia)
https://en.wikipedia.org/wiki/ARM_Cortex-M#Cortex-M0.2B - Improving ARM Code Density and Performance
New Thumb Extensions to the ARM Architecture Richard Phelan - The ARM Processor Architecture
http://www.arm.com/products/processors/technologies/instruction-set-architectures.php - Thumb-2 instruction set
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344c/Beiiegaf.html - Introduction to ARM thumb
http://www.eetimes.com/discussion/other/4024632/Introduction-to-ARM-thumb - ARM, Thumb, and ThumbEE instruction sets
http://www.keil.com/support/man/docs/armasm/armasm_CEGBEIJB.htm - An Introduction to ARM Assembly Language
http://dev.emcelettronica.com/introduction-to-arm-assembly-language - Processors – ARM
http://www.arm.com/products/processors/index.php - The ARM Instruction Set
http://simplemachines.it/doc/arm_inst.pdf - The Thumb instruction set
http://apt.cs.manchester.ac.uk/ftp/pub/apt/peve/PEVE05/Slides/05_Thumb.pdf