Az ARM processzor utasításkészletének tanulmányozása. Assembly School: ARM CPU-k összeállítási nyelve alprogramok, hurkok és feltételes utasítások

  • Dátum: 14.05.2021

1. A valós idejű óraszámlálót engedélyezni kell (1); Az órajelforrás kiválasztási bit törlődik (2), ha az óra nem a fő órától származik.

2. Egy vagy mindkét megszakítási esemény kiválasztó bitet (3) be kell állítani. És kiválasztásra kerül, hogy mely események indítsák el a megszakítási kérelmet (5).

3. A megszakítási eseménymaszkokat (4, 7) be kell állítani.

2.5 Az ARM7 programozása assemblerben

Az ARM7 utasításkészlet (1.4. szakasz) mindössze 45 utasítást tartalmaz, amelyek a címzési módszerek, feltételes mezők és módosítók sokfélesége miatt meglehetősen összetettek. Az assembly nyelvű program nehézkes és

Val vel nehezen olvasható. Ezért az assemblert ritkán használják az ARM7 architektúra programozásában.

A magas szintű C nyelv azonban sok építészeti jellemzőt rejt a programozó elől. A programozó gyakorlatilag nem érinti az olyan eljárásokat, mint a kernel mód kiválasztása, a memória lefoglalása a verem számára, és a megszakítások kezelése. Ezen eljárások megtanulásához hasznos legalább egy egyszerű assembly nyelvű programot írni.

Ezenkívül még C használata esetén is az assembly nyelvet kell használni.

1. Irányítani kell A C fordító, amely nyomon követi, hogy az optimalizálás során kizárta-e a fontos utasításokat, szükségtelennek tartja-e azokat. A fordító rendkívül nem hatékony kódot generál egy viszonylag egyszerű művelethez, az elégtelen optimalizálás miatt. Győződjön meg arról, hogy a fordító valóban azokat a hardver erőforrásokat használja, amelyek egy adott algoritmus hatékonyságának növelésére szolgálnak.

2. Hibák vagy kivételek okainak keresése közben (2.4.1. szakasz).

3. Teljesítmény vagy memóriafelhasználás szempontjából abszolút optimális kód beszerzése (2.2.20., 3.1.5. szakasz).

Tekintsük a program összeállításának alapvető technikáit assemblerben

Val vel a mikrokontroller által végrehajtott összes kód bemutatása, úgy, ahogy van, és közvetítés nélkül C fordító.

Az assembler alapú projekt létrehozásának folyamata majdnem ugyanaz, mint a C programok esetében (2.3.1–2.3.3 fejezet). Csak két kivétel van:

a) a *.S kiterjesztés hozzá van rendelve a forrásszövegfájlhoz;

b) itt feltételezzük, hogy a STARTUP.S fájl nem kapcsolódik a programhoz.

2.5.1 Alapvető szabályok az assemblerben történő programíráshoz

A program szövegét az assemblerben négy oszlopba szokás rendezni. Azt mondhatjuk, hogy minden sor négy mezőből áll, nevezetesen: címkék, műveletek, operandusok, megjegyzések mezői. A mezőket tabulátorok vagy szóközök választják el egymástól.

A fő mezők a műveletek és az operandusok. Az érvényes műveleteket és szintaxisukat a táblázat tartalmazza (1.4.2).

A címke egy parancs címének szimbolikus ábrázolása. A címke helyett mindenhol a címke előtti parancs címe lesz helyettesítve. A címkéket leggyakrabban a vezérlőátviteli parancsokban használják. Minden címkének egyedinek kell lennie, és nem kötelező. Sok más verziótól eltérően a RealView összeállításban a címkék nem végződnek kettősponttal (":").

A megjegyzéseket opcionálisan a sor végére kell helyezni, és pontosvesszővel ("; ") elválasztva.

Vegyünk egy egyszerű példát.

2.5.2 Álparancsok

A RealView assembler támogatja az úgynevezett pszeudo-parancsokat. A pszeudo-utasítás olyan mnemonikus jelölés, amely valójában nem felel meg a processzor utasításrendszerének, hanem egy vagy (ritkábban) több utasítás helyettesíti. A pszeudo-parancsok egyfajta makrók, és a szintaxis egyszerűsítését szolgálják. A támogatott pszeudo-parancsok listája a (2.5.1) táblázatban található.

2.5.3 Összeszerelési irányelvek

Az utasításokkal ellentétben az direktívák nem hoznak létre végrehajtható kódot, amely betöltődik a mikrokontroller memóriájába. A direktívák csak utasítások az assembler számára, irányítják a végrehajtható kód képzését.

Vessünk egy pillantást a gyakran használt RealView 4 assembler direktívákra.

EQU név állandó

A Név szimbolikus jelölőt rendeli az állandóhoz, amely az állandó szinonimája lesz. A fő cél az ellenőrzési regiszterek elnevezéseinek bevezetése,

TERÜLET neve, opciók

Meghatároz egy memóriaterületet a megadott névvel. A paraméterek határozzák meg a memóriaterület célját, például DATA (adat) vagy CODE (kód). A meghatározott terület címei a kiválasztott célállomástól függenek. A CODE terület a 0x00000000 címtől kezdve, a DATA terület a 0x40000000 címtől kezdődik. A programnak rendelkeznie kell egy RESET nevű CODE régióval. A programmemóriában lefoglalt állandókat egy CODE, READONLY paraméterpárral kell deklarálni.

Kijelöli a program belépési pontját, megmutatja a "kezdetét". Egy ilyen direktívának mindig jelen kell lennie a programban. Általában közvetlenül az AREA RESET, CODE utasítás után kerül elhelyezésre.

2.5.1. táblázat – A RealView 4 assembler által támogatott pszeudo-parancsok

Mnemonikus jelölés

Művelet

Tényleges megvalósítás

és szintaxis

ADR (feltétel)

regisztrálni

Konstans összeadása vagy kivonása egy PC-ből

ADD vagy SUB parancsok

ADRL(feltétel)

regisztrálni

Dupla ADD vagy SUB PC-vel

(bővített címtartomány)

ASR(feltétel)(S)

Aritmetikai jobbra eltolás

ASR(feltétel)(S)

shift operandus

LDR (feltétel)

regisztrálni

címzés (PC + közvetlen eltolás)

Állandó elhelyezése

programmemóriában

