Štúdium inštrukčnej sady procesora ARM. Štúdium inštrukčnej sady procesora ARM Príklady inštrukcií assembleru ARM

GBA ASM - Deň 2: Niekoľko informácií o assembleri ARM - Archív WASM.RU

ARM je spoločnosť, ktorá vyrába procesor GBA. Procesory ARM sú procesory RISC (na rozdiel od procesorov INTEL). RISC je skratka pre Reduced Instruction Set Computers (CISC - Complex...). Zatiaľ čo tieto procesory nemajú veľa inštrukcií (čo je dobrá vec), ARM inštrukcie (a možno aj iné RISC procesory, neviem) majú veľa rôznych účelov a kombinácií, čo robí RISC procesory tak výkonnými, aké sú .

Registre

O inych ARM procesoroch neviem, ale ten pouzivany v GBA ma 16 registrov a na rozdiel od procesorov Intel (a inych) sa daju bezpecne pouzivat vsetky registre (zvycajne). Registre sú nasledovné:

r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15

Wow! Veľa! Vysvetlím po poriadku.

ro: Rob si čo chceš!

r2 až r12: to isté

r13: Na niektorých systémoch ARM je r13 ukazovateľ zásobníka (SP na procesoroch INTEL). Nie som si istý, či r13 hrá rovnakú úlohu v GBA, môžem vás len upozorniť, aby ste s ním boli opatrní pri práci so zásobníkom.

r14: Obsahuje návratovú adresu pre volané procedúry. Ak ich nepoužívate, môžete si s nimi robiť, čo chcete.

r15: Programové počítadlo a príznaky, rovnaké ako IP (Instruction Pointer v Intel). Líši sa od registra IP spoločnosti Intel v tom, že k nemu máte voľný prístup rovnako ako k akémukoľvek inému registru, ale všimnite si, že jeho zmena spôsobí prenesenie kontroly do inej časti kódu a zmeny príznakov.

Poďme si trochu spočítať... 16 mínus 3 (zvyčajne) nám dáva 13 registrov. No nie je to super? Ukľudni sa.

Možno sa teraz pýtate, čo to vlastne registre sú. Registre sú špeciálne oblasti pamäte, ktoré sú súčasťou procesora a nemajú skutočnú adresu, ale sú známe len pod ich názvami. Registre sú 32-bitové. Takmer všetko v akomkoľvek jazyku symbolických inštancií používa registre, takže by ste ich mali poznať rovnako dobre ako vaši príbuzní.

Pokyny pre zostavu ARM

Najprv chcem začať tým, že podľa mňa je génius ten, kto prišiel s ARM assemblerom.

Po druhé, chcem vám predstaviť môjho dobrého priateľa CMP. Pozdravte ho a možno, len možno, sa stane aj vaším priateľom. CMP znamená CoMPare (porovnať). Táto inštrukcia môže porovnávať register a číslo, register a register, alebo register a pamäťové miesto. Potom, po porovnaní, CMP nastaví príznaky stavu, ktoré vám oznámia výsledok porovnania. Ako si možno spomínate, register r15 obsahuje príznaky. Hlásia výsledok porovnania. Inštrukcia CMP bola špeciálne navrhnutá tak, aby nastavila hodnotu týchto príznakov a nič iné.

Príznaky môžu obsahovať nasledujúce stavy:

    EQ Rovný / Rovný

    NE Nerovná sa

    VS overflow Set

    VC pretečenie Clear

    VYŠŠIE / Vyššie

    LS Nižšie alebo rovnaké / Nižšie alebo rovnaké

    PL PLus / Plus

    MI MÍNus / Mínus

    Sada CS na prenášanie

    CC Carry Clear

    GE Väčšie alebo rovnaké / Väčšie alebo rovné

    GT Väčšie než / Viac

    LE Menej než alebo rovné / Menej než alebo rovné

    LT Menej ako / Menej

    Z je nula / nula

    NZ nie je nula / nie je nula

Tieto stavy hrajú veľmi dôležitú úlohu v assembleri ARM.

POZNÁMKA: Príznaky ukladajú iba podmienky (Rovné, Menšie ako atď.). Už nie sú dôležité.

Prípony podmienok

Už ste videli pokyn B (Pobočka). Inštrukcia B robí to, čo sa nazýva bezpodmienečný skok (ako GoTo v Basic alebo JMP v zostave INTEL). Môže však mať príponu (jednu z vyššie uvedených), potom skontroluje, či sa s ňou zhoduje stav vlajok. Ak nie, potom sa inštrukcia skoku jednoducho nevykoná. Ak teda chcete skontrolovať, či sa register r0 rovná registru r4 a potom prejsť na štítok s názvom label34, musíte napísať nasledujúci kód:

    CMP ro, r4; Komentáre v assembleri sú za bodkočiarkou (;)

    značka BEQ34; B je skoková inštrukcia a EQ je význam prípony

    ; "Ak sa rovná"

