2.3 Számítógép-architektúra fogalmai: ISA, mikroarchitektúra, regiszter, busz

2.3.1 Miért kell különválasztani a szerződést és a megvalósítást?

Egy C-ben írt tömbfeldolgozó függvény ugyanazzal az eredménnyel futhat két különböző ARM processzoron, miközben az egyik lényegesen hamarabb végez. Mi biztosítja az azonos eredményt, és mi okozza az eltérő futási időt?

A választ két szinten keressük:

Ez mérnöki szempontból alapvető különbség. A program helyességét nem alapozhatjuk egy adott processzor véletlen belső tulajdonságára; a teljesítményét viszont nem jósolhatjuk meg pusztán az utasítások jelentéséből.

Előfeltétel a Számrendszerek és bitszintű reprezentáció anyaga. Az alfejezet minden konkrét gépi példája A64 ISA, AArch64, ARMv8-A alapprofil, little-endian környezetben. Cortex-M-utasítást itt nem használunk.

2.3.2 A tárolt program és a Von Neumann-modell

A tárolt program elve (stored-program concept) szerint az utasítások kódolt bitmintái memóriában tárolhatók, ugyanúgy, mint a feldolgozott adatok. A Von Neumann-modell közös memóriában kezeli a programot és az adatokat; a processzor ebből olvassa ki a végrehajtandó utasításokat.

A „program mint adat” nem azt jelenti, hogy minden adat végrehajtható. Azt jelenti, hogy egy utasításnak is van tárolható, másolható, fájlba írható reprezentációja. Az assembler szöveges utasításnevekből ilyen gépi kódot állít elő.

Ugyanazokat a bájtokat különböző eszközök másként értelmezhetik:

A tényleges végrehajthatóságot a memória-hozzáférési jogosultságok is korlátozzák. Egy írható adatlap nem feltétlenül végrehajtható.

A mai processzorok gyakran külön utasítás- és adatgyorsítótárat használnak. Ez nem cáfolja a tárolt program elvét: a programozói modell lehet közös címtérre épülő, miközben a belső adatút részben szétválik. Nem következik belőle az sem, hogy minden memóriaforgalom egyetlen fizikai vezetékkötegen halad.

2.3.3 Mi tartozik az ISA-szerződéshez?

Az ISA több, mint az utasításnevek listája. Meghatározza többek között:

A mikroarchitektúra ezzel szemben olyan döntéseket tartalmazhat, mint a végrehajtóegységek száma, a gyorsítótárak szervezése vagy az egyszerre feldolgozható utasítások mennyisége.

Kérdés Elsődleges szint
Mit jelent az A64 ADD W0, W1, #5? ISA
Az A64 W0 írása hogyan hat az A64 X0 felső bitjeire? ISA
Hány egész összeadás indítható egy órajelciklusban? Mikroarchitektúra
Mekkora egy adott processzor adatgyorsítótára? Konkrét megvalósítás
Mennyi idő alatt futott le a laborprogram? Mérés, meghatározott környezetben
Melyik szám jelöli Linux alatt a folyamatkilépést? Operációsrendszer-interfész, nem ISA

Az azonos ISA-t megvalósító processzoroknak az előírt architekturális hatásokat kell biztosítaniuk, nem azonos belső lépéseket vagy futási időt. A bináris kompatibilitáshoz ezenfelül a használt kiterjesztések, az operációs rendszer és a bináris interfész egyezése is szükséges.

2.3.4 Regiszterfájl, ALU, busz és memóriahierarchia

A regiszter (register) a processzoron belüli, névvel elérhető tárolóhely. Az utasítás közvetlenül megnevezheti, mely regiszterek tartalmával dolgozzon. A programozó által látható regiszterek együttese az architekturális regiszterkészlet.

A regiszterfájl (register file) a regiszterértékeket tároló és kiszolgáló belső szerkezet. Egyszerű modellben megfelel a látható regiszterkészletnek; összetettebb processzorban több fizikai tárolóhely is lehet mögötte. Ennek részleteit egyelőre nem kell ismernünk.

Az aritmetikai-logikai egység (arithmetic logic unit, ALU) például összeadást és bitenkénti logikai műveleteket végez. Nem minden utasítást ugyanaz az egység hajt végre: a memóriaeléréshez vagy lebegőpontos számításhoz külön egységek tartozhatnak.