LDR(index címből-

ciója. Az eltolás PC.

LSL (feltétel) (S)

Logikai eltolás balra

LSL (feltétel) (S)

shift operandus

LSR(feltétel)(S)

Logikai jobbra váltás

LSR(feltétel)(S)

shift operandus

POP (konv.)

Regiszterek visszaállítása a veremből

Felépülés

regisztereket

csapat

LDMIA R13!,(...)

PUSH (feltétel)

Megőrzés

regisztereket

csapat

STMDB R13!,(...)

ROR(feltétel)(S)

Forgasd jobbra

ROR(feltétel)(S)

shift operandus

RRX(konv.)(S)

Forgatás jobbra

átvitel 1 számjegyűre

shift operandus

Név TÉR Méret

Memóriát tart fenn az adott méretű adatok tárolására. A név a lefoglalt hely címének szinonimájává válik. A címtér egysége lehetővé teszi, hogy ezt a direktívát mind állandóra, mind pedig számára használjuk véletlen hozzáférésű memória. A fő cél a globális változók létrehozása a RAM-ban (a DATA területen).

Címke DCB/DCW/DCD állandó

"Fűzd össze" az adatokat (numerikus állandókat) a programmemóriában. A címke szinonimája lesz annak a címnek, ahová az adatokat írják. A különböző méretű adatokhoz különböző direktívák (DCB , DCW és DCD ) használatosak: bájt, 16 bites szó, 32 bites szó (illetve).

Fájlvégi jelként szolgál. Az összes ilyen utasítás utáni szöveget figyelmen kívül hagyja az assembler.

2.5.4 Makrók

A makró egy program előre meghatározott része, amely valamilyen általános műveletet hajt végre. Ellentétben a vezérlőátviteli parancsokkal meghívott szubrutinokkal, a makrók használata nem csökkenti a teljesítményt, de nem csökkenti a programmemória fogyasztását. Mert minden makróhívásnál az assembler a teljes szövegét beágyazza a programba.

A következő konstrukció egy makró deklarálására szolgál

$Paraméter1, $Paraméter2, ...

A paraméterek lehetővé teszik a makró szövegének módosítását minden alkalommal, amikor meghívják. A makró belsejében (törzsben) a paraméterek az előző "$" jellel is használatosak. A makrótörzsben szereplő paraméterek helyett a hívás során megadott paraméterek kerülnek behelyettesítésre.

A makrót így hívják:

Név Paraméter1, Paraméter2, ...

Lehetőség van állapotellenőrzés, elágazás megszervezésére.

IF "$Parameter" == "Érték"

Kérjük, vegye figyelembe, hogy ez a kialakítás nem vezet a mikrovezérlő program általi állapotellenőrzéséhez. A feltételt az assembler ellenőrzi a futtatható kód kialakítása során.

Jelenleg még a meglehetősen egyszerű mikrokontrollerek programozásához is általában magas szintű nyelveket használnak, amelyek a C vagy C++ nyelv részhalmazai.

A processzorok architektúrájának és jellemzőinek tanulmányozásakor azonban célszerű assembler nyelveket használni, mivel csak ilyen megközelítéssel lehet a vizsgált architektúra jellemzőinek azonosítását biztosítani. Emiatt a további bemutatás az Assembly nyelven történik.

Mielőtt folytatná az ARM7 parancsok vizsgálatát, meg kell jegyezni a következő jellemzőket:

    Két utasításkészlet támogatása: ARM 32 bites utasításokkal és THUMB 16 bites utasításokkal. A következő egy 32 bites utasításkészlet, az ARM szó ehhez a formátumhoz tartozó utasításokat, az ARM7 szó pedig magát a CPU-t jelenti.

    Két 32 bites címformátum támogatása: kis-végi processzor és kis-végi processzor. Az első esetben a legjelentősebb bit (Most Significant Bit – MSB) a szó legkisebb jelentőségű bitjében, a második esetben pedig a legjelentősebb bitben található. Ez kompatibilitást biztosít a 32 bites processzorok többi családjával magas szintű nyelvek használatakor. Azonban számos ARM maggal rendelkező processzorcsaládban csak kis endiant használnak (azaz az MSB a cím legjelentősebb bitje), ami nagyban leegyszerűsíti a processzorral való munkát. Mivel az ARM7-hez használt fordítóprogram mindkét formátumú kóddal működik, ezért meg kell győződni arról, hogy a szóformátum helyesen van beállítva, különben a kapott kód "kifelé fordul".

    Különböző típusú eltolások végrehajtása az egyik operanduson "menet közben", mielőtt az ALU-ban használná

    Bármilyen parancs feltételes végrehajtásának támogatása

    Lehetőség a műveleti eredmények zászlóinak megváltoztatásának megtiltására.

      1. Feltételes parancsvégrehajtás

Az ARM utasításkészlet egyik fontos tulajdonsága, hogy támogatja bármely utasítás feltételes végrehajtását. A hagyományos mikrokontrollerekben az egyetlen feltételes utasítás a feltételes ugrás, és talán számos más, például az egyes bitek állapotának ellenőrzésére vagy megváltoztatására vonatkozó utasítások. Az ARM utasításkészletben az utasításkód felső 4 bitje mindig a CPSR regiszter feltételjelzőivel van összehasonlítva. Ha értékeik nem egyeznek, a visszafejtési szakaszban lévő parancsot a NOP (no operation) parancs váltja fel.

Ez jelentősen csökkenti a program "rövid" átmenetekkel rendelkező szakaszainak végrehajtási idejét. Így például, ha másodfokú egyenleteket oldunk meg valós együtthatóval és tetszőleges gyökekkel negatív diszkriminánssal, a négyzetgyök kiszámítása előtt meg kell változtatni a diszkrimináns előjelét, és az eredményt a válasz képzeletbeli részéhez kell rendelni.

A probléma hagyományos megoldásában egy feltételes ugrás parancsot kell megadni. Ennek a parancsnak a végrehajtása legalább 2 ciklust vesz igénybe - dekódolás és az új cím értékének betöltése a programszámlálóba, valamint további ciklusok száma a parancsfolyamat betöltéséhez. Ha feltételes parancsvégrehajtást használ pozitív diszkriminánssal, az előjelváltoztatási parancsot egy üres művelet helyettesíti. Ebben az esetben az utasítás csővezeték nem törlődik, és a veszteségek legfeljebb egy órajelet jelentenek. Az a küszöb, amelynél a feltételes utasítások NOP utasítással való helyettesítése hatékonyabb, mint a hagyományos feltételes elágazási utasítások végrehajtása és az ehhez kapcsolódó csővezeték újratöltése, megegyezik a mélységével, pl. három.

A funkció megvalósításához hozzá kell adnia a feltételjelzők tesztállapotait meghatározó tizenhat előtag bármelyikét az assembler utasítások (és a C) alapvető mnemonikus jelöléséhez. Ezeket az előtagokat a táblázat tartalmazza. 3. Ennek megfelelően minden csapat számára 16 lehetőség van. Például a következő parancs:

MOVEQ R1, #0x008

azt jelenti, hogy a 0x00800000 szám betöltése az R1 regiszterbe csak akkor történik meg, ha az utolsó adatfeldolgozási parancs eredménye „egyenlő” volt, vagy 0 eredmény érkezett és a CPSR regiszter jelzője (Z) ennek megfelelően van beállítva.

3. táblázat

Parancs előtagok

Jelentése

Z telepítve

Z leesett

Telepítve

Nagyobb vagy egyenlő (előjel nélküli)

C reset

Lent (aláíratlan)

N telepítve

Negatív eredmény

N leesett

Pozitív eredmény vagy 0

V telepítve

Túlcsordulás

V leesett

Nincs túlcsordulás

Telepítve,

Z leesett

Fent (jel nélkül)

leesett,

Z telepítve

Kisebb vagy egyenlő (előjel nélküli)

Nagyobb vagy egyenlő (előjeles)

N nem egyenlő V-vel

Kevesebb, mint (aláírva)

Z visszaállítás ÉS

(N egyenlő V)

Továbbiak (aláírva)

Z értéke VAGY

(N nem egyenlő V-vel)

Kisebb vagy egyenlő (jelű)

(figyelmen kívül hagyva)

Feltétel nélküli végrehajtás

Először KAR meglehetősen szokatlan assembler (ha újra tanulsz x86, MCS51 vagy AVR). De meglehetősen egyszerű és logikus felépítése van, így gyorsan felszívódik.

Nagyon kevés az orosz nyelvű dokumentáció az összeszerelő számára. Azt tudom tanácsolni, hogy látogass el 2 linkre (talán találsz még többet és elmondod? Hálás leszek.):
Az ARM család RISC processzorainak architektúrája és utasításkészlete - http://www.gaw.ru/html.cgi/txt/doc/micros/arm/index.htm
Az ARM assembler megértése, a GBA ASM sorozatból, Mike H, ford. Aquila - http://wasm.ru/series.php?sid=21.

Az utolsó link nagyon sokat segített, eloszlatta a ködöt =). A második dolog, ami jól tud segíteni, furcsa módon a C fordító IAR Embedded Workbench for ARM(a továbbiakban egyszerűen IAR EW ARM). A helyzet az, hogy ősidők óta képes (mint minden önmagát tisztelő fordítóprogram) a C-kódot assembler kódba fordítani, amit viszont az IAR assembler ugyanolyan könnyen lefordít objektumkódba. Ezért nincs is jobb, mint a legegyszerűbb függvényt C-ben megírni, assemblerbe fordítani, és azonnal kiderül, hogy melyik assembler parancs mit csinál, hogyan adják át az argumentumokat és hogyan adják vissza az eredményt. Két legyet ölsz egy csapásra - tanulj assembly nyelvet, és közben kapsz információt az assembly kód beépítéséről egy C projektbe. Tanulmányoztam a CRC16 számláló funkciót, és ennek eredményeként kaptam egy teljes értékű verziót assemblerben.