POZNÁMKA: V programe Goldroad Assembler nemusia byť štítky sprevádzané znakom ( a na riadku by nemalo byť nič iné ako názov štítku.

POZNÁMKA II: CMP a BEQ nie je potrebné písať veľkými písmenami, je to len preto, aby vám to bolo jasnejšie.

Teraz viete, ako urobiť prechod v závislosti od stavu vlajok, ale čo neviete, je, že môžete urobiť čokoľvek v závislosti od stavu vlajok, stačí pridať požadovanú príponu k ľubovoľnej inštrukcii!

Tiež nemusíte používať CMP na nastavenie stavu príznakov. Ak chcete napríklad inštrukciu SUB (Odčítať) na nastavenie príznakov, pridajte k inštrukcii príponu „S“ (skratka znamená „Nastaviť príznaky“). To je užitočné, ak nechcete nastavovať stav príznakov pomocou ďalšej inštrukcie CMP, takže to môžete urobiť a skočiť, ak bol výsledok nula, takto:

    SUBS r0,r1,0x0FF ; Nastaví príznaky podľa výsledku vykonania

    ; inštrukcie

    ldrZ r0,=0x0FFFF ; Načíta register r0 s 0x0FFFF iba v prípade, že stav

    vlajky sa rovná nule.

Preskúmanie

Dnes sme sa dozvedeli (trochu viac) o registroch. Dozvedeli sme sa tiež o flexibilite inštrukcií ARM, ktoré je možné vykonať (alebo nevykonať) v závislosti od stavu príznakov. Dnes sme sa veľa naučili.

Zajtra využijeme znalosti ARM assembleru získané dnes, aby sme zobrazili obrázok na obrazovke GBA.

Niečo nemožné je len dovtedy, kým sa to nestane možným / Jean-Luc Picard, Capt. ,USS Enterprise/. Mike H, prekl. Aquila

Procesory CISC vykonávajú pomerne zložité operácie v jednej inštrukcii, vrátane aritmetických a logických operácií s obsahom pamäťových buniek. Inštrukcie procesora CISC môžu mať rôzne dĺžky.

Naproti tomu RISC má relatívne jednoduchý inštrukčný systém s jasným rozdelením podľa typu operácie:

  • práca s pamäťou (čítanie z pamäte do registrov alebo zápis z registrov do pamäte),
  • spracovanie údajov v registroch (aritmetické, logické, posuny údajov doľava/doprava alebo rotácia bitov v registri),
  • príkazy podmienených alebo nepodmienených prechodov na iné adresy.

Spravidla (ale nie vždy a iba ak sa kód programu dostane do vyrovnávacej pamäte radiča) sa vykoná jeden príkaz v rámci jedného cyklu procesora. Dĺžka inštrukcie procesora ARM je pevná – 4 bajty (jedno počítačové slovo). V skutočnosti sa moderný procesor ARM môže prepnúť do iných prevádzkových režimov, napríklad do režimu THUMB, keď dĺžka inštrukcie dosiahne 2 bajty. To vám umožní urobiť kód kompaktnejším. Tento režim však v tomto článku nepokrývame, pretože nie je podporovaný v procesore Amber ARM v2a. Z rovnakého dôvodu nebudeme brať do úvahy režimy ako Jazelle (optimalizované na vykonávanie Java kódu) a nebudeme brať do úvahy príkazy NEON – príkazy na operácie s viacerými dátami. Koniec koncov, študujeme čistý inštrukčný systém ARM.

Registre procesora ARM.

Procesor ARM má niekoľko sád registrov, z ktorých má programátor v súčasnosti k dispozícii niekoľko režimov činnosti procesora, je zvolená príslušná banka registrov. Tieto prevádzkové režimy:

  • aplikačný režim (USR, užívateľský režim),
  • režim supervízora alebo režim operačného systému (SVC, režim supervízora),
  • režim spracovania prerušenia (IRQ, režim prerušenia) a
  • režim spracovania „naliehavé prerušenie“ (FIRQ, režim rýchleho prerušenia).

To znamená, že keď napríklad dôjde k prerušeniu, procesor sám prejde na adresu programu obsluhy prerušenia a automaticky „prepne“ banky registrov.

Procesory ARM starších verzií majú okrem vyššie uvedených prevádzkových režimov aj ďalšie režimy:

  • Prerušiť (používa sa na spracovanie výnimiek prístupu do pamäte),
  • Nedefinované (používa sa na implementáciu koprocesora v softvéri) a
  • režim privilegovaných úloh operačného systému Systém.

Procesor Amber ARM v2a tieto ďalšie tri režimy nemá.

Pre Amber ARM v2a môže byť množina registrov reprezentovaná nasledovne:

Registre r0-r7 sú rovnaké pre všetky režimy.
Registre r8-r12 sú spoločné len pre režimy USR, SVC, IRQ.
Register r13 je ukazovateľ zásobníka. Je svoj vo všetkých režimoch.
Register r14 - register návratu z podprogramu je tiež odlišný vo všetkých režimoch.
Register r15 je ukazovateľ na spustiteľné inštrukcie. Je to spoločné pre všetky režimy.

Je vidieť, že režim FIRQ je najviac izolovaný, má najviac vlastných registrov. To sa robí tak, že niektoré veľmi kritické prerušenia môžu byť spracované bez uloženia registrov v zásobníku, bez straty času.

Osobitná pozornosť by sa mala venovať registru r15, tiež známemu ako pc (Program Counter) - ukazovateľ na spustiteľné príkazy. S jeho obsahom môžete vykonávať rôzne aritmetické a logické operácie, čím sa vykonávanie programu presunie na iné adresy. Konkrétne pre procesor ARM v2a implementovaný v systéme Amber však existujú určité jemnosti v interpretácii bitov tohto registra.

Faktom je, že v tomto procesore sú v registri r15 (pc) okrem skutočného ukazovateľa na spustiteľné príkazy obsiahnuté nasledujúce informácie:

Bity 31:28 - príznaky pre výsledok aritmetickej alebo logickej operácie
Bity 27 - maska ​​IRQ prerušenia, pri nastavení bitu sú prerušenia zakázané.
Bity 26 - maska ​​prerušenia FIRQ, rýchle prerušenia sú vypnuté, keď je bit nastavený.
Bity 25:2 - skutočný ukazovateľ na programové inštrukcie zaberá iba 26 bitov.
Bity 1:0 - aktuálny prevádzkový režim procesora.
3 - Vedúci
2 - Prerušiť
1 - Rýchle prerušenie
0 - Používateľ

V starších procesoroch ARM sú všetky príznaky a servisné bity umiestnené v samostatných registroch Register aktuálneho stavu programu(cpsr) a Register stavu uloženého programu (spsr), pre prístup ku ktorým existujú samostatné špeciálne príkazy. Toto sa robí s cieľom rozšíriť dostupný adresný priestor pre programy.

Jednou z ťažkostí pri ovládaní ARM assembleru sú alternatívne názvy niektorých registrov. Takže, ako je uvedené vyššie, r15 je rovnaký počítač. Existuje aj r13 - to je ten istý sp (Stack Pointer), r14 je lr (Link Register) - register návratových adries z procedúry. Okrem toho, r12 je rovnaký ip (Intra-Procedure -call scratch register), ktorý používajú kompilátory C špeciálnym spôsobom na prístup k parametrom v zásobníku. Takéto alternatívne pomenovanie je niekedy mätúce, keď sa pozriete na cudzí programový kód – nachádzajú sa tam obe tieto označenia registrov.

Vlastnosti vykonávania kódu.

V mnohých typoch procesorov (napríklad x86) môže byť vykonaný iba prechod na inú adresu programu podľa podmienky. Toto nie je prípad ARM. Každá inštrukcia procesora ARM môže alebo nemusí byť vykonaná podmienečne. To vám umožňuje minimalizovať počet prechodov programom a tým efektívnejšie využívať procesorovú pipeline.

Koniec koncov, čo je to potrubie? Jedna inštrukcia procesora je teraz vybraná z programového kódu, predchádzajúca sa už dekóduje a predchádzajúca sa už vykonáva. To je prípad 3-stupňového pipeline procesora Amber A23, ktorý používame v našom projekte pre dosku Mars Rover2Mars Rover2. Modifikácia procesora Amber A25 má 5-stupňovú pipeline, je ešte efektívnejšia. Je tu však jedno veľké ALE. Príkazy skoku prinútia procesor vyčistiť potrubie a doplniť ho. Vyberie sa teda nový príkaz, no stále nie je čo dekódovať a navyše nie je čo okamžite vykonať. Efektívnosť vykonávania kódu klesá s častými prechodmi. Moderné procesory majú všetky druhy mechanizmov predikcie vetiev, ktoré nejakým spôsobom optimalizujú plnenie potrubia, ale náš procesor toto nemá. V každom prípade bol ARM múdry, aby umožnil vykonanie každého príkazu podmienečne.

Na procesore ARM sú v akomkoľvek type inštrukcie štyri bity podmienky vykonania inštrukcie zakódované v najvyšších štyroch bitoch kódu inštrukcie:

V procesore sú celkom 4 príznaky stavu:
. Negatívny - výsledok operácie bol negatívny,
. nula - výsledok je nula,
. Carry - prenos nastal pri vykonávaní operácie s číslami bez znamienka,
. oPretečenie - pri vykonávaní operácie s číslami so znamienkom došlo k pretečeniu, výsledok sa nezmestí do registra)

Tieto 4 príznaky tvoria mnoho možných kombinácií podmienok:

kód Prípona Význam Vlajky
4"h0 ekv Rovnaký Sada Z
4"h1 nie Nerovná sa Z jasné
4"h2 cs/hs Noste súpravu / nepodpísanú vyššie alebo rovnako C set
4"h3 cc/lo Noste čisté / nesignované nižšie C jasné
4"h4 mi Mínus/zápor N set
4"h5 pl Plus / plus alebo nula N jasné
4"h6 vs Pretečenie V set
4"h7 vc Žiadny prepad V jasné
4"h8 Ahoj Bez znamienka vyššie C set a Z jasné
4"h9 ls Bez znamienka nižšie alebo rovnaké C clear alebo Z set
4" ha ge Značené väčšie alebo rovné N == V
4"hb lt Podpísaných menej ako N = V
4"hc GT Podpísané väčšie ako Z == 0, N == V
4" HD le Podpísané menšie alebo rovnaké Z == 1 alebo N!= V
4" on al Vždy (bezpodmienečné)
4" vf - Neplatný stav

Teraz to vedie k ďalšiemu problému pri učení inštrukcií procesora ARM - k mnohým príponám, ktoré možno pridať do kódu inštrukcie. Napríklad sčítanie za predpokladu, že je nastavený príznak Z, je príkaz addeq ako add + prípona eq . Preskočte na podprogram, ak príznak N=0 je blpl ako bl + prípona pl .

Vlajky (zápor, nula, prenášanie, pretečenie) to isté nie je vždy nastavené počas aritmetických alebo logických operácií, ako sa to stáva, povedzme, v procesore x86, ale iba vtedy, keď to programátor chce. Na tento účel existuje ďalšia prípona k mnemotechnickému povelu: „s“ (v kóde príkazu je zakódovaný bitom 20). Príkaz sčítania teda nemení príznaky, ale príkaz add ich mení. Alebo môže existovať aj príkaz podmieneného sčítania, ktorý však zmení príznaky. Napríklad: addgts. Je zrejmé, že množstvo možných kombinácií názvov príkazov s rôznymi príponami pre podmienené vykonávanie a nastavenie príznakov robí kód zostavy procesora ARM veľmi zvláštnym a ťažko čitateľným. Postupom času si však na to zvyknete a začnete tomuto textu rozumieť.

Aritmetické a logické operácie (Spracovanie údajov).

Procesor ARM môže vykonávať rôzne aritmetické a logické operácie.

Skutočný štvorbitový operačný kód (Opcode) je obsiahnutý v bitoch inštrukcie procesora.

Akákoľvek operácia sa vykoná s obsahom registra a takzvaným posuvným operandom. Výsledok operácie sa uloží do registra. Štvorbitové Rn a Rd sú indexy registrov v aktívnej banke procesora.

V závislosti od bitu I 25 sa operand posunu spracováva buď ako číselná konštanta, alebo ako index druhého registra operandu a dokonca aj operácia posunu hodnoty druhého operandu.

Jednoduché príklady príkazov assembleru by vyzerali takto:

add r0,r1,r2 @ umiestnite súčet hodnôt registrov r1 a r2 do registra r0
sub r5,r4,#7 @ umiestnite rozdiel (r4-7) do registra r5

Vykonané operácie sú kódované takto:

4"h0 a logické AND Rd:= Rn AND operand_radenia
4"h1 eor Logical exclusive OR Rd:= Rn XOR shifter_operand
4"h2 sub Aritmetické odčítanie Rd:= Rn - operand_posunovača
4"h3 rsb Aritmetické spätné odčítanie Rd:= shifter_operand - Rn
4"h4 add Aritmetické sčítanie Rd:= Rn + shifter_operand
4"h5 adc Aritmetické sčítanie plus príznak prenosu Rd:= Rn + operand_posunovača + príznak prenosu
4"h6 sbc Aritmetické odčítanie s prenosom Rd:= Rn -operand_posunovača - NOT(Príznak prenosu)
4"h7 rsc Aritmetické spätné odčítanie s Rd:= shifter_operand - Rn - NOT(Carry Flag)
4"h8 tst Logický AND, ale bez uloženia výsledku sa zmenia iba vždy nastavené príznaky Rn AND shifter_operand S bit
4"h9 teq Logical exclusive OR, ale bez uloženia výsledku sa zmenia iba príznaky Rn EOR shifter_operand
Vždy nastavený bit S
4"ha cmp Porovnanie, alebo skôr aritmetické odčítanie bez uloženia výsledku, menia sa iba príznaky Rn - vždy nastavený bit shifter_operand S
4"hb cmn Porovnanie inverzného, ​​alebo skôr aritmetického sčítania bez uloženia výsledku, vždy sa menia len príznaky Rn + shifter_operand S bit
4"hc orr Logické OR Rd:= Rn ALEBO shifter_operand
4"hd mov Kopírovať hodnotu Rd:= shifter_operand (bez prvého operandu)
4"he bic Resetovacie bity Rd:= Rn AND NOT(operand_posun)
4"hf mvn Kopírovať inverznú hodnotu Rd:= NOT shifter_operand (žiadny prvý operand)

Prehadzovač sudov.

Procesor ARM má špeciálny obvod „barrel shifter“, ktorý umožňuje jeden z operandov posunúť alebo otočiť o určený počet bitov pred akoukoľvek aritmetickou alebo logickou operáciou. Ide o pomerne zaujímavú vlastnosť procesora, ktorá umožňuje vytvárať veľmi efektívny kód.

Napríklad:

@násobenie 9 znamená násobenie čísla 8
@ posunutím doľava o 3 bity plus ďalšie číslo
pridať r0, r1, r1, lsl #3 @ r0= r1+(r1<<3) = r1*9

@ násobenie číslom 15 je násobenie číslom 16 mínus číslo
rsb r0, r1, r1, lsl #4 @ r0= (r1<<4)-r1 = r1*15

@ prístup k tabuľke 4 bajtových slov, kde
@r1 je základná adresa tabuľky
@r2 je index prvku v tabuľke
ldr r0,

Okrem logického posunu vľavo lsl existuje aj logický posun vpravo lsr a aritmetický posun vpravo asr (posun zachovávajúci znamienko, najvýznamnejší bit sa násobí vľavo súčasne s posunom).

Existuje aj rotácia bitov ROR - bity sa pohybujú doprava a vytiahnuté doľava.
Existuje jeden bitový posun cez príznak C - toto je príkaz rrx. Hodnota registra sa posunie o jeden bit doprava. Vľavo je príznak C načítaný do najvýznamnejšieho bitu registra.

Posun môže byť uskutočnený nie pevným konštantným číslom, ale hodnotou tretieho registra operandov. Napríklad:

pridajte r0, r1, r1, lsr r3 @ toto je r0 = r1 + (r1>>r3);
pridajte r0, r0, r1, lsr r3 @ toto je r0 = r0 + (r1>>r3);

Takže shifter_operand je to, čo popisujeme v príkazoch assembleru, napríklad ako "r1, lsr r3" alebo "r2, lsl #5".

Najzaujímavejšie je, že používanie zmien v prevádzke nič nestojí. Tieto posuny (zvyčajne) nevyžadujú ďalšie taktovacie cykly, čo je veľmi dobré pre výkon systému.

Použitie číselných operandov.

Aritmetické alebo logické operácie môžu využívať ako druhý operand nielen obsah registra, ale aj číselnú konštantu.

Bohužiaľ, je tu jedno dôležité obmedzenie. Keďže všetky príkazy majú pevnú dĺžku 4 bajty (32 bitov), ​​nebude možné do nich zakódovať „akékoľvek“ číslo. V operačnom kóde sú už 4 bity obsadené kódom stavu vykonania (Cond), 4 bity pre samotný operačný kód (Opcode), potom 4 bity - register Rd prijímača a ďalšie 4 bity - register prvého operandu. Rn plus rôzne príznaky I 25 (označuje iba číselnú konštantu v kóde operácie) a S 20 (nastavenie príznakov po operácii). Celkovo zostáva len 12 bitov na možnú konštantu, takzvaný shifter_operand – videli sme to vyššie. Keďže 12 bitov dokáže kódovať čísla len v úzkom rozsahu, vývojári procesora ARM sa rozhodli kódovať konštantu nasledovne. Dvanásť bitov posuvného operandu je rozdelených na dve časti: štvorbitový indikátor rotácie encode_imm a aktuálnu osembitovú číselnú hodnotu imm_8.

Na procesore ARM je konštanta definovaná ako osembitové číslo vnútri 32-bitového čísla, otočené doprava o párny počet bitov. To je:

imm_32 = imm_8 ROR (encode_imm *2)

Ukázalo sa to dosť ošemetne. Ukazuje sa, že nie každé konštantné číslo je možné použiť v príkazoch assembleru.

Môžeš písať

pridajte r0, r2, #255 @ konštantu v desiatkovom tvare
pridajte r0, r3, #0xFF @ konštantu v šestnástkovej sústave

pretože 255 je v rozsahu 8 bitov. Tieto príkazy budú skompilované takto:

0: e28200ff pridať r0, r2, #255 ; 0xff
4: e28300ff pridať r0, r3, #255; 0xff

A dokonca môžete písať

pridajte r0, r4, #512
pridajte r0, r5, 0x650000

Kompilovaný kód bude vyzerať takto:

0: e2840c02 pridaj r0, r4, #512 ; 0x200
4: e2850865 pridať r0, r5, #6619136; 0x650000

Samotné číslo 512 sa v tomto prípade do bajtu samozrejme nezmestí. Ale potom si to predstavíme v hexadecimálnom tvare 32'h00000200 a vidíme, že toto je 2 rozšírené doprava o 24 bitov (1 alebo 24). Koeficient rotácie je dvakrát menší ako 24, teda 12. Takže to vyjde shifter_operand = ( 4’hc , 8’h02 ) – toto je dvanásť najmenej významných bitov príkazu. To isté platí pre číslo 0x650000. Pre neho je shifter_operand = ( 4’h8, 8’h65 ).

Je jasné, že nevieš písať

pridajte r0, r1,#1234567

alebo nevieš písať

mov r0, #511

keďže tu číslo nemôže byť reprezentované vo forme imm_8 a encode_imm - faktor rotácie. Kompilátor assembleru vyvolá chybu.

Čo robiť, keď konštantu nemožno priamo zakódovať do operandu shifter_operand? Budeme musieť robiť všelijaké triky.
Napríklad môžete najskôr načítať číslo 512 do voľného registra a potom odčítať číslo:

mov r0, #511
pod r0,r0,#1

Druhým spôsobom, ako načítať konkrétne číslo do registra, je načítať ho zo špeciálne vyhradenej premennej umiestnenej v pamäti:

ldr r7,my_var
.....
my_var: .word 0x123456

Najjednoduchší spôsob, ako to napísať, je takto:

ldr r2 = 511

V tomto prípade (všimnite si znak "="), ak môže byť konštanta reprezentovaná ako imm_8 a encode_imm , ak sa zmestí do bitu 12 shifter_operand , kompilátor zostavy automaticky skompiluje ldr do inštrukcie mov. Ale ak číslo nemôže byť reprezentované týmto spôsobom, potom samotný kompilátor vyhradí pamäťovú bunku v programe pre túto konštantu a sám dá tejto pamäťovej bunke názov a skompiluje príkaz do ldr .

Toto som napísal:

ldr r7,my_var
ldr r8 = 511
ldr r8 = 1024
ldr r9,=0x3456
........
My_var: .word 0x123456

Po kompilácii som dostal toto:

18: e59f7030 ldr r7, ; 50
1c: e59f8030 ldr r8, ; 54
20: e3a08b01 mov r8, #1024 ; 0x400
24: e59f902c ldr r9, ; 58
.............
00000050 :
50: 00123456 .slovo 0x00123456
54: 000001ff .slovo 0x000001ff
58: 00003456 .slovo 0x00003456

Všimnite si, že kompilátor používa adresovanie pamäte relatívne k registru pc (aka r15).

Čítanie pamäťovej bunky a zápis registra do pamäte.

Ako som písal vyššie, procesor ARM môže vykonávať iba aritmetické alebo logické operácie s obsahom registrov. Dáta pre operácie sa musia načítať z pamäte a výsledok operácií sa musí zapísať späť do pamäte. Sú na to špeciálne príkazy: ldr (pravdepodobne z kombinácie “LoaD Register”) na čítanie a str (pravdepodobne “STore Register”) na zápis.

Zdalo by sa, že existujú len dva tímy, no v skutočnosti majú veľa variácií. Stačí sa pozrieť na spôsob, akým sú príkazy ldr /str zakódované na procesore Amber ARM, aby ste videli, koľko pomocných príznakových bitov je L 20, W 21, B 22, U 23, P 24, I 25 - a určujú špecifické správanie príkaz:

  • Bit L 20 určuje zápis alebo čítanie. 1 - ldr, čítanie, 0 - str, zápis.
  • Bit B 22 určuje čítanie/zápis 32-bitového slova alebo 8-bitového bajtu. 1 znamená bajtovú operáciu. Keď sa bajt načíta do registra, najvýznamnejšie bity registra sa vynulujú.
  • Bit I 25 určuje použitie poľa Offset. Ak I 25 ==0, potom Offset je interpretovaný ako číselný offset, ktorý musí byť buď pridaný k základnej adrese z registra, alebo odčítaný. Ale sčítanie alebo odčítanie závisí od bitu U 23.