A busz (bus) kommunikációs út, amelyen címek, adatok és vezérlési információk továbbíthatók. Mai rendszerekben gyakran összetett összeköttetési hálózat helyettesíti az egyszerű közös buszt. Szélessége nem szükségképpen egyezik a processzor regiszterszélességével.

A memóriahierarchia (memory hierarchy) eltérő kapacitású és elérési költségű tárolási szinteket kapcsol össze:

Szint Bevezető szerep
Regiszterek Az utasítások közvetlen operandusai
Gyorsítótárak, cache-ek Memóriatartalom gyorsan elérhető másolatai
Főmemória A futó program nagyobb adat- és kódterülete
Háttértár Tartós fájltárolás; nem közönséges regiszteroperandus-forrás

Az A64 load/store jellegű ISA: az itt használt egész összeadás regisztereken dolgozik. A memóriában lévő értéket előbb betöltjük, az eredményt pedig külön utasítással írjuk vissza.

2.3.5 Mit jelent a 32 és a 64 bites regiszternézet?

A szóméret (word size) kifejezés önmagában nem elég pontos: jelenthet természetes feldolgozási szélességet, de lehet dokumentációban rögzített adategység is. Az A64 terminológiájában a word 32 bites, a doubleword 64 bites mennyiség, noha az AArch64 általános célú regiszterei 64 bitesek.

A64-en az általános célú regiszterek X0–X30 neve a 64 bites nézetet jelöli; W0–W30 ugyanazon regiszterek alsó 32 bites nézete. Nem két független regiszterkészletről van szó.

Kiemelten fontos A64-szabály:

Egy Wn célregiszter írása nullázza a megfelelő Xn regiszter felső 32 bitjét.

Például, előjel nélküli olvasatban:

A64-állapot X1 teljes tartalma W1 nézet
Kezdetben minden bit egy 0xffffffffffffffff 0xffffffff
A W1 célregiszterbe 37 kerül 0x0000000000000025 0x00000025

Ez ISA-szerződés, nem egy adott processzor optimalizálása.

A 64 bites regiszter nem jelenti azt sem, hogy minden utasítás 64 bites kódolású: az A64 utasításai 32 bites kódolásúak. A regiszterszélességből továbbá nem következik, hogy minden lehetséges 64 bites cím használható. A C-típusok és a gépi nézetek megfeleltetésénél pedig a célplatform adatmodelljét is figyelembe kell venni.

2.3.6 Az elővételezés–dekódolás–végrehajtás modellje

Az utasítás-elővételezés–dekódolás–végrehajtás (fetch–decode–execute) ciklus fogalmi modell:

  1. Elővételezés: a processzor a programszámláló (program counter, A64 PC) által meghatározott helyről utasításkódot kér le. Ezt gyakran az utasítás-gyorsítótár szolgáltatja.
  2. Dekódolás: a bitmezők alapján felismeri a műveletet, a regiszteroperandusokat és az esetleges közvetlen értéket.
  3. Végrehajtás: kiszámítja az eredményt, címet képez vagy vezérlést ad át.
  4. Állapotfrissítés: az eredmény megjelenik a célregiszterben vagy memóriában, és kialakul a következő utasítás helye.

A64-ben szekvenciális haladáskor a következő utasítás címe 4 bájttal nagyobb; elágazás ezt megváltoztathatja.

Ez nem négy kötelező órajelciklus! Egy tényleges processzor finomabban bonthatja a munkát, és több utasítás különböző feldolgozási lépéseit átfedheti. Az architekturális eredménynek ettől még meg kell felelnie az ISA szabályainak.

2.3.7 Órajel, ciklus, késleltetés és átbocsátás

Az órajel (clock) időzítési referencia. Egy órajelciklus (clock cycle) időtartama:

T=1f T=\frac{1}{f}

ahol TT másodpercben értendő, ff pedig az órajelfrekvencia hertzben.

Elméleti, levezetett példa: állandó 2 GHz mellett egy ciklus 0,5 ns. Ha valamely végrehajtás pontosan 12 ciklust igényel, az ideje 6 ns. Ez nem állítás a későbbi labor futási idejéről.

Két további mennyiséget különítünk el:

Egy hipotetikus, futószalagos egység eredménye lehet 3 ciklus után kész, miközben független műveletet minden ciklusban fogadhat. Egymás eredményére váró műveleteknél ez az átfedés nem használható ki ugyanígy.

Az „egy utasítás egy ciklus” ezért hibás általánosítás. Egy utasítás több ciklusig tarthat, több utasítás feldolgozása átfedhet, memóriaeléréskor pedig várakozás keletkezhet. A QEMU user-mode futási ideje nem alakítható át a célprocesszor hardverhű ciklusszámává.

2.3.8 Kidolgozott példa: memóriából regiszterbe, majd vissza

Munkakönyvtár: ~/arm-labor/a64/a003/ — az alfejezet minden laborfájlja ide kerül, és minden parancs innen futtatandó (a könyvtárrendet a Labor-alapkörnyezet alfejezet írja le).

mkdir -p ~/arm-labor/a64/a003
cd ~/arm-labor/a64/a003

Szerepe a fejezetben: ez a példa célzott kiegészítés: egyetlen jelenséget mutat meg külön builddel, ezért első olvasáskor kihagyható. A fejezet végigvitt projektjének ide tartozó lépése — amely nem hagyható ki — a szakasz végén, a „Végigvitt projekt” részben található.

A labor feladata: egy memóriában tárolt, előjel nélküli 32 bites értékhez adjunk 5-öt, tároljuk az eredményt, majd adjuk vissza kilépési állapotként.

C-szinten a művelet eredmeny = bemenet + 5u. Most láthatóvá tesszük a mögötte álló adatmozgást is.

A példa előre használ néhány eszközlánc-elemet; telepítésüket a Labor-alapkörnyezet: eszközlánc telepítése és verifikáció tárgyalja. Nincs szükség fizikai ARM gépre.

Mentsük lab-isa.s néven:

// A64 ISA, ARMv8-A; GNU as, AArch64-cél; little-endian.
.section .text
.balign 4
.global _start
.type _start, %function