Itt van az eredeti C függvény (u16 azt jelenti, hogy unsigned short, u32 azt jelenti, hogy unsigned int, u8 azt jelenti, hogy unsigned char):
// crc16.c fájl
u16 CRC16 (void* databuf, u32 méret)
{
u16 tmpWord, crc16, idx;
u8bitCnt;
#define CRC_POLY 0x1021;

crc16 = 0;
idx=0;
míg (méret!=0)
{
/* x vagy add hozzá a crc16 magas bájtját és a bemeneti bájtot */
tmpWord = (crc16>>8) ^ (*(((u8*)databuf)+idx));
/* írja be az eredményt a crc16 magas bájtjába */
tmpWord<<= 8;
crc16=tmpWord+(0x00FF&crc16);
for (bitCnt=8;bitCnt!=0;bitCnt--)
{
/* ellenőrizze a CRC-akkumulátor legjelentősebb bitjét */
if (crc16 és 0x8000)
{
crc16<<= 1;
crc16 ^=CRC_POLY;
}
más
crc16<<= 1;
}
idx++;
méret--;
}
return crc16;
}

Az IAR EW ARM assembler kód generálása nagyon egyszerű. A projekthez hozzáadott crc16.c fájl beállításainál bejelöltem a négyzetet Az örökölt beállítások felülbírálása, majd a Lista lapon tegyen 3 jelölőnégyzetet - kimeneti assembler fájl, Tartalmazza a forrástés Tartalmazza a híváskeret-információkat(bár az utolsó jelölőnégyzet valószínűleg kihagyható - egy csomó szükségtelent generál CFI-irányelvek). A fordítás után megkaptuk a projekt_mappa \ ewp \ at91sam7x256_sram \ List \ crc16.s fájlt. Ez a fájl ugyanolyan egyszerűen hozzáadható a projekthez, mint egy C-fájl (jól lefordítható).

Természetesen amikor a C-fordítóba becsúsztattam a C-kód egy vágatlan változatát, akkora assembler listát adott, hogy nem értettem belőle semmit. De amikor egy kivételével az összes C-operátort kidobtam a funkcióból, egyértelműbb lett. Aztán lépésről lépésre hozzáadtam a C-operátorokat, és a végén mi történt:

; crc16.s fájl
NÉV crc16
NYILVÁNOS CRC16

CRC_POLY EQU 0x1021

SZEKCIÓ ".szöveg":CODE:NOROOT(2)
KAR

// u16 CRC16 (void* databuf, u32 méret)
;R0 - visszatérési eredmény, CRC16
;R1 - méret paraméter
;R2 - databuf paraméter (az R0 bejáratánál volt)
;R3, R12 - ideiglenes regiszterek

CRC16:
PUSH (R3, R12); véletlenszerűen kiderült, hogy R3 és R13 menteni kell
; nem szükséges. De úgy döntöttem, hogy minden esetre spórolok
; esemény.
MOVS R2,R0 ;most R2==adatbuf
MOV R3,#+0
MOVS R0,R3 ;crc16 = 0
CRC16_LOOP:
CMP R1, #+0 ;minden bájt feldolgozva (méret==0)?
BEQ CRC16_RETURN ; ha igen, lépjen ki
LSR R3, R0, #+8;R3 = crc16>>8
LDRB R12, ;R12 = *adatf
EOR R3, R3, R12 ;R3 = *databuf ^ HIGH (crc16)
LSL R3, R3, #+8;R3<<= 8 (tmpWord <<= 8)
ÉS R0, R0, #+255 ;crc16 &= 0x00FF
ADD R0, R0, R3 ;crc16 = tmpWord + (0x00FF & crc16)
MOV R12, #+8 ;bitCnt = 8
CRC16_BIT_LOOP:
BEQ CRC16_NEXT_BYTE ;bitCnt == 0?
TST R0,#0x8000 ;Még nincs minden bit feldolgozva.
BEQ CRC16_BIT15ZERO ;Ellenőrizze a crc16 legjelentősebb bitjét.
LSL R0,R0,#+1 ;crc16<<= 1
MOV R3, #+(LOW(CRC_POLY)) ;crc16 ^= CRC_POLY
ORR R3,R3,#+(MAGAS(CRC_POLY)<< 8) ;
EOR R0,R3,R0 ;
B CRC16_NEXT_BIT

CRC16_BIT15ZERO:
LSL R0,R0,#+1 ;crc16<<= 1
CRC16_NEXT_BIT:
SUBS R12,R12,#+1 ;bitCnt--
B CRC16_BIT_LOOP ;

CRC16_NEXT_BYTE:
ADD R2,R2,#+1 ;databuf++
SUBS R1,R1,#+1;méret--
B CRC16_LOOP ;hurok az összes bájton

CRC16_RETURN:
POP (R3, R12) ; regiszterek visszaállítása
BX LR ;szubrutin kilépés, R0==crc16

Az IAR C fordítója meglepően jó kódot produkál. Nem nagyon sikerült optimalizálnom. Csak egy extra ideiglenes regisztert dobott ki, amit a fordító használni akart (valamiért az LR-t vette extra ideiglenes regiszternek, bár elég volt az R3 és az R12), valamint kivett pár plusz parancsot, ami ellenőrzi a számlálókat és beállítja a flageket (egyszerűen az S utótag hozzáadása a szükséges parancsokhoz).

Létrehoztunk tehát egy új projektet, elvégeztük az alapbeállításokat, létrehoztunk és a projekthez csatlakoztattunk egy fájlt, amibe egy egyszerű programot akarunk írni assemblerben.

Mi a következő lépés? Valójában a Cortex-M3 mag által támogatott thumb-2 utasításkészlettel is írhat programot. A támogatott parancsok listája és leírása a dokumentumban található Cortex-M3 Általános felhasználói kézikönyv(fejezet A Cortex-M3 utasításkészlet), amely a Keil uVision 5 projektmenedzser Könyvek lapján található. A hüvelykujj-2 parancsokkal kapcsolatos részleteket a cikk következő részeinek egyikében írjuk le, de most beszéljünk az STM32 programjairól. Tábornok.

Mint minden más assembler program, az STM32 programja is olyan parancsokból és pszeudo-parancsokból áll, amelyek közvetlenül gépi kódokká lesznek lefordítva, valamint különféle direktívákból, amelyeket nem fordítanak le gépi kódokká, hanem szolgáltatási célokra használják (programjelölés, karakterkonstansnevek hozzárendelése stb.)

Például egy speciális direktíva lehetővé teszi egy program külön szakaszokra bontását - TERÜLET. A következő szintaxissal rendelkezik: AREA Section_Name (, type) (, attr) ..., ahol:

  1. Szakasz_neve- szakasz neve.
  2. típus- szakasz típusa. Az adatokat tartalmazó szakasznál meg kell adni a DATA típust, a parancsokat tartalmazó szakasznál pedig a CODE típust.
  3. attr- további attribútumok. Például az readonly vagy readwrite attribútumok azt jelzik, hogy a szakaszt melyik memóriába kell elhelyezni, az align=0..31 attribútum azt határozza meg, hogy a szakaszt hogyan kell igazítani a memóriában, a noinit attribútum pedig olyan memóriaterületek lefoglalására szolgál, amelyekre nincs szükség inicializálni vagy nullára inicializálni (ennek az attribútumnak a használatakor nem kell megadni a szakasz típusát, mivel csak adatszelvényekhez használható).