(Cond) - podmienka vykonania operácie. Interpretované rovnakým spôsobom ako pre logické/aritmetické príkazy – čítanie alebo zápis môže byť podmienený.

V texte zostavy teda môžete napísať niečo takéto:

ldr r1, @ do registra r1 prečíta slovo na adrese z registra r0
ldrb r1, @ do registra r1 načíta bajt na adrese z registra r0
ldreq r2, @ podmienené čítanie slov
ldrgtb r2, @ podmienené čítanie bajtu
ldr r3, @ číta slovo na adrese 8 relatívne k adrese z registra r4
ldr r4, @ číta slovo na adrese -16 relatívne k adrese z registra r5

Po zostavení tohto textu môžete vidieť skutočné kódy týchto príkazov:

0: e5901000 ldr r1,
4: e5d01000 ldrb r1,
8: 05912000 ldreq r2,
c: c5d12000 ldrbgt r2,
10: e5943008 ldr r3,
14: e5154010 ldr r4,

Vo vyššie uvedenom príklade používam iba ldr , ale str sa používa takmer rovnakým spôsobom.

Existujú režimy prístupu k pamäti pred indexovaním a po indexovaní. V týchto režimoch sa ukazovateľ prístupu do pamäte aktualizuje pred alebo po vykonaní inštrukcie. Ak ste oboznámení s programovacím jazykom C, potom ste oboznámení s konštrukciami prístupu ukazovateľa ako ( *psource++;) alebo ( a=*++psource;). Procesor ARM implementuje tento režim prístupu do pamäte. Pri vykonaní príkazu čítania sa aktualizujú dva registre naraz - register prijímača prijme hodnotu načítanú z pamäte a hodnota v registri ukazovateľa do pamäťovej bunky sa posunie dopredu alebo dozadu.

Písanie týchto príkazov je podľa mňa trochu nelogické. Zvyknúť si na to trvá dlho.

ldr r3, ! @psrc++; r3 = *psrc;
ldr r3, , #4 @ r3 = *psrc; psrc++;

Prvý príkaz ldr najprv zvýši ukazovateľ a potom načíta. Druhý príkaz najprv číta a potom zvyšuje ukazovateľ. Hodnota ukazovateľa psrc je v registri r0.

Všetky príklady diskutované vyššie boli pre prípad, keď bol bit I 25 v príkazovom kóde resetovaný. Ale stále sa dá nainštalovať! Potom hodnota poľa Offset nebude obsahovať číselnú konštantu, ale tretí register zúčastňujúci sa operácie. Navyše, hodnota tretieho registra môže byť stále vopred posunutá!

Tu sú príklady možných variácií kódu:

0: e7921003 ldr r1, @ čítať adresu - súčet hodnôt z registrov r2 a r3
4: e7b21003 ldr r1, ! @ to isté, ale po prečítaní r2 sa zvýši o hodnotu z r3
8: e6932004 ldr r2, , r4 @ najprv sa načíta adresa r3 a potom sa r3 zvýši o r4
c: e7943185 ldr r3, @ čítať adresu r4+r5*8
10: e7b43285 ldr r3, ! @ prečítaj adresu r4+r5*32, po prečítaní sa r4 nastaví na hodnotu tejto adresy
14: e69431a5 ldr r3, , r5, lsr #3 @ adresa pre čítanie r4, po vykonaní príkazu r4 sa nastaví na r4+r5/8

Toto sú variácie príkazov na čítanie/zápis v procesore ARM v2a.

V starších modeloch procesorov ARM je táto rozmanitosť príkazov ešte väčšia.
Je to spôsobené tým, že procesor umožňuje napríklad čítať nielen slová (32-bitové čísla) a bajty, ale aj polovičné slová (16 bitov, 2 bajty). Potom sa k príkazom ldr / str pridá prípona „h“ zo slova polovičné slovo. Príkazy budú vyzerať ako ldrh alebo strh. Existujú aj príkazy na načítanie polovičných slov ldrsh alebo bajtov ldrsb interpretovaných ako čísla so znamienkom. V týchto prípadoch sa najvýznamnejší bit načítaného slovného slova alebo bajtu vynásobí najvýznamnejšími bitmi celého slova v registri prijímača. Napríklad načítanie polslova 0xff25 pomocou príkazu ldrsh v cieľovom registri má za následok 0xffffff25 .

Viacnásobné čítanie a zápis.

Príkazy ldr /str nie sú jediné na prístup k pamäti. Procesor ARM má tiež príkazy, ktoré umožňujú vykonávať prenosy blokov – obsah niekoľkých po sebe idúcich slov môžete načítať z pamäte a niekoľkých registrov naraz. Môžete tiež zapísať hodnoty niekoľkých registrov postupne do pamäte.

Mnemotechniky príkazov na prenos blokov začínajú v koreňovom adresári ldm (LoaD Multiple) alebo stm (Store Multiple). Ale potom, ako to už v ARM býva, začína príbeh s príponami.

Vo všeobecnosti príkaz vyzerá takto:

op(cond)(režim) Rd(, {Register list} !}

Prípona (Cond) je zrozumiteľná, je to podmienka pre vykonanie príkazu. Prípona (režim) je režim prenosu, o tom neskôr. Rd je register, ktorý určuje základnú adresu v pamäti pre čítanie alebo zápis. Výkričník za registrom Rd označuje, že sa po operácii čítania/zápisu zmení. Zoznam registrov, ktoré sú načítané z pamäte alebo stránkované do pamäte, je (Zoznam registrov).

Zoznam registrov je uvedený v zložených zátvorkách oddelených čiarkami alebo ako rozsah. Napríklad:

stm r0,(r3,r1, r5-r8)

Pamäť bude zapísaná mimo prevádzky. Zoznam jednoducho uvádza, ktoré registre sa zapíšu do pamäte a to je všetko. Príkazový kód obsahuje 16 bitov vyhradených pre zoznam registrov, čo je presne počet registrov v procesorovej banke. Každý bit v tomto poli označuje, ktorý register sa zúčastní operácie.

Teraz o režime čítania/zápisu. Tu je priestor na zmätok. Faktom je, že pre rovnakú akciu možno použiť rôzne názvy režimov.

Ak urobíme malú lyrickú odbočku, potom sa musíme porozprávať o... hromade. Zásobník je spôsob prístupu k údajom typu LIFO - Last In First Out (wiki) - posledný dovnútra, prvý von. Zásobník je široko používaný pri programovaní pri volaní procedúr a ukladaní stavu registrov na vstupe funkcií a ich obnove pri výstupe, ako aj pri odovzdávaní parametrov volaným procedúram.

Existujú, kto by si myslel, štyri typy pamäťových zásobníkov.

Prvý typ je Plne zostupne. Toto je, keď ukazovateľ zásobníka ukazuje na obsadený prvok zásobníka a zásobník rastie smerom k klesajúcim adresám. Keď potrebujete vložiť slovo do zásobníka, najprv sa zníži ukazovateľ zásobníka (Decrement Before), potom sa slovo zapíše na adresu ukazovateľa zásobníka. Keď potrebujete odstrániť počítačové slovo zo zásobníka, slovo sa načíta pomocou aktuálnej hodnoty ukazovateľa zásobníka a potom sa ukazovateľ posunie nahor (Increment After).

Druhý typ je Full Ascending. Zásobník nerastie nadol, ale nahor, smerom k väčším adresám. Ukazovateľ tiež ukazuje na obsadený prvok. Keď potrebujete vložiť slovo do zásobníka, najprv sa zvýši ukazovateľ zásobníka, potom sa slovo zapíše do ukazovateľa (Increment Before). Keď potrebujete odstrániť zo zásobníka, najprv si prečítajte ukazovateľ zásobníka, pretože ukazuje na obsadený prvok, potom sa ukazovateľ zásobníka zníži (Decrement After).

Tretím typom je Empty Descending. Zásobník rastie smerom nadol, ako v prípade Úplného zostupovania, ale rozdiel je v tom, že ukazovateľ zásobníka ukazuje na neobsadenú bunku. Preto, keď potrebujete vložiť slovo do zásobníka, záznam sa urobí okamžite, potom sa ukazovateľ zásobníka zníži (Decrement After). Pri odstraňovaní zo zásobníka sa ukazovateľ najprv zvýši a potom sa načíta (Increment Before).

Štvrtým typom je Empty Ascending. Dúfam, že je všetko jasné - zásobník rastie nahor. Ukazovateľ zásobníka ukazuje na prázdny prvok. Vloženie do zásobníka znamená napísanie slova na adresu ukazovateľa zásobníka a zvýšenie ukazovateľa zásobníka (Increment After). Vyskočiť zo zásobníka - znížte ukazovateľ zásobníka a prečítajte si slovo (Znížiť predtým).

Pri vykonávaní operácií na zásobníku je teda potrebné zvýšiť alebo znížiť ukazovateľ - (Increment/Decrement) pred alebo po (Pred/Po) čítaní/zápise do pamäte, v závislosti od typu zásobníka. Napríklad procesory Intel majú špeciálne príkazy na prácu so zásobníkom, ako je PUSH (vloženie slova do zásobníka) alebo POP (vysunutie slova zo zásobníka). V procesore ARM nie sú žiadne špeciálne inštrukcie, ale používajú sa inštrukcie ldm a stm.