_start:
    adr     x2, lab_values
    movn    x1, #0
    ldr     w1, [x2]
    add     w0, w1, #5
    str     w0, [x2, #4]

lab_before_exit:
    movz    x8, #93
    svc     #0

.size _start, . - _start

.section .data
.balign 4
lab_values:
    .word   37
    .word   0

.section .note.GNU-stack,"",%progbits

A ponttal kezdődő sorok assemblerdirektívák, nem processzorutasítások. Ebben a GNU as, AArch64-célú példában a .word 32 bites adatot hoz létre, a .balign 4 pedig 4 bájtos igazítást kér. A címeket a linker rendezi el; nem használunk kézzel választott memóriacímeket.

Az A64-utasítások jelentése és releváns kódolási korlátai:

A64-utasítás Hatás és korlát
ADR X2, lab_values PC-relatív címet képez. Eltérése előjeles 21 bites bájteltérés: −1 MiB és +1 MiB − 1 bájt között.
MOVN X1, #0 A nullás közvetlen érték invertálásával minden bitet egyre állít. Az immediate 16 bites; 64 bites alakban eltolása 0, 16, 32 vagy 48 lehet, itt 0.
LDR W1, [X2] 4 bájtot tölt be. A használt unsigned-offset alak 12 bites, 4 bájttal skálázott eltérést enged; itt az eltérés 0.
ADD W0, W1, #5 32 bites összeadás. Az immediate 12 bites, opcionálisan 12 bites balra tolással; itt nincs eltolás.
STR W0, [X2, #4] 4 bájtot tárol a második adatmezőbe. Az eltérés 4 többszöröse, ebben az alakban 0–16380 bájt lehet.
MOVZ X8, #93 Nullázás mellett beírja a 16 bites immediate értékét; az eltolási lehetőségek azonosak a fenti 64 bites MOVN lehetőségeivel.
SVC #0 Supervisor-hívást kezdeményez; az immediate 16 bites. Linux/AArch64 alatt itt a rendszerhívási interfészt használjuk.

A Linux/AArch64 konvencióban az A64 X8 = 93 választja az exit rendszerhívást, az A64 X0 pedig az állapot argumentuma. Ez Linux-szabály: az A64 SVC #0 önmagában nem jelent „kilépést”.

2.3.8.1 Összeállítás, linkelés, futtatás

# Target: qemu-user, Linux/AArch64, little-endian.
# Host baseline: Ubuntu Server 24.04 LTS x86_64.

aarch64-linux-gnu-as -march=armv8-a -g -o lab-isa.o lab-isa.s
aarch64-linux-gnu-ld -e _start -o lab-isa lab-isa.o
aarch64-linux-gnu-objdump -d ./lab-isa

if qemu-aarch64 ./lab-isa; then
    lab_status=0
else
    lab_status=$?
fi
printf 'Kilépési állapot: %d\n' "$lab_status"

Várt, levezetett kimenet: Kilépési állapot: 42. A program nem ír szöveget; ezt a shell írja ki. A 42 itt szándékos, nem hibát jelző eredmény.

A64-en a lab_before_exit címke elérésekor:

Megfigyelhető állapot Várt érték, előjel nélküli olvasat
X1 0x0000000000000025
X0 0x000000000000002a
X2 A lab_values futásidejű címe
Első 32 bites adatmező 37
Második 32 bites adatmező 42

Little-endian tárolásnál a második mező bájtjai növekvő cím szerint: 0x2a, 0x00, 0x00, 0x00.

2.3.8.2 A köztes állapot ellenőrzése

Az első terminálban:

# Target: qemu-user, Linux/AArch64; GDB-kapcsolatra vár.
qemu-aarch64 -g 1234 ./lab-isa

A második terminálban:

# Target: az előző qemu-user folyamat AArch64 vendégállapota.
# A GDB kisbetűs nevei az A64 X0, X1, X2 regisztereket jelölik.
gdb-multiarch -q ./lab-isa \
    -ex 'target remote localhost:1234' \
    -ex 'break lab_before_exit' \
    -ex 'continue' \
    -ex 'info registers x0 x1 x2' \
    -ex 'x/2uw $x2' \
    -ex 'continue' \
    -ex 'quit'

A töréspont a kilépés előtt állítja meg a programot. Az x/2uw két, előjel nélküli decimális, 4 bájtos memóriaegységet jelenít meg; várt tartalmuk 37 és 42. A folytatás után a QEMU-folyamat várt kilépési állapota ismét 42.

2.3.8.3 Végigvitt projekt: a lab-hidden utasításai mint ISA-szerződés

A Miért assembly alfejezetben készült lab-hidden programon most az ISA-szerződés két elemét azonosítjuk: a rögzített utasításkódolást és a regiszternézetet. A vizsgálathoz elég a kész bináris; ugyanebben a könyvtárban dolgozunk:

# Hostoldali vizsgálat; a fájl célja AArch64 Linux, qemu-user.
cp ~/arm-labor/a64/a001/lab-hidden .
aarch64-linux-gnu-objdump -d --disassemble=lab_next_u lab-hidden

Levezetett, várt részlet (a címek a buildtől függenek):

<lab_next_u>:
  ...:  11000400    add w0, w0, #0x1
  ...:  d65f03c0    ret

Mit mutat ez az alfejezet fogalmaival?

2.3.9 Tipikus hibák és ellenpróbák

„Az A64 X1 felső fele biztosan megmarad.”
Hipotézis: az A64 W1 írása csak az alsó biteket módosítja. Ellenpróba: a program előbb minden bitet egyre állít, majd betölt az A64 W1 célregiszterbe. A GDB-ben látott nullás felső fél cáfolja a hipotézist.

„A fájlban még nulla van, tehát a tárolás nem működött.”
Az objdump az ELF-fájlt vizsgálja, nem a futó folyamat módosított adatmemóriáját. A futásidejű állapotot a GDB-ben ellenőrizzük.

„A 42 utasításszámot vagy ciklusszámot jelent.”
Nem: ez a kiszámított eredmény, amelyet kilépési állapotként továbbítunk. A helyességi teszt nem teljesítménymérés.

„Az ADR minden távolságot elér.”
Nem: a relatív tartomány korlátozott. Tartományhiba esetén a linker diagnosztikáját kell vizsgálni, nem találomra más címet beírni.

2.3.10 Feladatok

  1. (magyarázó, alapszint — cél: megértés-ellenőrzés) Sorold az ISA, mikroarchitektúra vagy mérési eredmény kategóriába: az A64 utasításhossz; az adatgyorsítótár mérete; az A64 W1 írásának hatása az A64 X1 felső felére; egy futás falióraideje.

  2. (implementációs, alapszint — cél: rutinépítés) Csak az összeadás közvetlen értékét módosítsd úgy, hogy a program 44-et adjon vissza. Építsd újra az előbbi parancsokkal, és ellenőrizd a második adatmezőt is.

  3. (hibakeresési, középszint — cél: rutinépítés) Kontrollált hibaként cseréld az A64 ADD utasításban az 5-öt 6-ra. Írd le a tünet–hipotézis–adatgyűjtés–javítás–ellenőrzés láncot. Használd az objdump és a GDB eredményét.

  4. (számítási, alapszint — cél: megértés-ellenőrzés) Egy hipotetikus végrehajtás 2 GHz állandó órajel mellett 20 ciklust vesz igénybe. Mennyi az ideje? Miért nem állapítható meg ebből az utasítások száma?

2.3.11 Megoldások

1. megoldás — Segítség. Melyik tulajdonságot garantálja MINDEN azonos ISA-t megvalósító processzor, és melyik változhat gyártónként vagy akár futtatásonként?

1. megoldás — Teljes megoldás. Az A64 utasításhossz és a regiszternézetek írási szabálya ISA-tulajdonság. A gyorsítótár mérete megvalósítási tulajdonság. A falióraidő mérési eredmény, amelyhez a környezetet is rögzíteni kell.

2. megoldás — Segítség. 37-hez mennyit kell adni, hogy 44 legyen? Fér-e ez az érték a 12 bites immediate mezőbe?

2. megoldás — Teljes megoldás. A módosított sor A64-környezetben: add w0, w1, #7. A 7 belefér a 12 bites immediate mezőbe. A várt eredmény 44, hexadecimálisan 0x2c; ezt várjuk a második adatmezőben és kilépési állapotként.

3. megoldás — Segítség. Melyik regiszter tartalmazza a hibás összeget — az A64 X1 (a betöltött bemenet) vagy az A64 X0 (az összeadás eredménye)? Innen indulj a hipotézisalkotásnál.

3. megoldás — Teljes megoldás. Tünet: 42 helyett 43. Hipotézis: hibás összeadandó. Az objdump mutatja a tényleges immediate értéket; a GDB-ben az A64 X1 továbbra is 37, az A64 X0 és a második adatmező viszont 43. Egyetlen javítás: 6 helyett 5. Újraépítés után mindkét ellenőrzésnek 42-t kell adnia.

4. megoldás — Segítség. Az órajelciklus-idő T=1/fT=1/f; helyettesítsd be f=2⋅109f = 2 \cdot 10^9 Hz-et, majd szorozd meg 20-szal. A ciklusszám és az utasításszám miért nem ugyanaz a mennyiség, ha a feldolgozás átfedhet?

4. megoldás — Teljes megoldás. Az elméleti idő 20/(2⋅109)20/(2\cdot10^9) másodperc, vagyis 10 ns. Az utasításszám nem következik a ciklusszámból, mert a feldolgozás átfedhet, illetve várakozásokat tartalmazhat.

2.3.12 Rövid összefoglaló

Az ISA azt rögzíti, milyen állapotváltozást kell a programnak megfigyelnie; a mikroarchitektúra azt határozza meg, hogyan jön létre ez az eredmény. A tárolt program utasításai memóriában reprezentált bitminták. A processzor elővételezi, dekódolja és végrehajtja őket, de ezek a fogalmi lépések nem rendelhetők automatikusan külön órajelciklusokhoz. A regiszterek közvetlen operandusokat tartanak, az ALU számol, az összeköttetések adatot továbbítanak. A labor külön betöltést, összeadást és tárolást mutatott, valamint ellenőrizhetővé tette az A64 32 bites regiszterírás nullakiterjesztő hatását. A helyes eredmény önmagában nem mondja meg a hardveres futási időt.

2.3.13 Források és ellenőrzési hiányok

A linkek és a dokumentumverziók közös állapota az Olvasói útmutató forrásállapot-táblájában van; itt csak az alfejezet saját megállapításai szerepelnek.

Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is ténylegesen fennmaradnak: laborfuttatás nem történt. A kimenetek levezetett várakozások. A hallgatói laborban rögzítendők az Ubuntu 24.04 környezet tényleges eszközverziói. Mikroarchitekturális késleltetési vagy átbocsátási adatot nem mértünk.