Irányelv EQU valószínűleg mindenki számára ismerős, mivel minden assemblerben megtalálható, és szimbolikus nevek hozzárendelésére szolgál különféle állandókhoz, memóriacellákhoz stb. A következő szintaxissal rendelkezik: Név EQU számés közli a fordítóval, hogy az összes szimbólum találkozott Név számmal kell helyettesíteni szám. Mondjuk, ha mint szám memóriacella címét használjuk, akkor ezt a cellát a jövőben nem a cím alapján lehet elérni, hanem a megfelelő szimbolikus jelöléssel ( Név).

Irányelv KAP fájl név nevű fájlból szöveget szúr be a programba fájl név. Ez analóg az AVR assembler include direktívájával. Használható például arra, hogy a különböző regiszterekhez szimbolikus neveket rendelő direktívákat külön fájlba helyezzünk. Azaz minden névadási hozzárendelést külön fájlba helyezünk, majd ahhoz, hogy ezeket a szimbolikus neveket használni tudjuk a programban, egyszerűen csak beépítjük a GET direktívával ezt a fájlt a programunkba.

Természetesen a fent felsoroltakon kívül van még egy csomó különféle irányelv, amelyek teljes listája a fejezetben található. Irányelvek hivatkozása dokumentum Az Assembler használati útmutatója, amely a Keil uVision 5-ben a következő elérési úton található: tab Könyvek projektvezető -> Eszközök használati útmutatója -> A Felhasználói kézikönyv teljes kiválasztása -> Az Assembler használati útmutatója.

A legtöbb parancs, pszeudo-parancs és direktíva egy programban a következő szintaxissal rendelkezik:

(címke) SZIMBÓLUM (kifejezés) (,kifejezés) (,kifejezés) (; megjegyzés)

(címke) - címke. Erre azért van szükség, hogy meg tudjuk határozni az ezt a címkét követő parancs címét. A címke nem kötelező elem, és csak akkor használatos, ha tudnia kell egy parancs címét (például, hogy erre a parancsra ugorjon). A címke előtt nem lehet szóköz (azaz a sor legelső pozíciójában kell kezdődnie), és a címke neve csak betűvel kezdődhet.

A SYMBOL egy parancs, pszeudo-parancs vagy direktíva. A parancsnak, a címkével ellentétben, a sor elejétől kell lennie némi behúzással, még akkor is, ha előtte nincs címke.

(kifejezés) (,kifejezés) (,kifejezés) — operandusok (regiszterek, konstansok…)

; - határoló. Az elválasztó utáni sorban lévő összes szöveg megjegyzésként kezelendő.

Nos, most, ahogy ígértem, a legegyszerűbb program:

TERÜLET START , KÓD , OLVASÁS dcd 0x20000400 dcd Program_start BELÉPÉS Program_start b Program_start END

AREA START, CODE, READONLY dcd 0x20000400 dcd Program_start BEJEGYZÉS Program_start b Program_start VÉGE

Ebben a programban csak egy szakaszunk van, ez a START. Ez a szakasz a flash memóriában van lefoglalva (mivel a csak olvasható attribútumot használják hozzá).

Ennek a szakasznak az első 4 bájtja a verem tetejének címét tartalmazza (esetünkben 0x20000400), a második 4 bájt pedig a belépési pont (a végrehajtható kód eleje) címét tartalmazza. Ezután maga a kód következik. A legegyszerűbb példánkban a végrehajtható kód egyetlen parancsból áll, amely feltétel nélkül a Program_start címkére ugrik, vagyis ugyanazt a parancsot hajtja végre.

Mivel a flashben csak egy szakasz van, ezért a programunk scatter fájljában First_Section_Name néven pontosan meg kell adnunk a nevét (azaz START).

Ebben az esetben kevert adatok és parancsok vannak. A verem tetejének címét és a belépési pont (adat) címét dcd direktívák segítségével írjuk közvetlenül a kódszekcióba. Persze lehet így is írni, de nem túl szépen. Különösen, ha leírjuk a megszakítások és kivételek teljes táblázatát (ami elég hosszú lesz), és nem csak a visszaállítási vektort. Sokkal szebb, ha nem zsúfoljuk össze a kódot felesleges adatokkal, hanem a megszakítási vektor táblát külön szekcióba, vagy még jobb esetben külön fájlba helyezzük. Hasonlóképpen, a verem inicializálása elhelyezhető egy külön szakaszban vagy akár egy fájlban. Például mindent külön szakaszokba fogunk helyezni:

TERÜLET VEREM, NOINIT, OLVASÁSI TERÜLET 0x400 ; 400 bájt kihagyása Stack_top ; és címke AREA RESET, DATA, READONLY dcd Stack_top ; címke címe Stack_top dcd Program_start ; címke címe Program_start TERÜLET PROGRAM, KÓD, CSAK OLVASÁSI BEJEGYZÉS ; belépési pont (a futtatható kód eleje) Program_start ; program start marker b Program_start END

Nos, itt van, ugyanaz a program (ami még mindig nem csinál semmi hasznosat), de most sokkal világosabbnak tűnik. A program szórófájljában meg kell adnia a RESET nevet First_Section_Name néven, hogy ez a szakasz legyen először a flash memóriában.

A CISC processzorok egy utasításban meglehetősen összetett műveleteket hajtanak végre, beleértve a memóriacellák tartalmával végzett aritmetikai és logikai műveleteket is. A CPU CISC utasításai különböző hosszúságúak lehetnek.

Ezzel szemben a RISC-nek viszonylag egyszerű utasításkészlete van, egyértelmű felosztással a művelet típusa szerint:

  • a memóriával való munka (olvasás a memóriából a regiszterekbe vagy írás a regiszterekből a memóriába),
  • adatfeldolgozás a regiszterekben (aritmetikai, logikai, adateltolás balra/jobbra vagy bitforgatás a regiszterben),
  • utasítások feltételes vagy feltétel nélküli ugráshoz más címekre.

Általános szabály, hogy (de nem mindig, és csak akkor, ha a programkód a vezérlő gyorsítótárába kerül) egy processzorciklusban egy utasítás kerül végrehajtásra. Az ARM processzor utasításhossza rögzített - 4 bájt (egy számítógépes szó). Valójában a modern ARM processzorok más üzemmódokra válthatnak, például THUMB módra, amikor az utasítások hossza 2 bájt lesz. Ez lehetővé teszi a kód kompaktabbá tételét. Ebben a cikkben azonban nem térünk ki erre a módra, mivel az Amber ARM v2a processzor nem támogatja. Ugyanebből az okból kifolyólag nem vesszük figyelembe az olyan módokat, mint a Jazelle (Java kód végrehajtására optimalizálva), és nem vesszük figyelembe a NEON parancsokat - a több adaton végzett műveletek parancsait. Ennek ellenére egy tiszta ARM utasításkészletet tanulmányozunk.

ARM processzor regiszterek.

Az ARM processzor több regiszterkészlettel rendelkezik, amelyekből jelenleg csak 16 áll a programozó rendelkezésére. Ezek a működési módok a következők:

  • alkalmazás mód (USR, felhasználói mód),
  • felügyelő mód vagy operációs rendszer mód (SVC, felügyelő mód),
  • megszakításkezelési mód (IRQ, interrupt mode) és
  • feldolgozási mód "sürgős megszakítás" (FIRQ, gyors megszakítási mód).

Azaz például megszakítás esetén a processzor maga megy a megszakításkezelő program címére, és automatikusan „átkapcsol” a regiszterbankok között.

A régebbi ARM processzorok a fenti működési módokon kívül további módokkal is rendelkeznek:

  • Megszakítás (memória-hozzáférési kivételek kezelésére szolgál),
  • Undefined (a társprocesszor programozott megvalósítására szolgál) és
  • Az operációs rendszer privilegizált feladatmódja Rendszer.

Az Amber ARM v2a processzor nem rendelkezik ezzel a további három móddal.