Ak implementujete zásobník pomocou inštrukcií procesora ARM, získate nasledujúci obrázok:

Prečo bolo potrebné tomu istému tímu dať iné mená? Vôbec tomu nerozumiem... Tu, samozrejme, treba poznamenať, že štandard zásobníka pre ARM je stále Full Descending.

Ukazovateľ zásobníka v procesore ARM je register sp alebo r13. Toto je v podstate dohoda. Samozrejme, zápis stm alebo čítanie ldm je možné vykonať aj s inými základnými registrami. Treba si však zapamätať, ako sa sp register líši od iných registrov – môže sa líšiť v rôznych prevádzkových režimoch procesora (USR, SVC, IRQ, FIRQ), pretože majú svoje vlastné banky registrov.

A ešte jedna poznámka. Napíšte takýto riadok do kódu zostavy ARM tlačiť (r0-r3), Samozrejme môžete. Len v skutočnosti to bude ten istý tím stmfd sp!,(r0-r3).

Nakoniec uvediem príklad kódu assembleru a jeho zostaveného rozloženého textu. Máme:


stmfd sp!,(r0-r3)
stmdb sp!, (r0-r3)
push(r0-r3)

@tieto tri pokyny sú rovnaké a robia to isté
pop(r0-r3)
ldmia sp!,(r0-r3)
ldmfd r13!, (r0-r3)

Stmfd r4,(r0-r3,r5,r8)
stmea r4!,(r0-r3,r7,r9,lr,pc)
ldm r5,(r0,pc)

Po kompilácii dostaneme:

0: e92d000f push (r0, r1, r2, r3)
4: e92d000f push (r0, r1, r2, r3)
8: e92d000f push (r0, r1, r2, r3)
c: e8bd000f pop (r0, r1, r2, r3)
10: e8bd000f pop (r0, r1, r2, r3)
14: e8bd000f pop (r0, r1, r2, r3)
18: e904012f stmdb r4, (r0, r1, r2, r3, r5, r8)
1c: e8a4c28f stmia r4!, (r0, r1, r2, r3, r7, r9, lr, pc)
20: e8958001 ldm r5, (r0, ks)

Prechody v programoch.

Programovanie nie je možné bez prechodov. V každom programe existuje cyklické vykonávanie kódu a volania procedúr, funkcií a existuje aj podmienené vykonávanie sekcií kódu.

Procesor Amber ARM v2a má len dva príkazy: b (od slova Branch - vetva, prechod) a bl (Branch with Link - prechod pri zachovaní návratovej adresy).

Syntax príkazu je veľmi jednoduchá:

b(podmienka) štítok
bl(cond)label

Je jasné, že akékoľvek prechody môžu byť podmienené, to znamená, že program môže obsahovať zvláštne slová, ako sú tieto, vytvorené z koreňov „b“ a „bl“ a prípony podmienky (Cond):

beq, bne, bcs, bhs, bcc, blo, bmi, bpl, bvs, bvc, bhi, bls, bge, bgt, ble, bal, b

bleq, blne, blcs, blhs, blcc, bllo, blmi, blpl, blvs, blvc, blhi, blls, blge, blgt, blle, blal, bl

Rozmanitosť je úžasná, však?

Príkaz skoku obsahuje 24-bitový offset Offset. Adresa skoku sa vypočíta ako súčet aktuálnej hodnoty ukazovateľa pc a čísla posunu posunutého o 2 bity doľava, interpretovaného ako číslo so znamienkom:

Nový ks = ks + posun*4

Rozsah prechodov je teda 32 MB dopredu alebo dozadu.

Pozrime sa, čo je to prechod pri zachovaní spiatočnej adresy bl. Tento príkaz sa používa na volanie podprogramov. Zaujímavosťou tohto príkazu je, že návratová adresa z procedúry pri volaní procedúry nie je uložená v zásobníku, ako na procesoroch Intel, ale v bežnom registri r14. Potom, aby ste sa vrátili z procedúry, nepotrebujete špeciálny príkaz ret, ako pri rovnakých procesoroch Intel, ale môžete jednoducho skopírovať hodnotu r14 späť do počítača. Teraz je jasné, prečo má register r14 alternatívny názov lr (Link Register).

Pozrime sa na postup outbyte z projektu hello-world pre Amber SoC.

000004a0<_outbyte>:
4a0: e59f1454 ldr r1, ; 8fc< адрес регистра данных UART >
4a4: e59f3454 ldr r3, ; 900< адрес регистра статуса UART >
4a8: e5932000 ldr r2, ; prečítajte si aktuálny stav
4ac: e2022020 a r2, r2, #32
4b0: e3520000 cmp r2, #0 ; skontrolujte, či UART nie je zaneprázdnený
4b4: 05c10000 strbeq r0, ; napíšte znak do UART iba vtedy, ak nie je zaneprázdnený
4b8: 01b0f00e movseq pc, lr ; podmienený návrat z procedúry, ak UART nebol zaneprázdnený
4bc: 1affff9 bne 4a8<_outbyte+0x8>; slučky na kontrolu stavu UART

Myslím, že z komentárov v tomto fragmente je jasné, ako tento postup funguje.

Ďalšia dôležitá poznámka o prechodoch. Register r15 (pc) môže byť použitý v bežných aritmetických alebo logických operáciách ako register prijímača. Takže príkaz ako add pc,pc,#8 je celkom inštrukcia na presun na inú adresu.

V súvislosti s prechodmi je potrebné urobiť ešte jednu poznámku. Staršie procesory ARM majú aj ďalšie vetvené inštrukcie bx, blx a blj. Toto sú príkazy na preskočenie na fragmenty kódu s iným príkazovým systémom. Bx /blx vám umožňuje prepnúť na 16-bitový kód THUMB procesorov ARM. Blj je volanie postupov inštrukčného systému Jazelle (podpora jazyka Java v procesoroch ARM). Náš Amber ARM v2a tieto príkazy nemá.

Ahojte všetci!
Podľa povolania som Java programátor. Posledné mesiace práce ma prinútili zoznámiť sa s vývojom pre Android NDK a podľa toho aj s písaním natívnych aplikácií v C. Tu som narazil na problém s optimalizáciou linuxových knižníc. Mnohé sa ukázali byť úplne neoptimalizované pre ARM a značne zaťažovali procesor. Predtým som prakticky nikdy neprogramoval v assembleri, takže spočiatku bolo ťažké začať sa učiť tento jazyk, ale napriek tomu som sa rozhodol skúsiť to. Tento článok bol napísaný takpovediac od začiatočníka pre začiatočníkov. Pokúsim sa popísať základy, ktoré som sa už naučil, dúfam, že to niekoho zaujme. Okrem toho budem rád, ak dostanem konštruktívnu kritiku od profesionálov.

Úvod
Najprv si teda poďme zistiť, čo je ARM. Wikipedia dáva túto definíciu:

Architektúra ARM (Advanced RISC Machine, Acorn RISC Machine, pokročilý stroj RISC) je rodina licencovaných 32-bitových a 64-bitových mikroprocesorových jadier vyvinutých spoločnosťou ARM Limited. Spoločnosť pre ne výhradne vyvíja jadrá a nástroje (kompilátory, nástroje na ladenie atď.), pričom zarába na licencovaní architektúry výrobcom tretích strán.

Ak niekto nevie, väčšina mobilných zariadení a tabletov je teraz vyvinutá na tejto architektúre procesora. Hlavnou výhodou tejto rodiny je nízka spotreba energie, vďaka čomu sa často používa v rôznych vstavaných systémoch. Architektúra sa postupom času vyvíjala a počnúc ARMv7 boli definované 3 profily: ‚A‘ (aplikácia) – aplikácie, ‚R‘ (v reálnom čase) – v reálnom čase, ‚M‘ (mikrokontrolér) – mikrokontrolér. Históriu vývoja tejto technológie a ďalšie zaujímavé údaje si môžete prečítať na Wikipédii alebo vygoogliť na internete. ARM podporuje rôzne prevádzkové režimy (Thumb a ARM, navyše sa nedávno objavil Thumb-2, čo je zmes ARM a Thumb). V tomto článku sa pozrieme na samotný režim ARM, v ktorom sa vykonáva 32-bitová inštrukčná sada.

Každý procesor ARM je vytvorený z nasledujúcich blokov:

  • 37 registrov (z ktorých iba 17 je viditeľných počas vývoja)
  • Aritmetická logická jednotka (ALU) - vykonáva aritmetické a logické úlohy
  • Barrel shifter - zariadenie určené na presun blokov dát o určitý počet bitov
  • CP15 je špeciálny systém, ktorý riadi koprocesory ARM
  • Dekodér inštrukcií – zaoberá sa prevodom inštrukcií na postupnosť mikrooperácií
Nie sú to všetky komponenty ARM, no ponorenie sa do džungle konštrukcie procesorov je nad rámec tohto článku.
Realizácia potrubia
Procesory ARM používajú 3-stupňový pipeline (od ARM8 bol implementovaný 5-stupňový pipeline). Pozrime sa na jednoduchú pipeline s použitím procesora ARM7TDMI ako príklad. Vykonanie každej inštrukcie pozostáva z troch krokov:

1. Fáza odberu vzoriek (F)
V tejto fáze prúdia inštrukcie z RAM do potrubia procesora.
2. Fáza dekódovania (D)
Inštrukcie sú dekódované a ich typ je rozpoznaný.
3. Fáza vykonávania (E)
Dáta vstúpia do ALU a vykonajú sa a výsledná hodnota sa zapíše do určeného registra.

Pri vývoji je však potrebné vziať do úvahy, že existujú pokyny, ktoré používajú niekoľko cyklov vykonávania, napríklad načítanie (LDR) alebo ukladanie. V tomto prípade je realizačná etapa (E) rozdelená na etapy (E1, E2, E3...).

Podmienečné prevedenie
Jednou z najdôležitejších funkcií assemblera ARM je podmienené vykonávanie. Každá inštrukcia môže byť vykonaná podmienečne a používajú sa na to prípony. Ak sa k názvu inštrukcie pridá prípona, pred jej vykonaním sa skontrolujú parametre. Ak parametre nespĺňajú podmienku, inštrukcia sa nevykoná. Prípony:
MI - záporné číslo
PL - kladné alebo nulové
AL - vždy vykonávať inštrukcie
Existuje oveľa viac prípon podmieneného vykonania. Prečítajte si zvyšok prípon a príkladov v oficiálnej dokumentácii: Dokumentácia ARM
Teraz je čas zvážiť...
Základná syntax assemblera ARM
Pre tých, ktorí už predtým pracovali s assemblerom, môžete tento bod preskočiť. Pre všetkých ostatných popíšem základy práce s týmto jazykom. Takže každý program v assembleri pozostáva z inštrukcií. Inštrukcia je vytvorená týmto spôsobom:
(štítok) (návod|operandy) (@ ​​komentár)
Označenie je voliteľný parameter. Inštrukcia je priama mnemotechnická pomôcka inštrukcií pre procesor. Základné pokyny a ich použitie budú uvedené nižšie. Operandy - konštanty, adresy registrov, adresy v RAM. Komentár je voliteľný parameter, ktorý neovplyvňuje vykonávanie programu.
Registrovať mená
Povolené sú nasledujúce názvy registrov:
1.r0-r15

3.v1-v8 (premenné registre, r4 až r11)

4.sb a SB (statický register, r9)

5.sl a SL (r10)

6.fp a FP (r11)

7.ip a IP (r12)

8.sp a SP (r13)

9.lr a LR (r14)

10.pc a PC (počítadlo programov, r15).

Premenné a konštanty
V ARM assembleri, ako v každom (prakticky) inom programovacom jazyku, možno použiť premenné a konštanty. Sú rozdelené do nasledujúcich typov:
  • Numerický
  • hlavolam
  • Reťazec
Číselné premenné sa inicializujú takto:
SETA 100; vytvorí sa číselná premenná "a" s hodnotou 100.
Premenné reťazca:
improb SETS "doslova"; vytvorí sa premenná improb s hodnotou „literal“. POZOR! Hodnota premennej nemôže presiahnuť 5120 znakov.
Booleovské premenné používajú hodnoty TRUE a FALSE.
Príklady inštrukcií assembleru ARM
V tejto tabuľke som zhromaždil základné pokyny, ktoré budú potrebné pre ďalší vývoj (v najzákladnejšej fáze:):

Aby sme posilnili používanie základných inštrukcií, napíšme niekoľko jednoduchých príkladov, ale najprv budeme potrebovať arm toolchain. Pracujem na Linuxe, takže som si vybral: frank.harvard.edu/~coldwell/toolchain (arm-unknown-linux-gnu toolchain). Dá sa nainštalovať rovnako jednoducho ako ktorýkoľvek iný program na Linuxe. V mojom prípade (ruská Fedora) som potreboval nainštalovať iba rpm balíčky zo stránky.
Teraz je čas napísať jednoduchý príklad. Program bude úplne zbytočný, ale hlavné je, že bude fungovať :) Tu je kód, ktorý vám ponúkam:
štart: @ Voliteľný riadok označujúci začiatok programu mov r0, #3 @ Načítajte register r0 s hodnotou 3 mov r1, #2 @ Urobte to isté s registrom r1, len teraz s hodnotou 2 pridajte r2, r1, r0 @ Pridajte hodnoty r0 a r1, odpoveď sa zapíše do r2 mul r3, r1, r0 @ Vynásobte hodnotu registra r1 hodnotou registra r0, odpoveď sa zapíše do r3 stop: b stop @ Riadok ukončenia programu
Kompilujeme program, aby sme získali súbor .bin:
/usr/arm/bin/arm-unknown-linux-gnu-as -o arm.o arm.s /usr/arm/bin/arm-unknown-linux-gnu-ld -Ttext=0x0 -o ​​​​arm. elf arm .o /usr/arm/bin/arm-unknown-linux-gnu-objcopy -O binárne rameno.elf arm.bin
(kód je v súbore arm.s a reťazec nástrojov je v mojom prípade v adresári /usr/arm/bin/)
Ak všetko prebehlo dobre, budete mať 3 súbory: arm.s (skutočný kód), arm.o, arm.elf, arm.bin (skutočný spustiteľný program). Na kontrolu fungovania programu nie je potrebné mať vlastné rameno. Stačí nainštalovať QEMU. Pre informáciu:

QEMU je bezplatný a open source program na emuláciu hardvéru rôznych platforiem.

Obsahuje emuláciu procesorov Intel x86 a I/O zariadení. Dokáže emulovať 80386, 80486, Pentium, Pentium Pro, AMD64 a ďalšie procesory kompatibilné s x86; PowerPC, ARM, MIPS, SPARC, SPARC64, m68k - len čiastočne.

Funguje na Syllable, FreeBSD, FreeDOS, Linux, Windows 9x, Windows 2000, Mac OS X, QNX, Android atď.

Takže na emuláciu arm budete potrebovať qemu-system-arm. Tento balík je v yum, takže pre tých, ktorí majú Fedoru, sa nemusíte obťažovať a stačí spustiť príkaz:
yum install qemu-system-arm

Ďalej musíme spustiť emulátor ARM, aby spustil náš program arm.bin. Na tento účel si vytvoríme súbor flash.bin, ktorý bude flash pamäťou pre QEMU. Je to veľmi jednoduché:
dd if=/dev/zero of=flash.bin bs=4096 počet=4096 dd if=arm.bin of=flash.bin bs=4096 conv=notrunc
Teraz načítame QEMU s výslednou flash pamäťou:
qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
Výstup bude asi takýto:

$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
Monitor QEMU 0.15.1 – pre viac informácií napíšte „help“.
(qemu)

Náš program arm.bin musel zmeniť hodnoty štyroch registrov, preto, aby sme skontrolovali správnu činnosť, pozrime sa na tieto rovnaké registre. Robí sa to veľmi jednoduchým príkazom: info registers
Na výstupe uvidíte všetkých 15 ARM registrov a štyri z nich budú mať zmenené hodnoty. Skontrolujte :) Hodnoty registra sa zhodujú s tými, ktoré možno očakávať po spustení programu:
(qemu) informačné registre R00=00000003 R01=00000002 R02=00000005 R03=00000006 R04=00000000 R05=00000000 R06=00000000 R07=000 00000 R10=00000000 R11=00000000 R12=00000000 R13=00000000 R14= 00000000 R15=00000010 PSR=400001d3 -Z-- A svc32

P.S. V tomto článku som sa snažil popísať základy programovania v ARM assembleri. Dúfam, že sa vám to páčilo! To bude stačiť na ďalšie ponorenie sa do džungle tohto jazyka a písanie programov v ňom. Ak sa všetko podarí, napíšem ďalej, čo sám zistím. Ak sa vyskytnú chyby, nevyhadzujte ma, pretože som v assembleri nový.

Ahojte všetci!
Podľa povolania som Java programátor. Posledné mesiace práce ma prinútili zoznámiť sa s vývojom pre Android NDK a podľa toho aj s písaním natívnych aplikácií v C. Tu som narazil na problém s optimalizáciou linuxových knižníc. Mnohé sa ukázali byť úplne neoptimalizované pre ARM a značne zaťažovali procesor. Predtým som prakticky nikdy neprogramoval v assembleri, takže spočiatku bolo ťažké začať sa učiť tento jazyk, ale napriek tomu som sa rozhodol skúsiť to. Tento článok bol napísaný takpovediac od začiatočníka pre začiatočníkov. Pokúsim sa popísať základy, ktoré som sa už naučil, dúfam, že to niekoho zaujme. Okrem toho budem rád, ak dostanem konštruktívnu kritiku od profesionálov.

Úvod
Najprv si teda poďme zistiť, čo je ARM. Wikipedia dáva túto definíciu:

Architektúra ARM (Advanced RISC Machine, Acorn RISC Machine, pokročilý stroj RISC) je rodina licencovaných 32-bitových a 64-bitových mikroprocesorových jadier vyvinutých spoločnosťou ARM Limited. Spoločnosť pre ne výhradne vyvíja jadrá a nástroje (kompilátory, nástroje na ladenie atď.), pričom zarába na licencovaní architektúry výrobcom tretích strán.