Amber ARM v2a esetén a regiszterkészlet a következőképpen ábrázolható:

Az r0-r7 regiszterek minden módban azonosak.
Az r8-r12 regiszterek csak az USR, SVC, IRQ módokban általánosak.
Az r13 regiszter a veremmutató. Ő minden módban van.
r14 regiszter - az alprogramból származó visszatérési regiszter is minden módban saját.
Az r15 regiszter a végrehajtható parancsokra mutató mutató. Minden módban közös.

Látható, hogy a FIRQ mód a legszigeteltebb, ennek van a legtöbb saját regisztere. Ez azért történik, hogy néhány nagyon kritikus megszakítás feldolgozható legyen anélkül, hogy a regisztereket a verembe mentené, anélkül, hogy erre időt pazarolnánk.

Különös figyelmet kell fordítani az r15 regiszterre, más néven pc (Program Counter) - a végrehajtható parancsokra mutató mutató. Tartalmán különféle aritmetikai és logikai műveleteket lehet végrehajtani, ezáltal a programvégrehajtás más címekre vált át. Azonban az Amber rendszerben megvalósított ARM v2a processzor esetében van néhány finomság a regiszter bitjeinek értelmezésében.

A tény az, hogy ebben a processzorban az r15 (pc) regiszterben a végrehajtható parancsokra mutató tényleges mutató mellett a következő információkat is tartalmazza:

31:28 bitek - egy aritmetikai vagy logikai művelet eredményének zászlói
27. bitek - Megszakítási IRQ maszk, a megszakítások le vannak tiltva, ha a bit be van állítva.
26. bitek - FIRQ megszakítási maszk, a gyors megszakítások letiltva vannak, ha a bit be van állítva.
25:2 bitek - maga a programutasításokra mutató mutató csak 26 bitet foglal el.
1:0 bitek - a processzor aktuális üzemmódja.
3-Felügyelő
2- Megszakítás
1-Gyors megszakítás
0 - felhasználó

A régebbi ARM processzorokban minden jelző és szolgáltatásbit külön regiszterekben található. A program aktuális állapotának nyilvántartása( cpsr ) és Saved Program Status Register ( spsr ), amelyekhez külön speciális parancsok érhetők el. Ez a programok elérhető címterének bővítése érdekében történik.

Az ARM assembler elsajátításának egyik nehézsége egyes regiszterek alternatív nevei. Tehát, mint fentebb említettük, az r15 ugyanaz a számítógép. Van még r13 - ez ugyanaz az sp (Stack Pointer), r14 az lr (Link Register) - az eljárásból származó visszatérési cím regisztere. Ettől eltekintve az r12 ugyanaz az ip (Intra-Procedure -call scratch register), amelyet a C fordítók speciális módon használnak a verem paramétereinek eléréséhez. Az ilyen alternatív elnevezések néha zavaróak, ha valaki más programkódjába nézünk – ezek és ezek a regisztermegjelölések is vannak.

A kódvégrehajtás jellemzői.

Számos processzortípusban (például x86) csak egy másik programcímre való ugrás hajtható végre feltétel alapján. Az ARM esetében ez nem így van. Minden ARM processzor utasítás végrehajtható vagy nem hajtható végre feltétel szerint. Ez lehetővé teszi a programugrások számának minimalizálását, és ezáltal a processzor folyamatának (pipeline) hatékonyabb felhasználását.

Végül is mi az a csővezeték? A programkódból most egy processzorutasítást kérünk le, az előzőt már dekódoljuk, az előzőt pedig már végrehajtjuk. Ez a 3-fokozatú Amber A23 processzor csővezeték esetében van így, amelyet projektünkben a Mars Rover2Rover2 kártyához használunk. Az Amber A25 processzor módosítása 5 fokozatú pipeline, még hatékonyabb. De van egy nagy DE. Az ugrásos utasítások arra kényszerítik a processzort, hogy ürítse ki a csővezetéket, és töltse fel. Így egy új parancs kerül kiválasztásra, de még nincs mit dekódolni, és még kevésbé semmi, amit azonnal végre kell hajtani. A kódvégrehajtás hatékonysága csökken a gyakori átmenetekkel. A modern processzorok mindenféle elágazás-előrejelző mechanizmussal rendelkeznek, amelyek valahogy optimalizálják a csővezeték kitöltését, de ez a mi processzorunkban nem így van. Mindenesetre az ARM bölcsen tette lehetővé az egyes utasítások feltételes végrehajtását.

Egy ARM processzorban bármilyen típusú utasításban az utasítás végrehajtási feltétel négy bitje az utasításkód felső négy bitjébe van kódolva:

4 állapotjelző van a processzorban:
. Negatív - a művelet eredménye negatív,
. Nulla - az eredmény nulla,
. Carry - előjel nélküli számokkal végzett művelet végrehajtása során átvitel történt,
. oTúlcsordulás - előjeles számokkal végzett művelet végrehajtása során túlcsordulás történt, az eredmény nem kerül be a regiszterbe)

Ez a 4 zászló számos lehetséges feltétel-kombinációt alkot:

A kód Utótag Jelentése Zászlók
4"h0 ekv Egyenlő Z készlet
4"h1 ne nem egyenlő Z tiszta
4"h2 cs/hs Carry set / unsigned magasabb vagy azonos Cset
4"h3 cc/lo Hordja tiszta / előjel nélküli alsó C tiszta
4"h4 mi mínusz/negatív N készlet
4"h5 pl Plusz/pozitív vagy nulla N tiszta
4"h6 ellen túlcsordulás V készlet
4"h7 vc nincs túlcsordulás V tiszta
4"h8 Szia Előjel nélküli magasabb C set és Z tiszta
4"h9 ls Előjel nélküli alsó vagy ugyanaz C tiszta vagy Z készlet
4" ha ge Nagyobb vagy egyenlő előjellel N == V
4"hb lt Aláírva kevesebb, mint N = V
4"hc gt Aláírva nagyobb, mint Z == 0, N == V
4" hd le Kisebb vagy egyenlő aláírva Z == 1 vagy N != V
4" ő al mindig (feltétel nélkül)
4"hf - érvénytelen állapot

Ebből az ARM processzor utasítások megtanulásának másik nehézsége következik - az utasításkódhoz hozzáadható sok utótag. Például az összeadás, feltéve, hogy a Z jelző be van állítva, az addeq parancs add + eq utótag. Ugrás a szubrutinra, ha az N=0 jelző blpl bl + pl utótag.

Zászlók (Negatív, nulla, hordozás, túlcsordulás) ez nem mindig van beállítva az aritmetikai vagy logikai műveletek során, mint mondjuk egy x86-os processzornál, hanem csak akkor, amikor a programozó akarja. Ehhez van egy másik utótagja a parancsmnemonikának: "s" (a parancskódban a 20-as bit kódolja). Így az add parancs nem változtatja meg a zászlókat, de az add parancs módosítja a jelzőket. És lehet egy feltételes összeadás parancs is, amely megváltoztatja a zászlókat. Például: addgts . Nyilvánvaló, hogy a különböző feltételes végrehajtási utótagokkal és beállítási jelzőkkel rendelkező parancsnevek lehetséges kombinációinak száma nagyon különlegessé és nehezen olvashatóvá teszi az ARM processzor összeállítás kódját. Idővel azonban megszokja, és elkezdi megérteni ezt a szöveget.

Aritmetikai és logikai műveletek (adatfeldolgozás).

Az ARM processzor különféle aritmetikai és logikai műveleteket tud végrehajtani.

A tényleges négybites műveleti kódot (Opcode) a processzor utasításbitjei tartalmazzák.

A regiszter és az úgynevezett shifter_operand tartalmával bármilyen műveletet végrehajtunk. A művelet eredménye egy regiszterbe kerül. A négybites Rn és Rd regiszterindexek a processzor aktív bankjában.