Ak niekto nevie, väčšina mobilných zariadení a tabletov je teraz vyvinutá na tejto architektúre procesora. Hlavnou výhodou tejto rodiny je nízka spotreba energie, vďaka čomu sa často používa v rôznych vstavaných systémoch. Architektúra sa postupom času vyvíjala a počnúc ARMv7 boli definované 3 profily: ‚A‘ (aplikácia) – aplikácie, ‚R‘ (v reálnom čase) – v reálnom čase, ‚M‘ (mikrokontrolér) – mikrokontrolér. Históriu vývoja tejto technológie a ďalšie zaujímavé údaje si môžete prečítať na Wikipédii alebo vygoogliť na internete. ARM podporuje rôzne prevádzkové režimy (Thumb a ARM, navyše sa nedávno objavil Thumb-2, čo je zmes ARM a Thumb). V tomto článku sa pozrieme na samotný režim ARM, v ktorom sa vykonáva 32-bitová inštrukčná sada.

Každý procesor ARM je vytvorený z nasledujúcich blokov:

  • 37 registrov (z ktorých iba 17 je viditeľných počas vývoja)
  • Aritmetická logická jednotka (ALU) - vykonáva aritmetické a logické úlohy
  • Barrel shifter - zariadenie určené na presun blokov dát o určitý počet bitov
  • CP15 je špeciálny systém, ktorý riadi koprocesory ARM
  • Dekodér inštrukcií – zaoberá sa prevodom inštrukcií na postupnosť mikrooperácií
Nie sú to všetky komponenty ARM, no ponorenie sa do džungle konštrukcie procesorov je nad rámec tohto článku.
Realizácia potrubia
Procesory ARM používajú 3-stupňový pipeline (od ARM8 bol implementovaný 5-stupňový pipeline). Pozrime sa na jednoduchú pipeline s použitím procesora ARM7TDMI ako príklad. Vykonanie každej inštrukcie pozostáva z troch krokov:

1. Fáza odberu vzoriek (F)
V tejto fáze prúdia inštrukcie z RAM do potrubia procesora.
2. Fáza dekódovania (D)
Inštrukcie sú dekódované a ich typ je rozpoznaný.
3. Fáza vykonávania (E)
Dáta vstúpia do ALU a vykonajú sa a výsledná hodnota sa zapíše do určeného registra.

Pri vývoji je však potrebné vziať do úvahy, že existujú pokyny, ktoré používajú niekoľko cyklov vykonávania, napríklad načítanie (LDR) alebo ukladanie. V tomto prípade je realizačná etapa (E) rozdelená na etapy (E1, E2, E3...).

Podmienečné prevedenie
Jednou z najdôležitejších funkcií assemblera ARM je podmienené vykonávanie. Každá inštrukcia môže byť vykonaná podmienečne a používajú sa na to prípony. Ak sa k názvu inštrukcie pridá prípona, pred jej vykonaním sa skontrolujú parametre. Ak parametre nespĺňajú podmienku, inštrukcia sa nevykoná. Prípony:
MI - záporné číslo
PL - kladné alebo nulové
AL - vždy vykonávať inštrukcie
Existuje oveľa viac prípon podmieneného vykonania. Prečítajte si zvyšok prípon a príkladov v oficiálnej dokumentácii: Dokumentácia ARM
Teraz je čas zvážiť...
Základná syntax assemblera ARM
Pre tých, ktorí už predtým pracovali s assemblerom, môžete tento bod preskočiť. Pre všetkých ostatných popíšem základy práce s týmto jazykom. Takže každý program v assembleri pozostáva z inštrukcií. Inštrukcia je vytvorená týmto spôsobom:
(štítok) (návod|operandy) (@ ​​komentár)
Označenie je voliteľný parameter. Inštrukcia je priama mnemotechnická pomôcka inštrukcií pre procesor. Základné pokyny a ich použitie budú uvedené nižšie. Operandy - konštanty, adresy registrov, adresy v RAM. Komentár je voliteľný parameter, ktorý neovplyvňuje vykonávanie programu.
Registrovať mená
Povolené sú nasledujúce názvy registrov:
1.r0-r15

3.v1-v8 (premenné registre, r4 až r11)

4.sb a SB (statický register, r9)

5.sl a SL (r10)

6.fp a FP (r11)

7.ip a IP (r12)

8.sp a SP (r13)

9.lr a LR (r14)

10.pc a PC (počítadlo programov, r15).

Premenné a konštanty
V ARM assembleri, ako v každom (prakticky) inom programovacom jazyku, možno použiť premenné a konštanty. Sú rozdelené do nasledujúcich typov:
  • Numerický
  • hlavolam
  • Reťazec
Číselné premenné sa inicializujú takto:
SETA 100; vytvorí sa číselná premenná "a" s hodnotou 100.
Premenné reťazca:
improb SETS "doslova"; vytvorí sa premenná improb s hodnotou „literal“. POZOR! Hodnota premennej nemôže presiahnuť 5120 znakov.
Booleovské premenné používajú hodnoty TRUE a FALSE.
Príklady inštrukcií assembleru ARM
V tejto tabuľke som zhromaždil základné pokyny, ktoré budú potrebné pre ďalší vývoj (v najzákladnejšej fáze:):

Aby sme posilnili používanie základných inštrukcií, napíšme niekoľko jednoduchých príkladov, ale najprv budeme potrebovať arm toolchain. Pracujem na Linuxe, takže som si vybral: frank.harvard.edu/~coldwell/toolchain (arm-unknown-linux-gnu toolchain). Dá sa nainštalovať rovnako jednoducho ako ktorýkoľvek iný program na Linuxe. V mojom prípade (ruská Fedora) som potreboval nainštalovať iba rpm balíčky zo stránky.
Teraz je čas napísať jednoduchý príklad. Program bude úplne zbytočný, ale hlavné je, že bude fungovať :) Tu je kód, ktorý vám ponúkam:
štart: @ Voliteľný riadok označujúci začiatok programu mov r0, #3 @ Načítajte register r0 s hodnotou 3 mov r1, #2 @ Urobte to isté s registrom r1, len teraz s hodnotou 2 pridajte r2, r1, r0 @ Pridajte hodnoty r0 a r1, odpoveď sa zapíše do r2 mul r3, r1, r0 @ Vynásobte hodnotu registra r1 hodnotou registra r0, odpoveď sa zapíše do r3 stop: b stop @ Riadok ukončenia programu
Kompilujeme program, aby sme získali súbor .bin:
/usr/arm/bin/arm-unknown-linux-gnu-as -o arm.o arm.s /usr/arm/bin/arm-unknown-linux-gnu-ld -Ttext=0x0 -o ​​​​arm. elf arm .o /usr/arm/bin/arm-unknown-linux-gnu-objcopy -O binárne rameno.elf arm.bin
(kód je v súbore arm.s a reťazec nástrojov je v mojom prípade v adresári /usr/arm/bin/)
Ak všetko prebehlo dobre, budete mať 3 súbory: arm.s (skutočný kód), arm.o, arm.elf, arm.bin (skutočný spustiteľný program). Na kontrolu fungovania programu nie je potrebné mať vlastné rameno. Stačí nainštalovať QEMU. Pre informáciu:

QEMU je bezplatný a open source program na emuláciu hardvéru rôznych platforiem.

Obsahuje emuláciu procesorov Intel x86 a I/O zariadení. Dokáže emulovať 80386, 80486, Pentium, Pentium Pro, AMD64 a ďalšie procesory kompatibilné s x86; PowerPC, ARM, MIPS, SPARC, SPARC64, m68k - len čiastočne.

Funguje na Syllable, FreeBSD, FreeDOS, Linux, Windows 9x, Windows 2000, Mac OS X, QNX, Android atď.

Takže na emuláciu arm budete potrebovať qemu-system-arm. Tento balík je v yum, takže pre tých, ktorí majú Fedoru, sa nemusíte obťažovať a stačí spustiť príkaz:
yum install qemu-system-arm

Ďalej musíme spustiť emulátor ARM, aby spustil náš program arm.bin. Na tento účel si vytvoríme súbor flash.bin, ktorý bude flash pamäťou pre QEMU. Je to veľmi jednoduché:
dd if=/dev/zero of=flash.bin bs=4096 počet=4096 dd if=arm.bin of=flash.bin bs=4096 conv=notrunc
Teraz načítame QEMU s výslednou flash pamäťou:
qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
Výstup bude asi takýto:

$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
Monitor QEMU 0.15.1 – pre viac informácií napíšte „help“.
(qemu)

Náš program arm.bin musel zmeniť hodnoty štyroch registrov, preto, aby sme skontrolovali správnu činnosť, pozrime sa na tieto rovnaké registre. Robí sa to veľmi jednoduchým príkazom: info registers
Na výstupe uvidíte všetkých 15 ARM registrov a štyri z nich budú mať zmenené hodnoty. Skontrolujte :) Hodnoty registra sa zhodujú s tými, ktoré možno očakávať po spustení programu:
(qemu) informačné registre R00=00000003 R01=00000002 R02=00000005 R03=00000006 R04=00000000 R05=00000000 R06=00000000 R07=000 00000 R10=00000000 R11=00000000 R12=00000000 R13=00000000 R14= 00000000 R15=00000010 PSR=400001d3 -Z-- A svc32