Az I. bittől függően a 25 shifter_operand vagy numerikus konstansként, vagy az operandus második regiszterének indexeként, sőt a második operandus értékére vonatkozó shift műveletként is kezelhető.

Az assembler parancsok egyszerű példái például így néznek ki:

add hozzá az r0,r1,r2 @ r0 regiszterbe az r1 és r2 regiszterek értékeinek összegét
sub r5,r4,#7 @ különbséget (r4-7) ír be az r5 regiszterbe

Az elvégzett műveletek kódolása a következő:

4"h0 és Logikai ÉS Rd:= Rn ÉS shifter_operand
4"h1 eor XOR Rd:= Rn XOR shifter_operand
4"h2 al Aritmetikai kivonás Rd:= Rn - shifter_operand
4"h3 rsb Aritmetikai vissza kivonás Rd:= shifter_operand - Rn
4"h4 add Aritmetikai összeadás Rd:= Rn + shifter_operand
4"h5 adc Aritmetikai összeadás plusz átviteli jelző Rd:= Rn + shifter_operand + Carry Flag
4"h6 sbc hordozó aritmetikai kivonás Rd:= Rn - shifter_operand - NOT (Carry Flag)
4"h7 rsc hordozó aritmetikai fordított kivonás Rd:= shifter_operand - Rn - NOT (Carry Flag)
4"h8 tst Logikai ÉS, de az eredmény mentése nélkül csak az Rn ÉS shifter_operand S bit mindig beállított jelzői módosulnak
4"h9 teq Logikai exkluzív VAGY, de az eredmény tárolása nélkül csak az Rn EOR shifter_operand jelzők módosulnak
S bit mindig beállítva
4 "ha cmp Összehasonlítás, vagy inkább aritmetikai kivonás az eredmény tárolása nélkül, csak az Rn flagek változnak - shifter_operand S bit mindig beállítva
4 "hb cmn Inverz, vagy inkább aritmetikai összeadás összehasonlítása az eredmény emlékezése nélkül, csak az Rn + shifter_operand S bit mindig beállított jelzői módosulnak
4"hc orr Logikai VAGY Rd:= Rn VAGY shifter_operand
4" hd mov érték másolása Rd:= shifter_operand (nincs első operandus)
4"he bic Reset bits Rd:= Rn AND NOT(shifter_operand)
4"hf mvn Inverz érték másolása Rd:= NEM shifter_operand (nincs első operandus)

hordó váltó.

Az ARM processzor egy speciális „hordóváltó” sémával rendelkezik, amely lehetővé teszi, hogy az egyik operandust adott számú bittel eltolja vagy elforgatja bármilyen aritmetikai vagy logikai művelet előtt. Ez a processzor egy meglehetősen érdekes funkciója, amely lehetővé teszi nagyon hatékony kód létrehozását.

Például:

@ A 9-cel való szorzás egy szám 8-cal való szorzása
@ balra tolással 3 bit plusz egy másik szám
add hozzá r0, r1, r1, lsl #3 @ r0= r1+(r1<<3) = r1*9

A @ 15-tel való szorzás 16-tal való szorzás mínusz a szám
rsb r0, r1, r1, lsl #4 @ r0= (r1<<4)-r1 = r1*15

@ hozzáférés egy 4 bájtos szavakat tartalmazó táblázathoz, ahol
@r1 a tábla alapcíme
@r2 az elem indexe a táblázatban
ldr r0,

Az lsl logikai balra eltoláson kívül van még egy logikai jobbra eltolás lsr és egy aritmetikai jobbra eltolás asr (jelmegőrző eltolás, a legjelentősebb bitet balról szorozzuk az eltolással egyidejűleg).

A ror bitek is forognak - a biteket jobbra, a kinyomottakat pedig balra tolják.
Egy bites eltolás van a C jelzőn keresztül - ez az rrx parancs. A regiszter értéke egy bittel jobbra tolódik. A bal oldalon a C jelzőt a regiszter felső bitjébe töltjük be

Az eltolást nem egy fix számállandó, hanem a harmadik regiszter-operandus értéke hajthatja végre. Például:

összeadjuk r0, r1, r1, lsr r3 @ értéke r0 = r1 + (r1>>r3);
összeadjuk r0, r0, r1, lsr r3 @ értéke r0 = r0 + (r1>>r3);

Tehát a shifter_operand az, amit az assembler utasításokban írunk le, például "r1, lsr r3" vagy "r2, lsl #5".

A legérdekesebb dolog az, hogy a műszakváltások használata nem kerül semmibe. Ezek az eltolások (általában) nem igényelnek extra órajelet, és nagyon jók a rendszer teljesítményéhez.

Numerikus operandusok használata.

Az aritmetikai vagy logikai műveletek második operandusként nemcsak a regiszter tartalmát, hanem egy numerikus állandót is használhatnak.

Sajnos itt van egy fontos korlát. Mivel minden parancs 4 bájt (32 bit) fix hosszúságú, nem lehet benne „bármilyen” számot kódolni. A műveleti kódban 4 bitet már elfoglal a végrehajtási feltétel kódja (Cond), 4 bitet magának a műveleti kódnak (Opcode), majd 4 bitet - az Rd vevőregisztert, és további 4 bitet - az első regisztert. Rn operandus, plusz különböző I 25 jelzők (csak numerikus állandót jelöl a műveleti kódban) és S 20 (jelzők beállítása a művelet után). Összességében csak 12 bit marad egy lehetséges konstanshoz, az úgynevezett shifter_operandhoz – ezt láttuk fent. Mivel 12 bit csak szűk tartományban tud számokat kódolni, az ARM processzor fejlesztői úgy döntöttek, hogy a konstanst a következőképpen kódolják. A shifter_operand tizenkét bitje két részre oszlik: a négybites forgatási indexre encode_imm és a tényleges nyolcbites imm_8 numerikus értékre.

Az ARM processzoron a konstans egy 8 bites szám egy 32 bites számon belül, páros számú bittel jobbra elforgatva. Azaz:

imm_32 = imm_8 ROR (encode_imm *2)

Elég okosnak bizonyult. Kiderült, hogy nem minden számállandó használható az assembler parancsokban.

Tudsz írni

add hozzá r0, r2, #255 @ decimális állandó
add hozzá az r0, r3, #0xFF @ állandót hexadecimálisan

mivel a 255 a 8 bites tartományba esik. Ezek a parancsok a következőképpen lesznek összeállítva:

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

És még írni is tudsz

add hozzá r0, r4, #512
add hozzá r0, r5, 0x650000

A lefordított kód így fog kinézni:

0: e2840c02 add r0, r4, #512 ; 0x200
4: e2850865 add r0, r5, #6619136 ; 0x650000

Ebben az esetben maga az 512-es szám természetesen nem fér bele egy bájtba. De másrészt elképzeljük, hogy hexadecimális formában 32'h00000200, és azt látjuk, hogy 24 bittel (1 ror 24) jobbra van fordítva. A forgatási együttható kétszer kisebb, mint 24, azaz 12. Így kiderül, shifter_operand = ( 4'hc , 8'h02 ) - ez a parancs tizenkét legkisebb jelentőségű bitje. Ugyanez a helyzet a 0x650000 számmal. Ehhez shifter_operand = ( 4'h8, 8'h65 ).

Egyértelmű, hogy írni nem lehet

add hozzá r0, r1,#1234567

vagy nem tudsz írni

mov r0, #511

mivel itt a szám nem ábrázolható imm_8-ként és encode_imm - az elforgatási tényező. Az assembler fordítója hibát fog dobni.

Mi a teendő, ha egy konstans nem kódolható közvetlenül a shifter_operand-ba? Mindenféle trükköt kell csinálnod.
Például először betöltheti az 512-es számot egy szabad regiszterbe, majd kivonhat egyet:

mov r0, #511
al r0,r0,#1

A második módja annak, hogy egy adott számot betöltsünk egy regiszterbe, az, hogy kiolvassuk egy speciálisan lefoglalt változóból a memóriában:

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