P.S. V tomto článku som sa snažil popísať základy programovania v ARM assembleri. Dúfam, že sa vám to páčilo! To bude stačiť na ďalšie ponorenie sa do džungle tohto jazyka a písanie programov v ňom. Ak sa všetko podarí, napíšem ďalej, čo sám zistím. Ak sa vyskytnú chyby, nevyhadzujte ma, pretože som v assembleri nový.

1. Počítadlo hodín reálneho času musí byť povolené (1); Bit voľby zdroja hodín sa vymaže (2), ak taktovanie nezabezpečuje hlavný generátor hodín.

2. Musí byť nastavený jeden alebo oba bity výberu udalosti prerušenia (3). A vyberá sa, ktoré udalosti spustia požiadavku na prerušenie (5).

3. Musia byť špecifikované masky udalostí prerušenia (4, 7).

2.5 O programovaní ARM7 v assembleri

Inštrukčná sada ARM7 (časť 1.4) obsahuje iba 45 inštrukcií, ktoré sú pomerne zložité kvôli rôznym metódam adresovania, podmieneným poliam a modifikátorom. Program assembler je ťažkopádny a

s ťažko čitateľný. Preto sa assembler pri programovaní pre architektúru ARM7 používa len zriedka.

Zároveň jazyk C na vysokej úrovni skrýva pred programátorom mnoho architektonických prvkov. Programátor sa prakticky nedotýka procedúr, ako je výber režimu jadra, prideľovanie pamäte pre zásobník a spracovanie prerušení. Na naučenie sa týchto postupov je užitočné napísať aspoň jeden jednoduchý program v jazyku symbolických inštancií.

Navyše, aj keď používate C, stále sa musíte uchýliť k jazyku symbolických inštrukcií.

1. Malo by byť kontrolované Kompilátor jazyka C sleduje, či počas optimalizácie vylúčil dôležité príkazy, pričom ich považuje za zbytočné. Generuje kompilátor extrémne neefektívny kód pre relatívne jednoduchú operáciu z dôvodu nedostatočnej optimalizácie. Aby ste sa uistili, že kompilátor skutočne používa tie hardvérové ​​prostriedky, ktoré sú určené na zvýšenie efektívnosti konkrétneho algoritmu.

2. Pri hľadaní chýb alebo príčin výnimiek (časť 2.4.1).

3. Získať kód, ktorý je absolútne optimálny z hľadiska výkonu alebo spotreby pamäte (kapitoly 2.2.20, 3.1.5).

Pozrime sa na základné techniky písania programu v assembleri

s cieľom je ukázať všetok kód vykonávaný mikrokontrolérom tak, ako je, a bez sprostredkovania C kompilátor.

Postup vytvárania projektu založeného na assembleri je takmer rovnaký ako pri programoch C (kapitoly 2.3.1–2.3.3). Existujú len dve výnimky:

a) zdrojový textový súbor má priradenú príponu *.S;

b) tu sa predpokladá, že súbor STARTUP.S nie je pripojený k programu.

2.5.1 Základné pravidlá pre písanie programov v assembleri

Text programu assembler je zvyčajne formátovaný v štyroch stĺpcoch. Môžeme povedať, že každý riadok pozostáva zo štyroch polí, a to: návestia, operácie, operandy, komentáre. Polia sú od seba oddelené znakmi tabulátora alebo medzerami.

Hlavnými poľami sú operácie a operandy. Platné operácie a ich syntax sú uvedené v tabuľke (1.4.2)

Štítok je symbolické označenie adresy príkazu. Všade sa namiesto označenia nahradí adresa príkazu, ktorému predchádza označenie. Najčastejšie sa tagy používajú v príkazoch riadiaceho prenosu. Každý štítok musí byť jedinečný a je voliteľný. Na rozdiel od mnohých iných verzií v assembleri RealView štítky nekončia dvojbodkou (":").

Komentáre sú voliteľne umiestnené na konci riadku a oddelené bodkočiarkou („;“).

Uveďme si jednoduchý príklad.

2.5.2 Pseudopríkazy

Assembler RealView podporuje takzvané pseudoinštrukcie. Pseudoinštrukcia je mnemotechnický zápis, ktorý v skutočnosti nezodpovedá inštrukčnej sade procesora, ale je nahradený jednou alebo (zriedkavo) niekoľkými inštrukciami. Pseudopríkazy sú akýmsi makrám a slúžia na zjednodušenie syntaxe. Zoznam podporovaných pseudopríkazov je uvedený v tabuľke (2.5.1).

2.5.3 Montážne smernice

Direktívy na rozdiel od príkazov nevytvárajú spustiteľný kód, ktorý sa načíta do pamäte mikrokontroléra. Direktívy sú len inštrukcie pre assembler, riadia tvorbu spustiteľného kódu.

Pozrime sa na často používané direktívy assembleru RealView 4.

Názov EQU Konštanta

Priraďuje konštante symbolické označenie Meno, ktoré sa stáva synonymom pre konštantu. Hlavným účelom je predstaviť názvy riadiacich registrov,

Názov OBLASTI, parametre

Definuje oblasť pamäte s daným názvom. Pomocou parametrov určíte účel oblasti pamäte, napríklad DATA (údaje) alebo CODE (kód). Adresy definovanej oblasti závisia od zvoleného cieľa. Oblasť CODE sa nachádza na adrese 0x00000000, oblasť DATA - na adrese 0x40000000. Program musí mať oblasť CODE s názvom RESET. Konštanty umiestnené v pamäti programu by mali byť deklarované v sekcii s dvojicou parametrov CODE, READONLY.

Označuje vstupný bod do programu, ukazuje jeho „začiatok“. Jedna takáto smernica musí byť v programe vždy prítomná. Zvyčajne sa umiestňuje hneď za príkazom AREA RESET, CODE.

Tabuľka 2.5.1 – Pseudoinštrukcie podporované assemblerom RealView 4

Mnemotechnický zápis

Prevádzka

Skutočná realizácia

a syntax

ADR (podmienka)

do registra

Pridanie alebo odčítanie konštanty od súčinnosti PC

Príkazy ADD alebo SUB

ADRL(podmienka)

do registra

Dvojité ADD alebo SUB zahŕňajúce PC

(rozšírený rozsah adries)

ASR(Podmienka)(S)

Aritmetický posun doprava

ASR(Podmienka)(S)

posunový operand

LDR (podmienka)

do registra

adresovanie (PC + okamžitý offset)

Umiestnenie konštanty

v pamäti programu

LDR(z indexovej adresy-

cie. PC slúži ako offset.

LSL (podmienené) (S)

Logický posun doľava

LSL (podmienené) (S)

posunový operand

LSR(podmienka) (S)

Logický posun doprava

LSR(podmienka) (S)

posunový operand

POP (podmienka)

Obnovte registre zo zásobníka

zotavenie

registrov

tím

LDMIA R13!, (...)

PUSH (Podmienka)

Zachovanie

registrov

tím

STMDB R13!, (...)

ROR(podmienené)(S)

Cyklický posun doprava

ROR(podmienené)(S)

posunový operand

RRX (podmienka) (S)

Prejdite na bicykli priamo

prevod o 1 číslicu

posunový operand

Názov SPACE Veľkosť

Rezervuje pamäť na ukladanie údajov danej veľkosti. Názov sa stáva synonymom adresy vyhradeného priestoru. Jednota adresného priestoru umožňuje použiť túto smernicu pre permanentnú aj RAM. Hlavným účelom je vytváranie globálnych premenných v RAM (v oblasti DATA).

Označenie DCB/DCW/DCD Konštantná

„Flash“ dáta (numerické konštanty) v pamäti programu. Štítok sa stáva synonymom adresy, na ktorú sa budú zaznamenávať údaje. Rôzne direktívy (DCB, DCW a DCD) slúžia pre dáta rôznych veľkostí: bajt, 16-bitové slovo, 32-bitové slovo (v tomto poradí).

Slúži ako znak konca súboru. Celý text za touto direktívou je ignorovaný assemblerom.

2.5.4 Makrá

Makro je preddefinovaný fragment programu, ktorý vykonáva nejakú bežnú operáciu. Na rozdiel od podprogramov volaných pomocou riadiacich prenosových príkazov, použitie makier neznižuje výkon, ale neznižuje spotrebu programovej pamäte. Pretože zakaždým, keď je zavolané makro, assembler vloží celý jeho text do programu.

Ak chcete deklarovať makro, použite nasledujúcu konštrukciu

$ Parameter1, $ Parameter2, ...

Parametre vám umožňujú upraviť text makra pri každom prístupe k nemu. Vo vnútri (v tele) makra sa parametre používajú aj s predchádzajúcim znakom „$“. Namiesto parametrov v tele makra sa nahradia parametre zadané počas volania.

Makro sa volá takto:

Názov Parameter1, Parameter2, ...

Je možné organizovať kontrolu stavu a vetvenie.

IF "$Parameter" == "Hodnota"

Upozorňujeme, že tento návrh nevedie k softvérovej kontrole stavu mikrokontrolérom. Stav je kontrolovaný assemblerom počas generovania spustiteľného kódu.