Az írás legegyszerűbb módja a következő:

ldr r2,=511

Ebben az esetben (figyelje meg a "=" jelet), ha a konstans imm_8 és encode_imm formátumban ábrázolható, ha beilleszthető egy 12 bites shifter_operandba, akkor az assembler fordító automatikusan lefordítja az ldr-t egy mov utasításba. De ha a szám nem ábrázolható így, akkor a fordító maga lefoglal egy memóriacellát ennek a konstansnak a programban, és maga ad nevet ennek a memóriacellának, és lefordítja a parancsot az ldr -be.

Íme, amit írtam:

ldr r7,my_var
ldr r8,=511
ldr r8,=1024
ldr r9,=0x3456
........
Saját_var: .word 0x123456

Összeállítás után ezt kaptam:

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

Vegye figyelembe, hogy a fordító a pc regiszterhez (más néven r15) képest memóriacímzést használ.

Memóriacella beolvasása és regiszter írása a memóriába.

Ahogy fentebb is írtam, az ARM processzor csak aritmetikai vagy logikai műveleteket tud végrehajtani a regiszterek tartalmával. A műveletekhez szükséges adatokat ki kell olvasni a memóriából, és a műveletek eredményét vissza kell írni a memóriába. Vannak erre speciális parancsok: ldr (valószínűleg a “LoaD Register” kombinációból) az olvasáshoz és str (valószínűleg “ STore Register”) az íráshoz.

Úgy tűnik, hogy csak két csapat van, de valójában sok változatuk van. Elég megnézni az Amber ARM processzor ldr /str parancsainak kódolási módjait, hogy lássuk, hány L 20 , W 21 , B 22 , U 23 , P 24 , I 25 kiegészítő jelzőbit van - és ezek határozzák meg a konkrét a parancs viselkedése:

  • Az L 20 bit határozza meg, hogy írjon vagy olvasson. 1 - ldr , olvasás, 0 - str , írás.
  • A B 22 bit határozza meg, hogy 32 bites szót vagy 8 bites bájtot kell-e olvasni/írni. 1 byte műveletet jelent. Amikor egy bájtot beolvasunk egy regiszterbe, a regiszter felső bitjei nullára kerülnek.
  • Az I 25. bit az Eltolás mező használatát határozza meg. Ha I 25 ==0, akkor az Offset numerikus eltolásként értelmeződik, amelyet vagy hozzáadni kell a báziscímhez a regiszterből, vagy ki kell vonni. De az összeadás vagy kivonás az U 23 bittől függ.

(Cond) - a művelet végrehajtásának feltétele. Értelmezése ugyanúgy történik, mint a logikai/aritmetikai parancsok esetében - az olvasás vagy az írás lehet feltételes.

Így az assembler szövegbe valami ilyesmit írhat:

ldr r1, @ az r1 regiszterbe beolvassa a címen lévő szót az r0 regiszterből
ldrb r1, @ az r1 regiszterbe beolvassa a bájtot a címen az r0 regiszterből
ldreq r2, @ feltételes szóolvasás
ldrgtb r2, @ feltételes bájt beolvasása
ldr r3, @ beolvassa a szót a 8-as címen az r4 regiszterben lévő címhez képest
ldr r4, @ beolvassa a szót a -16 címen az r5 regiszterben lévő címhez képest

A szöveg összeállítása után láthatja a parancsok tényleges kódjait:

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

A fenti példában csak az ldr -t használom, de az str is nagyjából ugyanúgy használatos.

Vannak módok az indexelés előtti és az indexelés utáni visszaírási memória elérésére. Ezekben az üzemmódokban a memória-hozzáférési mutató az utasítás végrehajtása előtt vagy után frissül. Ha ismeri a C programozási nyelvet, akkor ismeri a mutató hozzáférési konstrukciókat, mint például ( *psource++;) vagy ( a=*++psource;). Az ARM processzorban ez a memóriaelérési mód éppen csak megvalósul. Az olvasási parancs végrehajtásakor két regiszter egyszerre frissül – a vevőregiszter fogadja a memóriából kiolvasott értéket, és a memóriacella regisztermutatójában lévő érték előre vagy hátra mozog.

Véleményem szerint ezeknek a parancsoknak az írása némileg logikátlan. Sok időbe telik megszokni.

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

Az első ldr parancs először növeli a mutatót, majd beolvassa. A második parancs először beolvassa, majd növeli a mutatót. A psrc mutató értéke az r0 regiszterben található.

A fenti példák mindegyike arra az esetre vonatkozott, amikor a parancskód I 25. bitje visszaállt. De még telepíthető! Ekkor az Offset mező értéke nem egy numerikus állandót fog tartalmazni, hanem a műveletben érintett harmadik regisztert. Sőt, a harmadik regiszter értéke még előre tolható!

Példák a lehetséges kódváltozatokra:

0: e7921003 ldr r1, @ beolvasási cím - az r2 és r3 regiszterekből származó értékek összege
4: e7b21003 ldr r1, ! @ ugyanaz, de az r2 beolvasása után az r3 értékével növekszik
8: e6932004 ldr r2, , r4 @ először r3-at olvas be, majd az r3 értéke r4-gyel nő
c: e7943185 ldr r3, @ r4+r5*8 ​​cím olvasása
10: e7b43285 ldr r3, ! @ cím az r4+r5*32 olvasásához, az r4 beolvasása után ennek a címnek az értékére lesz beállítva
14: e69431a5 ldr r3, , r5, lsr #3 @ beolvassa az r4 címet, az r4 parancs végrehajtása után az r4+r5/8 értékre lesz állítva

Ezek az olvasási/írási parancsok változatai az ARM v2a processzorban.

Az ARM processzorok régebbi modelljeiben az utasítások sokfélesége még nagyobb.
Ez annak köszönhető, hogy a processzor például nem csak szavak (32 bites számok) és bájtok olvasását teszi lehetővé, hanem félszavak (16 bit, 2 bájt) olvasását is. Ezután a "h" utótag a félszó szóból hozzáadódik az ldr / str parancsokhoz. A parancsok így néznek ki: ldrh vagy strh . Vannak olyan parancsok is, amelyek előjeles számként értelmezik az ldrsh félszavakat vagy ldrsb bájtokat. Ezekben az esetekben a betöltött sávszó vagy bájt legjelentősebb bitje a célregiszterben a teljes szó legjelentősebb bitjére szorzódik. Például a 0xff25 félszó ldrsh paranccsal való betöltése 0xffffff25-öt eredményez a célregiszterben.

Többszöri olvasás és írás.

Nem az ldr /str parancsok az egyedüliek a memória elérésére. Az ARM processzornak vannak olyan utasításai is, amelyek lehetővé teszik a blokkátvitel végrehajtását - egyszerre több regiszterbe töltheti be a memóriából több egymást követő szó tartalmát. Lehetőség van arra is, hogy több regiszter értékeit egymás után írjuk a memóriába.

A blokkátviteli parancsok megjegyzései az ldm gyökérrel (LoaD Multiple) vagy az stm-vel (Többszörös tárolása) kezdődik. De aztán, ahogy az ARM-ben lenni szokott, a történet utótagokkal kezdődik.

Általában a parancs így néz ki:

op(kond)(mód) Rd(, {Register list} !}

Az utótag (Cond) érthető, ez a parancs végrehajtásának feltétele. Az utótag (mód) az átviteli mód, erről később. Az Rd egy olyan regiszter, amely megadja az olvasáshoz vagy íráshoz szükséges alapcímet a memóriában. Az Rd regiszter utáni felkiáltójel azt jelzi, hogy az olvasási/írási művelet után módosul. A memóriából betöltött vagy a memóriába kirakott regiszterek listája (Register lista) .

A regiszterek listája kapcsos zárójelben van megadva, vesszővel elválasztva vagy tartományként. Például:

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

A memóriába írás nem megfelelő sorrendben történik. A lista egyszerűen jelzi, hogy mely regiszterek kerülnek a memóriába, és ennyi. A parancskódban 16 bit van fenntartva a Regiszterlista számára, akárcsak a processzorbank regisztereinek száma. Ebben a mezőben minden bit jelzi, hogy melyik regiszter vesz részt a műveletben.

Most az olvasási/írási mód. Van hol összezavarodni. A helyzet az, hogy ugyanahhoz a művelethez különböző módnevek használhatók.

Ha tesz egy kis lírai kitérőt, akkor beszélnie kell ... a veremről. A verem egy módja a LIFO típusú adatok elérésének - Last In First Out (wiki) - Last in, First Out. A verem széles körben használatos a programozásban eljárások meghívásakor és a regiszterek állapotának elmentésekor a függvények bejáratánál, illetve kilépéskor visszaállítva, valamint paraméterek átadásakor a meghívott eljárásoknak.

A halom a memóriában, ki gondolta volna, négyféle.

Az első típus a Full Descending . Ekkor a veremmutató egy foglalt veremelemre mutat, és a verem a csökkenő címek irányába növekszik. Ha egy szót kell a verembe helyezni, akkor először a veremmutató csökken (Decrement Before), majd a szó a veremmutató címére kerül. Ha el kell távolítania egy számítógépes szót a veremből, akkor a szó a veremmutató aktuális értékén kerül beolvasásra, majd a mutató felfelé mozog (Increment After).

A második típus a Full Ascending. A verem nem lefelé, hanem felfelé nő, a nagy címek felé. A mutató a foglalt elemre is mutat. Ha egy szót kell a verembe tenni, akkor először a veremmutató növekszik, majd a szót a mutató írja (Increment Before). Amikor ki kell venni a veremből, először a veremmutatóval olvasunk, mert az a foglalt elemre mutat, majd a veremmutatót csökkentjük (Decrement After).

A harmadik típus az Empty Descending . A verem lefelé növekszik, mint a Full Descending esetén, de a különbség az, hogy a veremmutató egy üres cellára mutat. Így amikor egy szót kell a verembe tenni, azonnal rögzítésre kerül, majd a veremmutató csökken (Decrement After). A veremből való eltávolításkor a mutató először növekszik, majd beolvasásra kerül (Increment Before).

A negyedik típus az Empty Ascending . Remélem, minden világos - a verem nő. A veremmutató egy üres elemre mutat. A verem megnyomásával egy szót kell írni a veremmutató címére, és növelni kell a veremmutatót (Increment After ). Nyújtsa ki a veremből - csökkentse a veremmutatót, és olvassa el a szót (Csökkentse az előtt).

Így a veremmel végzett műveletek során a verem típusától függően növelni vagy csökkenteni kell a mutatót - (Növekedés / Csökkentés ) a memória olvasása / írása előtt vagy után (Előtte / Utána). Az Intel processzorok például speciális parancsokkal dolgoznak a veremmel, mint például PUSH (szót rak a verembe) vagy POP (szó kiemelése a veremből). Az ARM processzorban nincsenek speciális parancsok, de az ldm és az stm parancsok használatosak.

Ha az ARM processzor utasításaival valósítja meg a veremet, akkor a következő képet kapja:

Miért kellett ugyanannak a csapatnak különböző neveket adni? Egyáltalán nem értem... Itt persze meg kell jegyezni, hogy az ARM stack szabványa továbbra is Full Descending.

Az ARM processzor veremmutatója az sp vagy r13 regiszter. Ez általában egy ilyen megállapodás. Természetesen az stm írása vagy az ldm olvasása más alapregiszterekkel is megoldható. Emlékeztetni kell azonban arra, hogy az sp regiszter miben különbözik a többi regisztertől - eltérő lehet a processzorok különböző üzemmódjaiban (USR, SVC, IRQ, FIRQ), mert saját regiszter bankjaik vannak.

És még egy megjegyzés. Írjon az ARM összeállítási kódba egy hasonló sort push (r0-r3), Természetesen lehet. De valójában ugyanaz a parancs lesz stmfd sp!,(r0-r3).

Végül adok egy példát az assembly kódra és a lefordított szétszedett szövegére. Nekünk van:


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

@ez a három utasítás ugyanaz, és ugyanazt csinálja
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)

Összeállítás után kapjuk:

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, pc)

Átmenetek a programokban.

A programozás nem lehetséges átmenetek nélkül. Bármely programban van kód ciklikus végrehajtása és eljárások, függvények hívásai, van kódszakaszok feltételes végrehajtása is.

Az Amber ARM v2a processzorban csak két parancs található: b (a Branch szóból - elágazás, átmenet) és bl (Branch with Link - átmenet a visszatérési cím elmentésével).

A parancs szintaxisa nagyon egyszerű:

b(cond) címke
bl(cond) címke

Nyilvánvaló, hogy bármilyen átmenet lehet feltételes, vagyis a programban előfordulhatnak olyan furcsa szavak, amelyek a "b" és a "bl" gyökökből és a feltétel utótagokból (Cond) alakultak:

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

A változatosság elképesztő, nem?

Az ugrási utasítás 24 bites eltolást tartalmaz. Az ugráscímet a pc mutató aktuális értékének és az eltolási számnak 2 bittel balra eltolt összegeként számítjuk ki, előjeles számként értelmezve:

Új pc = pc + Offset*4

Így az ugrási tartomány 32 MB előre vagy hátra.

Nézzük meg, mi az az elágazás, amely a bl visszaküldési címet menti. Ez a parancs szubrutinok hívására szolgál. Ennek az utasításnak egy érdekessége, hogy az eljárás visszaküldési címe az eljárás meghívásakor nem a veremben van tárolva, mint az Intel processzoroknál, hanem a szokásos r14 regiszterben. Ezután az eljárásból való visszatéréshez nincs szükség speciális ret parancsra, mint ugyanazon Intel processzorok esetében, hanem egyszerűen visszamásolhatja az r14 értékét a számítógépre. Most már világos, hogy az r14 regiszternek miért van alternatív neve lr (Link Register).

Tekintsük az Amber SoC hello-world projektjének outbyte rutinját.

000004a0<_outbyte>:
4a0: e59f1454 ldr r1, ; 8fc< адрес регистра данных UART >
4a4: e59f3454 ldr r3, ; 900< адрес регистра статуса UART >
4a8: e5932000 ldr r2, ; olvassa el az aktuális állapotot
4ac: e2022020 és r2, r2, #32
4b0: e3520000 cmp r2, #0 ; ellenőrizze, hogy az UART nem foglalt-e
4b4: 05c10000 strbeq r0, ; csak akkor írjon karaktert az UART-ba, ha az nem foglalt
4b8: 01b0f00e movseq pc, lr ; feltételes visszatérés az eljárásból, ha az UART nem volt foglalt
4bc: 1affff9 bne 4a8<_outbyte+0x8>; hurok az UART állapotának ellenőrzéséhez

Úgy gondolom, hogy ennek a töredéknek a megjegyzéseiből egyértelműen kiderül, hogyan működik ez az eljárás.

Egy másik fontos megjegyzés az átmenetekkel kapcsolatban. Az r15 regiszter (pc) használható normál aritmetikai vagy logikai műveletekben célregiszterként. Tehát egy olyan parancs, mint az add pc,pc,#8, elég utasítás egy másik címre ugráshoz.

Még egy megjegyzést kell tenni az átmenetekkel kapcsolatban. A régebbi ARM processzorok további bx, blx és blj ugrási utasításokkal is rendelkeznek. Ezek a parancsok a kódrészletekre való ugráshoz egy másik parancsrendszerrel. A Bx /blx lehetővé teszi az ARM processzorok 16 bites THUMB kódjára való átállást. A Blj a Jazelle parancsrendszer eljáráshívása (Java nyelv támogatása az ARM processzorokban). Az Amber ARM v2a nem rendelkezik ezekkel a parancsokkal.