Z80 Assembler

Hofmanné Boskovitz Éva
1985 - LSI Alkalmazástechnikai Tanácsadó Szolgála

Tartalom

Előszó

I. Bevezetés

II. A programfejlesztés rendszertechnikai eszközei
1. Firmware
2. Üzemmódok, parancsok
3. Operációs rendszerek
4. A gépi kód- assembly nyelv- magasszintű nyelv
5. Programbelövés fázisai és elemei
6. Az assembler

III. Utasítások, programok
1. Adatmozgatás
2. A programszámláló
3. Ciklus, szubrutin, stack
4 . Inkrementálás; dekrementálás; ugrások
5. Összehasonlítás, képernyő- és klaviatúra kezelés
6. Logikai utasítások, adatláncra vonatkozó műveletek
7. Bitet állító és léptető utasítások
8. Aritmetikai műveletek
9. Egyéb utasítások

IV. Input-output műveletek
1. Adatátviteli módok
2. Z80 busz
3. A megszakítás
4. A Z80 I/O utasításai
5. Az átviteli lánc elemeinek kapcsolata
6. Programozási példa fejletlen operációs rendszer esetén
7. Perifériamozgatás CP/M alatt



V. Példarutinok

1. ASCII kód - tömörített kód átalakítás
2. Bináris szám ASCII kód átalakítás
3. Sorbarendezés
4. Bináris számok szorzásas
5. Bináris számok osztása
6. Binárisan kódolt decimális (BCD) számok összeadása
7. BCD számok komplemensképzése
8. Lebegőpontos számábrázolás
9. Lebegőpontos számok összeadása
10. Lebegőpontos számok szorzása, osztása

VI. Adatkezelés
1. Fizikai és logikai adategységek
2. File-struktúrák, hozzáférési módok
3. File-kezelés CP/M alatt

Táblázatok, listák
ASD mnemonikájú utasítások szintaktikája
Direktívák
A CP/M beépített funkciói

Előszó

Magyarországon is terjedőben van a mini- és mikroszámítógépek használata. 1984. elején a nálunk forgalomba kerülő gépek közül egyre több épül a ZILOG cég által gyártott Z80 típusú, vagy ennek a licence alapján készült processzorra. És mivel mindig az assembly nyelven irt programnak, van esélye arra, hogy egy feladatot a legkisebb tárterület felhasználás mellett a leggyorsabban oldjon meg, indokoltnak látszik egy, a Z80 assembly programozásával foglalkozó könyv megjelentetése.
Problémát okoz azonban az, hogy a különböző gépek nagyon eltérő software-ellátottsággal bírnak. Ezért döntöttünk úgy, hogy könyvünkben a programozási példák bemutatása során a - nálunk - legelterjedtebb három esetet vesszük figyelembe:

A könyv felépítésénél hangsúlyozottan figyelembe vettük az iteráció elvét. Az egyes fejezetek fokozatosan épülnek egymásra, egy-egy rész olyan ismeretanyagot igyekszik átfogni, amely teljes lehetőséget ad a tárgykörbe tartozó feladatok megoldására. Erre a magra épül fel aztán a következő szint. A könyvet éppen ezért a Z80 mikroprocesszorral foglalkozó software-seken kívül szinte mindenkinek figyelmébe ajáljuk: softwares-eknek, akik még nem dolgoztak Z80-as rendszeren, mikroprocesszorral foglalkozó hardware szakembereknek, akik a Z80-nal még nem dolgoztak és olyan olvasókra is figyelemmel igyekeztünk lenni, akiknek hobbyja lett a számítógép, de egyelőre még kezdők ezen az érdekes területen.

I. Bevezetés

A számítógépek felépítését a tervezők gyakran a következő séma szerint szervezik:

 

1.
A processzor az az egység, mely felelős a program által előirt feladat végrehajtásáért. A program és az adatok a memóriában helyezkednek el. A processzor fő feladatai egy utasítás ciklusban:

A processzor feladata az is, hogy törődjék a buszon keresztül hozzáforduló perifériákkal, az egyes perifériáknak hozzáférést biztosítson a memóriához, lehetővé tegye számukra a busz használatát, valamint az egyes perifériákhoz tartózó programok futását.
A Z80 processzor ezen feladatok végrehajtásához a következő software által elérhető részegységeket tartalmazza:

A A' akkumulátor
F F' flag-regiszter
B B' általános
célú
regiszterek
C C'
D D'
E E'
H H'
L L'

A 8 bites regiszterek AF, BC, DE, HL párosításban 16 bitessé kapcsolhatók össze.
A fenti regiszterek vesszős párjukkal duplikálva vannak oly módon, hogy mindig az egyik regiszter-csoport áll elől, műveletet csak ezekkel lehet végezni. A sorrendet azonban meg lehet cserélni, vagyis a jelenlegi első csoportot hátra, a hátsót előre helyezni. Vesszősnek mindig a pillanatnyilag hátul álló regiszter-csoportot tekintjük.

IX, IY - index regiszterek,
SP - stack pointer,
PC - programszámláló (program counter)

Két 8 bites regiszternek speciális célja van, mellyel később foglalkozunk:

I - interrupt regiszter,
R - memória frissítés regisztere.

A programozás során kitüntetett szerep jut a flag-regiszternek (F). Ez is 8 bites, de software szempontból csak hat bitje hozzáférhető.

D7             D0
S Z X H X P/V N CY

A flageket (jelzőbiteket) az utasítások, ill. a műveletek eredményei állítják be igaz vagy hamis értékre, a programozó közülük négyet tesztelhet (CY, P/V, Z, S):

A H és az N flageket a programozó nem változtathatja meg, csak indirekt módon olvasni tudja. Ezeket a DAA utasítás használja (ld. később).

2.
A memória tárolja a futó programot és a futáshoz szükséges adatokat. A processzor mindig innen olvassa be a következő utasítást, vagy adatot, ide teszi vissza az eredményt. A memóriából kérnek, illetve oda adnak adatot a perifériák is, hol közvetlenül, hol a processzoron keresztül, közvetett módon. A Z80 8 bites (1 byte-os) és 16 bites (szavas) adatok feldolgozását képes végrehajtani. Byte-szervezésű, max. 64 kbyte-os memória címzésére alkalmas közvetlenül a processzor. A memória egy rekeszének tartalma 8 bit, a rekesz sorszáma 16 bit (4 hexadecimális jegy).
A memóriákat két csoportba oszthatjuk abból a szempontból, hogy a beléjük írt tartalmat mi módon lehet megváltoztatni:

3.
A perifériák adatot alakítanak át az

Kitüntetett szerep jut az ún. háttértáraknak (mágnesszalag, mágneslemez stb.), melyek feladata, hogy az adatokat megőrizzék, tárolják.

II. A programfejlesztés rendszertechnikai eszközei

1. Firmware
A gép bekapcsolása után a felhasználni memóriaterület üres (vagy véletlenszerű információ van benne), nem tartalmaz olyan programot, amely fel tudná venni a felhasználóval a kapcsolatot. Ezért mikrogépeknél alkalmaznak egy ún. firmware-t. Firmware-nek a ROM-okból felépített memóriaterületre irt programot hívjuk. Ez a program

2. Üzemmódok, parancsok
A gép bekapcsolása után közvetlenül a firmware szólal meg, és ún. parancs-módba állítja a gépet. Ebben az üzemmódban a gép alapparancsokat tud végrehajtani, melyek bevitelét a billentyűzetről várja, minden gép (valamilyen formában) legalább a következő három parancsot ismeri. (Rendszerint elég a parancs egy-két betűjét kiírni, a teljes parancsszó kiírása nem kötelező.):

Ezeket a parancsokat paraméterezni is kell, pl. meg kell adni, hogy a LOAD melyik programot töltse be, és honnan (kazettáról vagy diszkről); hogy a SAVE melyik memóriacímtől kezdje el a mentést és melyik perifériára, vagy hogy hol van az a program a memóriában, amelyet el kell indítani, stb.
Néhány gép ennél több alapparancsot is ismer, pl.:

A másik mód, (nevezzük AUTO-nak) mikrogépeknél rendszerint egy magasszintű programnyelv nevével (jelenleg leginkább BASIC) jelzett mód. Ilyenkor a firmware-nek az a része dolgozik, mely alkalmas ilyen nyelvű programok futtatására. A gép bekapcsoláskor PARANCS módba kerül. Parancs-módban a begépelt utasítások azonnal végrehajtódnak. Ha sorszámmal kezdődik a begépelt sor, akkor azt az interpreter tárolja. A GO vagy RUN (futás) parancs hatására kerül AUTO módba.

Fejlettebb rendszereknél az AUTO-mód bonyolultabb is lehet, ugyanis a felhasználói programok feldolgozása egy vezérlő programrendszer, az operációs rendszer (oporation system) "alatt", annak a vezérletével, felügyeletével is történhet. A memória egy része párhuzamos módon duplikálva van, tehát ugyanazzal a címmel található ROM terület is, és RAM terület is. Ilyen rendszer esetén a hálózati bekapcsolás után a munka általában a következő séma szerint történik:

  1. Közvetlenül a bekapcsolás után a firmware elvégzi a hardware kezdeti feltöltését (inicializálását), majd felveszi a kapcsolatot a felhasználóval. Ha - akár programból, akár klaviatúráról - a párhuzamos területet címezzük, akkor olvasás a ROM-ból írás a RAM-ba történik.
  2. A firmware a kezelő parancsára betölti egy háttértárról az operációs rendszert, amely - mivel írás - a RAM területre töltődik. Ezután a ROM "lekapcsolódik", most már csak a RAM terület funkciónál, tehát az esetleges olvasás (pl. pufferellenőrzés) is a RAM-területet fogja címezni.
  3. Most már AUTO-módban az operációs rendszer vezérli tovább a munkát.

3. Operációs rendszerek
Az operációs rendszer (Operation system: op.sys.) olyan vezérlőprogram, amelynek fő feladatai a következők:

Az ember-gép kapcsolat a következő módon valósul meg: hardware - operációs rendszer - felhasználói programok - felhasználó.
Az operációs rendszerek alapvetően két funkcionális egységre bonthatók:

1.
Az ún. supervisor, vagy executiv vagy monitorprogram. Nem tévesztendő össze a firmware monitorával. A gép most AUTO-módban van, abban fut az operációs rendszer, s azon belül létezik egy, a szlengben, szintén monitornak is nevezett program rész). A monitor különböző rutinokból áll, amelyek általában biztosítják a

2.
Rendszer-segédprogramok. A rendszer segédprogramjai általában három csoportra oszthatók:

Pl. tételezzük fel, hogy írtunk egy programot, mely lebegőpontos számokat von ki egymásból (ld. V.12. pont) és rajta van a diszken. A CP/M egy diszk-kezelésre alkalmas operációs rendszer, amelyet - szintén diszkről - egy LOAD paranccsal már behívtunk a memóriába és elindítottunk. Ilyenkor a CP/M parancsra vár, hogy megmondjuk mit is akarunk tőle. Ezt oly módon jelzi, hogy kiírja a képernyőre:

B>

A "B" azt jelenti, hogy aktuális diszknek a CP/M a B jelű diszket tekinti (vagy azért, mert onnan hívtuk be, vagy azért, mert ugyan az A-ról hívtuk, de a most futtatni kívánt program a B diszken van. A második esetben "A" jelentkezett be, s a kezelőnek kellett - operátori paranccsal - "átaktualizálni" a B diszkre a rendszert B: begépelésével). Ha nem emlékszünk pontosan, hogy is hívják azt a programot, amit el akarunk indítani, akkor a

DIR

hatására a CP/M megmondja, hogy milyen file-ok vannak az aktuális diszken:

Az operációs rendszerek lehetnek olyanok, hogy egyszerre csak egy program futását biztositják. Ilyen pl. a CP/M. De lehet olyan megoldás is, hogy a memóriában egyszerre több program tartózkodik. Addig, ameddig pl. az egyik program egy perifériáról beérkező adatra várakozik (tehát a processzor tétlen) az operációs rendszer "átadja" a CPU-t egy másik, eddig várakozó programnak. Így virtuálisan "egyszerre" fut a gépen több program. Ezt a módszert multiprogramozásnak nevezik. De megoldható ez időosztásos alapon is, amikor egy bizonyos ideig az 1-es programé, aztán a 2. programé... a CPU, majd újra az 1-esé stb. Ilyen pl. az MP/M II. operációs rendszer.
Mint látjuk, az op.sys. meglehetősen összetett programrendszer és általában hosszú, vagyis nagy tárterületet foglalna el. Ezért általában nem az egész rendszer van egyszerre bent az operatív tárban. Az operációs rendszereket gyakran úgy tervezik, hogy van egy ún. rezidens része, mely állandóan a memóriában van, míg az ún. tranziens rész diszken tárolódik, és csak akkor hív be ebből a részből egy-egy programot a rezidens rész, amikor konkrétan egy tranziensprogram által megoldandó feladatra kerül sor (Overlay).

4. A gépi kód- assembly nyelv- magasszintű nyelv
Minden program utasítások sorozata. Minden gépi utasítás egy-egy számkombináció. Pl. a Z80 az ED 44h (számkombináció) hatására az A regiszterben levő adatot kivonja nullából (vagyis képzi az A tartalmának kettes komplemensét) és beállítja a jelzőbiteket (flag bitek).

Természetesen a programozó nem ilyen számkombinációkkal dolgozik, hanem ún. mnemonikokkal. Minden gépi utasítás számkombinációjának megfeleltetnek egy betűcsoportot. Ezek rendszerint azoknak az angol szavaknak a rövidítései vagy betű-mozaikjai, mely szavak utalnak az utasítás feladatára. Az előbb említett ED 44h-nak pl. a NEG (negation) felel meg.

ED 44 : gépi kódú utasítás,
NEG: a fenti gépi kód assembly megfelelője.

Azt a nyelvet, amely minden gépi utasításhoz hozzárendel egy-egy mnemonikot, assembly (esszembli) nyelvnek nevezzük. Azt a fordító programot pedig, amely az assembly nyelven megirt programot meghatározott szintaktikai (formai) szabályok alapján gépi kódra fordítja, assemblernek nevezzük. De lehet a programozási nyelv olyan is, hogy egyetlen, papírra leirt programutasítás nem egyetlen gépi utasításnak felel meg, hanem többnek. Az ilyen, ún. magasszintű nyelvek rendszerint feladatorientáltak, tehát mentesek egy adott gép fizikai megkötéseitől. Pl. adatfeldolgozásra készült nyelv a COBOL, tudományos-műszaki feladatokra orientált a FORTRAN stb.

5. Programbelövés fázisai és elemei

5.1. Forrásszerkesztés
Azt a programot, amely nem gépi kódban áll rendelkezésre, hanem akár assembly, akár magasszintű nyelven, azt forrásprogramnak nevezzük. A papíron rendelkezésre álló forrásprogramot először természetesen be kell vinni a memóriába. Erre szolgál az ún. editor program. Az editor programok általában a forrásnyelvű programoknak nem csak a létrehozását segítik, hanem javítást, bővítést is lehetővé tesznek. Vagyis biztosítják:

5.2. A feldolgozás módjai
Az editor még úgy viszi be a tárba a forrásprogramot, hogy minden utasítás minden karakterének megfeleltet egy kódot. (Kisgépeknél általában az ASCII kódot használják.) Tehát pl. a

LD C,02H

utasítás képe a tárban:

L = 4C
D = 44
szóköz = 20
C = 43
, = 2C
0 = 30
2 = 32
H = 48

(Az utasítás jelentése: 02-t tölts a C regiszterbe!)
A tárban elhelyezkedő forrásprogram futtatására két lehetőség van:

  1. Fordítás compilerrel: Minden utasításnak megfeleltetjük a hozzátartozó gépi kódot (pl. a fenti esetben: 0E 02h-t, - illetve magasszintű nyelv esetén az utasításhoz tartozó gépi kódú rutint (pl. a BASIC nyelvű IF ... THEN utasítás egy feltételvizsgáló rutin) - és ezekből a gépi kódokból létrehozunk egy új tárgyprogramot. A későbbiek során mindig ezt az új programot futtatjuk.
  2. Futtatás interpreterrel: Elindítjuk a futtatást, és akkor, ott, futás közben választjuk ki a megfelelő gépi kódú rutint. Ezt a módszert elsősorban magasszintű nyelveknél használják.

5.3. Interpreter
Az interpreter (tolmácsoló, értelmező) egy olyan program, mely egy, az operatív tárban levő, magasszintű nyelven irt programot végrehajtat. A magasszintű nyelv utasításainak megfelel egy vagy több gépi kódú programrutin a tárban. Az értelmező rész egy pl., magasszintű nyelven írt utasítássort kielemez, megállapítja, hogy melyek azok a rutinok, amelyek lefuttatására szükség van az utasítás végrehajtásához, majd lefuttatja kiválasztott rutinjait. Veszi a következő utasítássort stb. A lényeg tehát az, hogy az interpreter nem gépi kódú programot futtat (de nem is feltétlenül a forrásnyelvű programot). Elsősorban professzionális gépeken szokás, hogy a forrásnyelvű programot először egy ún. "közbenső kódra" fordítják, azt esetleg még egy ún. postprocessor program tömörítheti is, és csak aztán kapja meg az interpreter.
Az első megoldást rendszerint akkor alkalmazzák, ha a firmware tartalmazza az interpretert. Ez lassú futást eredményez és általában nagy memóriaterületet köt le. A második megoldást úgy használják, hogy az interpretert egy operációs rendszer keretében írják meg. Ez egyrészt gyorsabb futást, másrészt lényegesen nagyobb alkalmazási területet biztosit, de persze drágább.

5.4. Fordítók, szerkesztők, relokálhatóság
A fordító (compiler) egy olyan program, amely az editorral szerkesztett forrásnyelvű programot a gép nyelvére fordítja le. A feldolgozás általános váza a következő:

A forrásprogramot először a fordítónak adjuk át. Vannak egymenetes és többmenetes fordítók.
Az egymenetes fordítók csak abszolút címre beültethető programokat állítanak elő, vagyis közvetlenül a futtatás helyére, a memóriába, a forrásban megjelölt kezdőcímtől kezdik meg a gépi kódú tárgyprogram beültetését. Címke, előrehivatkozás, szimbólum nem használható. A programot szerkeszteni sem lehet, a beültetett kód azonnal futtatható.
A többmenetes fordítók általában relokálható (áthelyezhető) programokat készítenek. Erre a célra használnak egy ún. referenciaszámlálót:

ref. száml. tárgykód
sorszám
forrássor
0000 02 0A 0B 0C
1
pl. 4 byte hosszú adat
0004 77 52
2
pl. egy 2 byte hosszú utasítás
0006 ...    

Ha a program relokálható, akkor a jelenleg 0000 címen levő tárgykód nem szükségszerűen kerül a memóriában is oda, hanem a felhasználói területen belül bárhová beültethető. Hogy hova akarjuk tenni, azt majd a szerkesztőnek kell megmondanunk, amely minden referenciaszámláló-értékhez hozzáadja az általunk megadott kezdőértéket. Ha pl. mi 100-at adunk bázisnak, akkor szerkesztés után:

mem. cím tárgykód
sorszám
forrássor
0100 02 0A 0B 0C
1
pl. 4 byte hosszú adat
0104 77 52
2
pl. egy 2 byte hosszú utasítás
0106 ...    

A fordítók, mikor először nézik át a bemenetül kapott forrásprogramot, ellenőrzik a formai szabályok helyességét és elkészítik minden forrásnyelvű utasítássor nyersfordítását.
A második menetben elkészül a tárgyprogram. A tárgyprogram azonban nemcsak a szükséges műveletek gépi kódját tartalmazza, mint egyfutásos esetben, hanem:

  1. Utasítások gépi kódú listája (tárgykód)
  2. Belső szimbólumok jegyzéke. Ez azoknak a címkéknek a neve és helye, melyeket a most lefordított programmodulban definiált a programozó, és itt is használta fel. Ezekre a címkékre máshol hivatkozni nem lehet.
  3. Külső szimbólumok jegyzéke (ún. cross-referencia tábla)
    A fejlettebb rendszereknél lehetőség van arra, hogy a program egyik részét ma fordítsuk, a másik részét pl. holnap. Vagyis a program különböző időben fordított forrásmodulokból épülhet fel. (Az is lehet, hogy az egyik modult más programnyelven írjuk, mint a másikat.) Természetesen ilyenkor előállhat az az eset, hogy az egyik modulból pl. át kellene ugrani egy, a másik fordítási modulban levő címkére. Azokat a szimbólumokat, amelyekre egy másik modulból is lehet hivatkozni, külső szimbólumoknak nevezzük. Ezt a táblázatot a fordító mindig elkészíti, mert a szerkesztőnek majd szüksége lesz rá.
  4. Lista-file, amely listázható formában tartalmazza a tárgymodult.

A szerkesztő (linked editor) a külön-külön lefordított tárgymodulokat kapja meg bemeneti információként, és készít belőlük futtatható formátumú programot (ún. procedúra file-t). A linked editor kimenete még az ún. beültetési térkép is, melyet konzolra vagy printerre listázhat ki.

A CP/M-nek van egy LOAD segédprogramja, amely a fordító által kiadott HEX kiterjesztésű tárgyprogramból - szerkesztő híján - COM kiterjesztésű futtatható formátumot készít. Sok gép tartalmaz nyomkövető programot (debugger), amely lépésenként futtatja az elkészült programot, s a jellemzőket kinyomtatja, így könnyítve meg a program követését hibakeresés esetén.

6. Az assembler

Az assembler az a fordítóprogram, amely assembly nyelvű forrásprogramot fordít le gépi kódra. Ma az országban három alapvető típusú fordítóprogram terjedt el:

1.
Egymenetű, abszolút címre fordító, azonnal futtatható tárgyprogramot készítő assembler. Magának a fordítónak a betöltése is közvetlenül a firmware segítségével történik, és operációs rendszert a futáshoz sem használ. Ez a Z80 közvetlen assembly programozását jelenti. A közvetlen fordító (ASD) segítségével a program-feldolgozás menete a következő:

2.
A relokálható, operációs rendszer alatt futtatható makroassembler az ASM. A program a ZILOG mnemonikokat használja, és mivel ez a processzornak is az assembly nyelve, ezért ez az assembler képezi könyvünk gerincét. A legelterjedtebb - a CP/M 2.2-operációs rendszert tételeztük fel hozzá.

3.
Mivel a CP/M-et eredetileg INTEL 8080 mikroprocesszorra irta Gary Kildall, megtalálható nálunk - és világszerte is sokfelé - olyan assembler, amely INTEL mnemonikokkal dolgozik. INTEL mnemonikokkal megirt assembly nyelvű forrásprogramból készít Z80-on futtatható programot (ASI).

A 2. és 3. esetben a programfeldolgozás menete:

6.1. Az assembly nyelv szabályai
Egy assembler utasítássor formája a következő:

CÍMKE   MŰVELETI-KÓD OPERANDUS   MEGJEGYZÉS

Az utasítássort egy "kocsi vissza" (carriage return CR) karakter zárja le. A sor részeit szóköz (space) vessző vagy tabulátorjel választja el egymástól.
A forrásprogramnak szigorú szintaktikai szabályoknak kell megfelelnie, ellenkező esetben a fordítóprogram hibát jelez, vagy súlyos esetben leáll. Hibajelzés akkor történik, ha a fordító valamilyen módon képes ugyan értelmezni az utasítássort, de nem biztos, hogy jól, s emiatt szemantikai (tartalmi) hiba léphet fel. Ha a fordító ún. végzetes hibát talál, akkor a fordítás leáll.

6.2. Direktívák
A direktívák - melyek az utasításokhoz hasonlóan a "műveleti kód" mezőben szerepelnek - a fordítóprogram számára jelentenek utasításokat, azaz a fordítást vezérlik. Főbb csoportjaik a következők:

Egyes direktívák nem foglalnak helyet a tárban, mások igen. Pl. ha egy direktívának az a jelentése, hogy "az operandus-mezőben szereplő adatokat tedd le a memóriába", akkor az a byte, szó vagy karaktersor, mely a direktíva után következik, bekerül a tárgyprogramba. De van olyan direktíva is, amely az érthetőség növelését szolgálja. Pl. egy byte hosszúságú adatot nyugodtan lehet egy utasítás operandusaként használni. Mondhatjuk azt: legyen a C reg. tartalma 80h. De pl. a feladattól függően kifejezőbb úgy mondani: legyen a C reg. tartalma egyenlő a puffer-hosszal. Ilyenkor megtehetjük, hogy utasítjuk az assemblert jegyezze meg, hogy a PUFH az 80H-t jelenti, és tegye a PUFH helyett a tárgyprogramba a 80H-t.
Egy ilyen programrészlet:

PUFH EQU 80H
     ...
     LD C,PUFH

A tárgykód LD C,80H utasításnak fog megfelelni, de a 80H mint adat az EQU hatására önállóan nem kerül a tárba.
Míg az azonos nyelvű assemblerek szintaktikai szabályai kevéssé térnek el (pl. 80H helyett $80 stb.), addig a direktívák jelölési mólja nagyon különböző és nincs tipikus. A programozási példák során általunk használt direktívákat a 3. sz. melléklet tartalmazza.

III. Utasítások, programok

Az utasításokat több szempont szerint lehet csoportosítani. Pl. aszerint, hogy milyen hosszú az az adat, amellyel az utasítás dolgozik. Lehet egy bit, négy bit (digit), egy byte, két byte.
Csoportosíthatjuk az utasításokat aszerint is, hogy milyen jellegű az elvégzendő feladat. Pl: adatmozgatás, aritmetikai művelet, logikai művelet, adminisztratív, stb:

Csoportosítási szempont lehet még, hogy milyen hosszú az utasítás (hány byte), milyen címzési módot ismer, milyen hosszú a végrehajtási ideje, stb. Mi az utasítások ismertetésinek felépítése során azt a szempontot vettük figyelembe, hogy az olvasó minél előbb önálló programot írhasson.

1. Adatmozgatás

1.1. Címzési módok
Minden számítógépen a leggyakrabban használt művelet az adatmozgatás. Ennek az eredeti ZILOG mnemonikja a LD (load). Néhány assembler azonban bizonyos esetekben MV (move: mozdit)-t kér.
Adatmozgatás esetén meg kell adni, hogy a gép honnan vegye elő az adatot, és hova vigye. Az utasításnak mindig az első operandusa mutatja azt, hogy hova, a második azt, hogy honnan. Pl.:

LD B,$12
LD B,12H

Mindkét utasítás ugyanazt jelenti, nevezetesen, hogy hexadecimális 12-t töltsön a gép a B regiszterbe. A szintaktikai eltérést a "hexadecimális" ábrázolásának különbsége okozta. Egyszerűbb gépeknél nálunk a $nn van elterjedve, s a fordítók nem is tudnak más számrendszerben ábrázolt értékeket elfogadni. Professzionális gépek azonban képesek erre, így pl.:

LD B,7CH (hexadecimális)
LD B,124 (decimális)
LD B,01111100B (bináris)

Nyolc bites adatok mozgatása lehetséges

(Adatátvitelt két memóriarekesz között ld. később.)
Azt a módot, ahogy meghatározzuk azt a helyet, ahonnan, illetve ahová az adatot vinni kell, címzési módnak nevezzük.

1.
Azt az esetet, amikor "fejből vesszük" az adatot, tehát az utasítás az adatnak nem a helyét közli, hanem magát az adatot, immediate (azonnali) címzési módnak nevezzük. Pl. a

LD A,42H

utasítás azt jelenti, hogy az A regiszterbe be kell tölteni 42H-t. Természetesen a szám bele kell, hogy essen a -128 <= n <=127 intervallumba, (ld. aritmetikai utasítások).

2.
Direkt címzési módot akkor alkalmazunk, mikor az adat

A zárójel azt jelenti, hogy az a szám, amely benne van, nem maga az adat, hanem az adat címe. Vagyis pl. az LD A,(20433H) utasítás így fordítható: a 2043H című rekesz tartalmát vidd az A regiszterbe.
Megjegyzés: A LD A(nhnl) formájú utasítás gépi kódja 3 byte. Pl. az LD A,(2043H):

00111010 3A
nl 43
nh 20

Vegyük észre, hogy a memóriában előbb következik a cím kisebb helyiértékű byte-ja, mint a nagyobb:

3.
Indirekt címzés esetén azt mondjuk meg, hogy melyik az a regiszterpár, amely a szükséges memóriarekesz címét tartalmazza.

LD A,(BC)

utasítás hatására a BC regiszterpár tartalma által címzett rekesz tartalma az akkumulátorba kerül. A zárójel itt azt jelenti, hogy "tartalmának a tartalmát", vagyis a BC regiszterpár tartalma által címzett memóriarekesz tartalmát kell az A regiszterbe tölteni.

4.
Kiterjesztett címzési mód: Van két címregiszter a Z80 processzorban (az IX és az IY) melyet bázisregiszternek használhatunk.
A bázis fogalma a következő:

A későbbiek során, egy rekesz kijelölésénél nem kell tudni a rekesz abszolút címét, csak azt, hogy a bázishoz képest hányadik. Ez a rekesznek a bázisrelatív címe. A bázisnak kijelölt rekesz címét pedig valamelyik bázisregiszterbe kell betölteni. A következő

LD A,(IY+03H)

utasítás hatására az A regiszter tartalma 12H lesz:

A fenti példák 8 bites adatokra vonatkoztak. De ugyanez a szintaktikája a regiszterpáros (16 bites) adatmozgatásnak is. (Kivéve néhány assemblert, ahol pl. a regiszterpárral kapcsolatos, és az A regiszterre vonatkozó immediate címzés esetén "MV" a mnemonik).

1.2. Adatmozgató utasítások, programrészletek
Az alábbi programrészlet az operatív tár 4500H című rekeszének tartalmát átviszi a 4651H című rekeszbe, a 4501H című rekesz tartalmát pedig a 4650H című rekeszbe. A forrásrekeszek tartalma a programrész futása után nem változik meg. Tételezzük fel, hogy a 4500H címtől a memóriatartalom:

4500H: 3E
4501H: 03
...
4650H: 00
4651H: 00

A program:

MV HL,($4500)
LD A,H
MV ($4650),A
LD A,L
MV ($4651),A
RET

A RET utasítással a későbbiek során még részletesen fogunk foglalkozni. Itt most az "állj" utasításnak, ill. a részprogram befejezésének felel meg.
A program első utasítása: MV HL, ($4500). Ennek hatására a 4500-as memóriarekeszben levő adat, a 3E, bekerül az L regiszterbe, a 4501-es rekesz tartalma pedig a H regiszterbe. Ennek oka, hogy a (standard ASS szerint) az LD HL,(nn) formátumú utasítás művelete:

(nn) L
(nn+1) H

Emiatt abban az esetben, mikor regiszterpáros (szavas) műveletekkel dolgozunk, célszerű a memóriát így elképzelni:

nagyobb helyiérték
kisebb helyiérték
0001
0000
0003
0002
...
...

Ezután a H regiszter tartalma az A regiszteren keresztül átíródik a memória 4650H-adik byte-jába, majd az L-ben levő 3E felülírja az A regisztert, majd az A tartalma a 4651-es memóriarekeszbe töltődik. A program végeredménye tehát:

Memóriarekeszek:

4500H: 3E
4501H: 03
...
4650H: 03
4651H: 3E

Regiszterek:

HL regiszterpár: 033E
A regiszter: 3E

Vegyük észre, hogy az ASD-nél, a

LD HL,(4500H)

utasításnál (amikor a zárójelet a második operandus tartalmazza) az adat annak a rekesznek a tartalmát jelenti, amely cím a zárójelben van. Ha azonban kipróbáljuk a következő utasítássort:

LD HL,4560H
LD (4580H),HL
RET

akkor azt fogjuk tapasztalni, hogy amennyiben az első operandus van zárójelben, az direkt címzést jelent. Ugyanis a fenti műveletsor eredménye az lesz, hogy a 4580H memóriacím tartalma 60 és a 4581H memóriacím tartalma 45.
Most nézzünk egy CP/M alá irt forrásprogramot:


HI
LO
AL
MAG
KEZD
ORG 100H
DEFB 3EH
DEFB 03H
DEFS 01
DEFS 01
LD HL,HI
LD A,H
LD (AL),A
LD A,L
LD (MAG),A
RET
END

Az ORG direktíva azt jelenti, hogy a program abszolút módú, s a memóriában a 100H címtől kezdődik a tárkép.
A DEFB direktíva byte-generálást jelent, vagyis a HI című memóriarekeszbe (ami most, mivel a HIGH az első byte-ja a programinak a 0100H címmel lesz egyenlő) 3EH-t fog a fordítóprogram elhelyezni.
A DEFS direktíva területet foglal le; jelenleg, mivel az operandusa 01, egyetlen byte-ot.
Jegyezzük meg: a LD (illetve MV) utasítások nem változtatják meg a flagek állapotát! (Kivétel a LD A,I és a LD A,R; ld. később!)
A CP/M segítségével az editor programmal szerkesztett forrásprogramot átadjuk az assemblernek. Az assembler lefordítja a programot és elhelyezi a tárban. A tárkép a következő lesz:

A program a 100H címen kezdődik: az első két byte az adat, a 3-4 byte egyelőre üres illetve változatlan memóriatartalom.
Ha kértünk fordítási listát is, akkor azt a következő formátumban kapjuk:


0100
0101


0104
0107
0108
010B
010C
010F

3E
03


21 00 01
7C
32 02 01
7D
32 03 01
C9

HI
LO
AL
MAG
KEZD
ORG 100H
DEFB 3EH
DEFB 03H
DEFS 01
DEFS 01
LD HL,HI
LD A,H
LD (AL),A
LD A,L
LD (MAG),A
RET
END

Az első oszlop mutatja, hogy melyik az a memóriacím, ahová a tárgykód kerül. A második oszlop mutatja, hogy mi lesz a memória cím tartalma (a tárgykód). Ha pl. azt látjuk, hogy

0104
0107
21 00 01
7C

akkor ez azt jelenti, hogy:

104 - 21 - LD kódja
105 - 00
106 - 01 - 0100 értékű cím adatként használva.
107 ...

Azt is láthatjuk, hogy a fordító a 102 és a 103. rekeszt üresen hagyta, azt majd a program tölti fel.
Amennyiben az lett volna a szándékunk a programmal, hogy

HIGH 3E H
LOW 03 L,

akkor ezt nem értük el, mert a memóriakép futtatás után:

Ugyanis a címet byte-onként tettük le, vagyis a memóriában 3E 03-ként szerepel. Ezt egy szavas LD-al akartuk a HL-be tölteni. Az nem baj, de a HI nem 3E-t jelent, hanem 0100-at. Vagyis a címke mindig a címmel azonos, és nem a tartalmával. Kövessük végig a programot debuggerrel.

A=00 B=0000 D=0000 H=0000 S=0100 P=0104
A=00 B=0000 D=0000 H=0100 S=0100 P=0107
A=01 B=0000 D=0000 H=0100 S=0100 P=0108
A=01 B=0000 D=0000 H=0100 S=0100 P=010B
A=00 B=0000 D=0000 H=0100 S=0100 P=010C
LD HL,HI
LD A,H
LD (AL),A
LD A,L
LD (MAG),A

A helyes eredmény eléréséhez a HI-t, mint operandust zárójelbe kell tenni. Ebben az esetben azt jelenti, vidd a HI című rekesz tartalmát a HL regiszterpárba. Figyeljük meg, hogy változik a 0104-es című rekeszen kezdődő tárgykód!


0100
0101


0104
0107
0108
010B
010C
010F

3E
03


2A 00 01
7C
32 02 01
7D
32 03 01
C9

HI
LO
AL
MAG
KEZD
ORG 100H
DEFB 3EH
DEFB 03H
DEFS 01
DEFS 01
LD HL,(HI)
LD A,H
LD (AL),A
LD A,L
LD (MAG),A
RET
END

Nyomkövetés debuggerrel:

A=00 B=0000 D=0000 H=0000 S=0100 P=0104
A=00 B=0000 D=0000 H=033E S=0100 P=0107
A=03 B=0000 D=0000 H=033E S=0100 P=0108
A=03 B=0000 D=0000 H=033E S=0100 P=010B
A=3E B=0000 D=0000 H=033E S=0100 P=010C
A=3E B=0000 D=0000 H=033E S=0100 P=010F
LD HL,(HI)
LD A,H
LD (AL),A
LD A,L
LD (MAG),A
RET

Ha most a futás után megvizsgáljuk a memóriát:

Az adat valóban átíródott a 2-3. byte-ra.
A két elv DEFB direktíva helyett használhattunk volna egy DEFW-t:

HI DEFW 3E03H

Ez a direktíva egy szót helyez el a memóriába mint címet, vagyis fordított sorrendben, mint ahogy az operandusban leírtuk:

100  03
101  3E

Feladatok:

  1. Írja ki a HL regiszterpár tartalmát a 4A00H és a 4A01H címekre úgy, hogy a 4A00H az L, a 4A01H pedig a H regiszter tartalmát kapja.
  2. Állítsa be a BC regiszterpár tartalmát 564EH-ra írja ki BC tartalmát a 424EH és a 424FH memóriacímre úgy, hogy a 424EH-ra a B, a 424FH-ra pedig a C tartalma kérüljön.
  3. Azt tudjuk, hogy a 45C0,-C1H memóriacímen levő adat egy memóriacímet reprezentál. Ennek az ismeretlen címnek a tartalmát hívja be az A regiszterbe úgy, hogy indirekt címzést használ.

1.3. Adatmozgató utasítások listája

LD A,(BC) Művelet: (BC) A
A BC regiszterpár tartalma által kijelölt memóriarekesz tartalma az akkumulátorba töltődik. A jelzőbitek értéke nem változik.

LD A,(DE) Művelet: (DE) A
A DE regiszterpár tartalma által kijelölt memóriarekesz tartalma az akkumulátorba töltődik. A jelzőbitek értéke nem változik.

LD A,I Művelet: I A
Az I (Interrupt Vector Register) tartalma az akkumulátorba töltődik. A jelzőbitek az utasítás végrehajtása után:
LD A,(nn) Művelet: (nhnl) A
Az nn című memóriarekesz tartalma az akkumulátorba töltődik. A jelzőbitek értéke nem változik.

LD A,R Művelet: R A
Az R (Memory Refresh Register) tartalma az akkumulátorba töltődik. Az utasítás végén a jelzőbitek állapota:
LD (BC),A Művelet: A (BC)
Az akkumulátor tartalma beíródik abba a memóriarekeszbe, melynek címét a BC regiszterpár tartalmazza. A jelzőbitek értéke nem változik.

LD (DE),A Művelet: A (DE)
Az akkumulátor tartalma a DE regiszterpár tartalma által specifikált memóriarekeszbe töltődik. A flag-regiszter tartalma nem változik.

LD (HL),n Művelet: n (HL)
Az n operandus mint egész szám. A HL regiszterpár által mutatott című memóriarekeszbe töltődik. A jelzőbitek értéke nem változik.

LD dd,nh,nl Művelet: nhnl dd
Az nn 2 b yte-os egész szám a dd regiszterpárba töltődik, ahol dd a BC, DE, HL vagy SP regiszterpárok egyikét jelöli. A jelzőbitek értéke nem változik.

LD dd,(nh,nl) Művelet: (nhnl) ddl
               (nhnl+1) ddh
Az nn című memóriarekesz tartalma a dd regiszterpár kisebb helyiértékü regiszterébe, mig az nn+1 című memóriarekesz tartalma a dd regiszterpár nagyobb helyértékű byte-jába töltődik. A dd a BC, DE, HL vagy SP regiszterpár egyikét jelöli. A jelzőbitek értéke nem változik.

LD (HL),r Művelet: r (HL)
Az r regiszter (mely az A, B, C, D, E, H vagy L valamelyike lehet) tartalma a HL regiszterpár által mutatott memóriarekeszbe töltődik. A jelzőbitek értéke nem változik.

LD I,A Művelet: A I
Az akkumulátor tartalma az I regiszterbe töltődik. A flag-regiszter tartalma változatlan marad.

LD IX,nhnl Művelet: nhnl IX
Az nn egész szám az IX indexregiszterbe töltődik. A jelzőbitek értéke nem változik.

LD IX,(nhnl) Művelet: (nhnl) IXl
               (nhnl+1) IXh
Az nn című memóriarekesz tartalma az IX indexregiszter kisebb helyértékű byte-jába, míg az nn+1 című memóriarekesz tartalma az IX indexregiszter nagyobb helyértékű byte-jába töltődik. A jelzőbitek értéke nem változik.

LD (IX+d),n Művelet: n (IX+d)
Az n operandus abba a memóriarekeszbe töltődik, amelynek címét az IX indexregiszter tartalma és a 2-es komplemensben megadott címkiegészítő operandus összege jelöl ki. A flagek értéke nem változik.

LD (IX+d),r Művelet: r (IX+d)
Az r regiszter tartalma abba a memóriarekeszbe töltődik, amelynek cymét az IX indexregiszter tartalma és a 2-es komplemensben ábrázolt címkiegészítés összege határozza meg. Az r operandus az A, B, C, D, E, H vagy L regisztereket jelölheti. A jelzőbitek változatlanok maradnak.

LD IY,nhnl Művelet: nn IY
Az nn egész szám az IY indexregiszterbe töltődik. A flag-regiszter tartalma nem változik

LD IY,(nh,nl) Művelet: (nhnl) IYl
               (nhnl+1) IYh
Az nn című memóriarekesz tartalma az IY indexregiszter kisebb helyértékű byte-jába, míg az nn+1 című memóriarekesz tartalma az IY indexregiszter nagyobb helyiértékü byte-jába töltődik. A flagek értéke nem változik.

LD (IY+d),n Művelet: n (IY+d)
Az n egész szám abba a memóriarekeszbe töltődik, amelynek címét az index regiszter tartalma, és a d kettes komplemesű címkiegészítés összege határoz meg. A flagek értéke nem változik.

LD (IY+d),r Művelet: r (IY+d)
A r regiszter tartalma töltődik abba a memóriarekeszbe, melynek címét az IY tartalmának és a d címkiegészítésnek az összege határoz meg. Az r az A, B, C, D, E, H és L regiszter valamelyikét jelenti. A flagek értéke nem változik.

LD (nn),A Művelet: A (nn)
Az akkumulátor tartalma az nn című memóriarekeszbe töltődik. A jelzőbitek értéke nem változik.

LD (nn),dd Művelet: ddl (nhnl)
               ddh (nhnl+1)
A dd regiszterpár kisebb helyiertekü byte-ja az nn című memóriarekeszbe, míg a nagyobb helyértékű byte az nn+1 című memóriarekeszbe töltődik. A dd a BC, DE, HL vagy SP regiszterpárok valamelyikét jelenti. A flagek értéke nem változik.

LD (nhnl),HL Művelet: L (nhnl)
               H (nhnl+1)
A HL regiszterpár kisebb helyértékű byte-jának, az L regiszternek a tartalma az nhnl című memóriarekeszbe, a nagyobb helyértékű byte, a H regiszter tartalma pedig az nhnl+1 című rekeszbe töltődik. A flagek értéke nem változik.

LD (nhnl),IX Művelet: IXh (nhnl)
               IXh (nhnl+1)
A IX indexregiszter tartalmának kisebb helyértékű byte-ja az nhnl című memóriarekeszbe, a nagyobb helyértékű byte-ja pedig az nhnl+1 című memóriarekeszbe töltődik. A flagek értéke nem változik.

LD (nhnl),IY Művelet: IYh (nhnl)
               IYh (nhnl+1)
A IY indexregiszter tartalmának kisebb helyértékű byte-ja az nhnl című memóriarekeszbe, a nagyobb helyértékű byte-ja pedig az nhnl+1 című memóriarekeszbe töltődik. A flagek értéke nem változik.

LD R,A Művelet: A R
Az akkumulátor tartalma az R regiszterbe töltődik /R - Memory Refresh register /. A flagek értéke nem változik.

LD r,(HL) Művelet: (HL) r
A HL regiszterpár egy memóriacímet tartalmaz. Az így kijelölt memóriarekesz tartalma töltődik az r regiszterbe, amely jelentheti az A, B, C, D, E, H vagy L regiszterek valamelyikét. A flag-ek értéke nem változik.

LD r,(IX+d) Művelet: (HL) r
A IX indexregiszter tartalmának és a d címkiegészítőnek az összege egy memóriacímet ad. Az így kijelölt memóriarekesznek a tartalma kerül az r regiszterbe, mely lehet az A, B, C, D, E, H vagy az L regiszter. A flag-ek értéke nem változik.

LD r,(IY+d) Művelet: (HL) r
A IY indexregiszter tartalmának és a d címkiegészítőnek az összege egy memóriacímet ad. Az így kijelölt memóriarekesznek a tartalma kerül az r regiszterbe, mely lehet az A, B, C, D, E, H vagy az L regiszter. A flag-ek értéke nem változik.

LD r,n Művelet: (HL) r
Az n egész szám az r regiszterbe töltődik, ahol r lehet az A, B, C, D, E, H vagy L regiszter. A flag-ek értéke nem változik.

LD r,r' Művelet: (HL) r
A r' által meghatározott regiszter tartalma az r regiszterbe töltődik. Az r és az r' lehet az A, B, C, D, E, H vagy az L regiszter. A flag-ek értéke nem változik.

LD SP,HL Művelet: HL SP
A HL regiszterpár tartalma betöltődik a stack pionter-be (Veremtár-mutató). A flag-ek értéke nem változik.

LD SP,IX Művelet: IX SP
Az IX indexregiszter tartalma a stack pointer-be töltődik. A flag-ek értéke nem változik.

LD SP,IY Művelet: IY SP
Az IY indexregiszter tartalma a stack pointer-be töltődik. A flag-ek értéke nem változik.

2. A programszámláló

Az utasítások és az adatok is számkombináció formájában vannak jelen a memóriában. Mi a különbség, honnan tudja a gép, hogy egy memóriarekesz tartalma (pl. a 77H) utasítás-e, amit végre kell hajtania vagy adat-e, amivel - egy utasítás hatására - műveletet kell végeznie? Ezt dönti el a programszámláló (utasításszámláló, program-counter (PC)).
A PC egy 16 bites regiszter, amelybe betöltődik annak a memóriarekesznek a címe, amelyben az éppen végrehajtandó utasítás van. Ha pl. a PC-be bekerül az a memóriacím, amely rekesz a 77H-t tartalmazza, akkor a 77-et a gép utasításnak. fogja fel (LD (HL),A), és végrehajtja. (Az "A" regiszter tartalmát kiírja a memóriába, arra a címre, amely a HL regiszterpárban van.) Ha a HL regiszterpár tartalma pl. éppen 4211, és A regiszter tartalma 02, akkor a 02 érték felülírja a memória 4211-es címén levő adatot. (Az A regiszter tartalma változatlan marad.)

Mint említettük, az utasítások sok szempont szerint csoportosíthatóak, egyik fontos jellemzőjük, hogy hány byte-ból állnak. Pl. az előbb említett 77H (mnemonikja: LD (HL), A) egy byte-os. De van olyan utasítás is, amely 2, 3 vagy 4 byte hosszúságú.
Amikor a gép a PC-ben levő cím alapján a memóriából beolvas a processzorba egy byte-ot, akkor a kód alapján megállapítja, hogy ez az egyetlen byte-ja-e az utasításnak, vagy van még a memória következő rekeszeiben olyan byte, amely ehhez az utasításhoz tartozik. Pl. az előző példánál, amikor a 77H bekerült a processzor belső (SW által el nem érhető) regisztereibe, a gép (a "mikroprogram") megállapította, hogy a 77 egybyte-os utasítás. Ezután végrehajtotta azt a feladatot, amely neki a 77H kódhoz rendelve van. Ezután megnövelte PC tartalmát, most 3219H lett. A memóriában a 3219H címen pl. az áll: 22H. A processzor most ezt olvassa be, és megvizsgálja. Csakhogy a "22" nem egy egy, hanem egy három byte hosszú utasítás első byte-ja. A PC tartalma megnövelődik eggyel (321AH) és a processzor beolvassa ezt a byte-ot is. A processzor megvizsgálja, hogy van-e még hátra ehhez az utasításhoz tartozó byte? Mint tudjuk, még van egy, hisz eddig csak két byte beolvasása történt meg. PC tartalma ismét megnövelődik eggyel (321BH), s akkor beolvasódik az utasítás utolsó byte-ja. Mivel ez az utasítás a processzor számára most már teljes egészében ismert, a processzor végrehajtja az utasítást. A 220E-n levő 42H-nak megfelelő mnemonik: LD (nn),HL. Ez azt jelenti, hogy a HL regiszter jelenlegi tartalmát be kell írni a memória 420EH címére (nn=420EH). A program futásának blokksémáját az alábbi ábra tünteti fel:

 

(Az IT kezelő magyarázatát ld. később.)

3. Ciklus, szubrutin, stack

3.1. Utasítások, programrészletek
Gyakran fordul elő a programozás során, hogy ugyanazt a feladatot többször kell végrehajtani. A többszörös végrehajtás alapvetően kétféle módon történhet.
Az egyik mód, amikor egy utasítássorozatot közvetlenül egymás után ciklikusan kell ismételni. Ilyent találhatunk pl. az előző ábrán. A kód beolvasás utasításait csak egyszer írjuk le és a tárban is csak egyszer szerepel, de a gép többször futtathatja le. Az ilyen megoldást ciklusnak nevezzük. Jellemző rá, hogy van egy adminisztrációs része, amely megszervezi a "lényegnek", a ciklus magjának többszöri lefutását. Azt az adatot, amely megmutatja, hogy a ciklusmag hányszor hajtódjék végre, ciklusváltozónak nevezzük.
A többszöri futtatás másik módja az, amikor egy programrészt nem közvetlenül egymás után, hanem aszimmetrikus időközökben használunk többször. Pl. írunk egy játékprogramot, amelynek során az egyik játékos "eldug" pl. egy ország nevet a másik játékosnak pedig ki kell azt találnia. Hogy ki tudja találni, ahhoz tehet fel kérdéseket a gépnek (pl. Európában van-e, tagja-e a KGST-nek, van-e tengerpartja, stb.). Mikor úgy gondolja, hogy tippelni tud, akkor ő is leírja az országnevet. A gép erre megmondja, hogy eltalálta-e vagy nem s a kérdés-tippelés folyik tovább. Láthatjuk, hogy az ország-névbeolvasó rutinra (programrészre) többször szükség van. Először, mikor az első játékos dug, aztán valahányszor a kettes játékos tippel.
Az ilyen részfeladatokat teljesítő programrészeket, melyeket csak egyszer kell leírni, azután bárhonnan hívható, szubrutinnak nevezzük.A folyamat a következőképp zajlik: A főprogram fut, egy bizonyos pontján abbahagyja a munkát, s "meghívja" az 1 Rutint. Az 1 Rutin dolgozik, s mikor elkészült, visszaadja a vezérlést a főprogramnak, pontosabban annak az utasításnak, amely az őt meghívó (ún. CALL utasítás után következik. Nézzük meg a következő ábrát:

Láthatjuk, hogy a szubrutinhívó utasítás három byte-os. Az első byte jelenti azt, hogy szubrutinhívás, a másik kettő pedig az a memóriacím, ahol a hívott rutin első utasítása van. (Vegyük észre, hogy a hívott cím 425AH, de a memóriában így szerepel: 5A42. Vagyis a cím nagyobb helyértékű bytja a memóriában a kisebb helyértékű byte mögé kerül.)
A példa szerint, amikor a processzor rátér a CALL utasítás beolvasására, a PC tartalma 3212H, mikor a beolvasást befejezte, akkor PC tartalma 3214H. Magának a CALL utasításnak az a lényege, hogy a PC-be egy másik, egy új címet, a szubrutin első utasításának a címét tegye. De akkor a szubrutin lefutása után a gép hogy fog visszatalálni a főprogram CALL utáni utasítására? Úgy, hogy a CALL, mielőtt a PC-t feltöltené, előbb "elmenti" a CALL után következő (példánkban a 3215H) utasítás címét. Azt a tárterületet ahova a CALL utasítás elmenti a PC tartalmát, azt úgy hívják, hogy stack (asztag). A programozónak módja van arra, hogy a memóriában kijelölje a stack területet, ahova adatokat menthet. A név találó, mert mindig azt a "kévét" kell először levenni, amit utoljára tettünk az asztag tetejére. A stack-et szokták zsák- vagy verem-memóriának is hívni. Az a csomag kerül először a kezembe, amit utolsónak raktam bele. Értelemzavaró lehet, hogy az asztag valami felfelétörőt, magasat érzékeltet, a zsák pedig mélyülést. A zavart feloldhatja, ha meggondoljuk, hogy ábrázolásban a rajzon magasabb helyen levő cím a kisebb.

FFF0
FFF5
A számozás - a szokásos ábrázolásmód szerint - lefelé nő!

A stack aktuális rekeszét a stack-pointer (SP) jelöli ki, amely mindig a stack tetejére mutat. Pl:

A CALL utasítás tehát:

  1. megnöveli a PC-értékét 1-gyel (most 3215H-t kap),
  2. ezt az értéket elmenti a stack-be,
  3. betölti a CALL-ban megadott értéket a PC-be.

A stack töltése a következő lépésekből áll:

  1. SP csökkentése 1-gyel,
  2. SP által mutatott címre betölteni az adat nagyobb helyi értékű byte-ját,
  3. SP csökkentése 1-gyel,
  4. az adat kisebb helyértékű byte-jának írása a memóriába.

Helyzet a rutin indulásának pillanatában:

A rutin kötelezően (valamilyen) RET utasítással fejeződik be, amelynek az a feladata, hogy a stack-ből kiemelje a visszatérési címet és a PC-be töltse.

A szubrutin paraméterei
Amikor egy szubrutint hívunk, a szubrutin természetes módon bizonyos adatokkal dolgozik. Ahogy láttuk, a szorzó rutinnál a szorzót és szorzan-dót adott memóriahelyeken találja meg. A paraméterek átadásának többféle technikája van:

  1. Regisztereken keresztüli paraméterátadás.
    Ez egy előnyös megoldás, ha gondoskodunk róla, hogy a regiszterek rendelkezésünkre álljanak, és mivel nem szükséges fix memória helyeket használnunk, így a szubrutin memóriafüggetlen lesz.
  2. A memórián keresztüli paraméter átadás.
    Ha egy rögzített memóriahelyet használunk, akkor a szubrutint használó programozónak nagyon óvatosnak kell lenni, hogy ezeket a memóriahelyeket más célra ne használja. Ez az oka annak, hogy a különböző szubrutinoknak átadandó paraméterek számára a memória helyeket előre rögzítik. Ez különösen nagy mennyiségű adatok esetén előnyös.
  3. A vermen keresztüli paraméterátadás.
    Ennek használata ugyanolyan előnyös mint a regisztereké, hiszen ez is memóriafüggetlen. Ebben az esetben a szubrutin egyszerűen a verem tetején levő paramétereket kapja meg.

Nyilvánvaló a programozóra van bízva, hogy melyik lehetőséggel él, de általában az a cél, hogy olyan sokáig legyen független az aktuális memóriahelyektől a paraméterek helye, ameddig csak lehetséges.
Ha a regiszterek nem használhatók, akkor a verem egy lehetséges megoldás. Továbbá, ha az információk mennyisége nagy, amelyet át kell adni a szubrutinnak, akkor ezeket az információkat közvetlenül a memóriába is elhelyezhetjük. Elegáns megoldás, ha az adatokat egy blokkban helyezzük el, és egyszerűen csak egy pointert adunk meg, amely nem más, mint a blokk kezdőcíme. Egy pointer átadható egy regiszterben vagy a veremben, vagy egy adott memóriacím(ek)en. Végül, ha a két megoldás egyike sem használható, akkor meg kell alkudni azzal a lehetőséggel, hogy az adat valamelyik fix memóriahelyre kerül.

A stack (verem)
A stack kezelésére egyébként a programozónak közvetlenül is rendelkezésére áll két utasítás, a PUSH és a POP.

Nézzünk egy példát: Tételezzük fel, hogy ki akarjuk olvasni az F (flag) regiszter tartalmát egy memóriacímre. A flag-regiszterhez - mint egészhez - csak úgy tudunk hozzáférni, hogy az A regiszterrel együtt (így képeznek két byte-ot) elmentjük a stack-be. Természetesen a monitor, mely elindította programunkat készít magának stack-et. De most ne nyúljunk abba bele, hanem készítsünk magunknak egy új stac-ket.

4600 MV ($4560),SP
MV SP,($4580)
LD A,$0
PUSH AF
POP AF
MV SP,($4560)
RET
; erederi stackpointer mentése
; új SP
0 -> A
; AF mentése

; SP visszaállítása

Az első utasítás a monitor stackpointerét elmenti a 4560-61H címre. Új stack jön létre:

Miután a program az A regisztert kinullázta, A-t és a flag-regisztert beírja a stackbe.

A stack áthelyezéssel nagyon óvatosan kell bánni. A MV SP,($4560) az elmentett monitor-stackpointert visszaírja az SP regiszterbe.
Míg a PUSH AF utasításnak az az értelme, hogy ez az egyetlen módja annak, hogy az F flaget a memóriába írjuk, a POP utasítás olyan helyre írja vissza, ahol már úgyis ott van. Akkor minek? Azért, mert szokjuk meg, hogy stack-kezelés esetén minden PUSH-nak legyen meg a POP párja. Ellenkező esetben nem az akadna a kezünkbe legközelebb, amire számítunk. Ezzel a látszólag funkciótlan POP-al is erre akarjuk felhívni a figyelmet.

Mint már említettük, egy felhasználói programot a futtató rendszer szubrutinként indít el, ezért használhatjuk visszatérő utasításként a RET-et. Mind az ASM, mind az ASI relokálható assembler és szerkesztésre is számit. Különböző programszervezési megfontolások alapján a fordító a rendszer stackjét közvetlenül a program elé helyezi, de úgy, hogy futtatás után rendszervisszatéréskor a felhasználói program első byte-ját használja.
Nálunk még (1984. február) nem mindenütt van meg ténylegesen a szerkesztőprogram, viszont a fordító már ahhoz alkalmazkodik. Ezt úgy lehet kivédeni, hogy programunkat a stack áthelyezésével kezdjük. A program végén - mielőtt visszatérünk a rendszerbe - a stacket természetesen illik visszamenteni.


0100
0104
0107

010B

010F
0112
0113
0116
0117
011A
011E


ED 73 09 01
31 00 05
18 06

03 3E

2A 0B 01
7C
32 0D 01
7D
32 0E 01
ED 7B 09 01
C9




SPO
HI
AL
KEZD
ORG 100H
LD (SPO),SP
LD SP,500H
JR KEZD
DEFS 2
DEFW 3E03H
DEFS 2
LD HL,(HI)
LD A,H
LD (AL),A
LD A,L
LD (AL+1),A
LD SP,(SPO)
RET
END

Mint látjuk, az operandusban használhatunk kifejezést is. Mivel az AL címke a 010D memóriacímet jelenti, a 010E címre AL+1 kifejezéssel is hivatkozhatunk.
A DEFW direktíva a 3E03H adatot fordított sorrendben helyezi el a memóriában, 033EH.
Memóriakép futás után:

3.2. Szubrutint és stacket közvetlenül kezelő utasítások.
A SP (Stack Pointer) töltését néhány típusu LD utasítás végzi, Iásd ott 1.3. fejezet.
A feltétlen szubrutinhívás utasítása:

CALL nn Művelet:
PCh (SP-1)
PCl (SP-2)
nn (PC)
A PC pillanatnyi értékének a stackbe mentése után az nn operandus töltődik a PC-be, amely így arra a memóriacímre mutat, amely a szubrutin első utasítás-byteját tartalmazza. A mentési művelet sorrendje:

SP-1 SP
PCh (SP)
SP-1 SP
PCl (SP)

A mentés megkezdésekor a PC tartalma már a CALL-t követő utasításra mutat. A flagek nem változnak.

CALL cc,nn Művelet:
ha cc igaz, akkor
  PCh (SP-1)
  PCl (SP-2)
  nn (PC)
ha cc nem igaz, akkor
  PC+1 PC
Feltételes utasítás, vagyis a szubrutin hívása és lefuttatása csak abban az esetben történik meg, ha cc igaz. Ekkor a mentési-töltési művelet megegyezik a 2-es pontban leírtakkal. Ha cc nem igaz, akkor a CALL utáni utasítás hajtódik végre. Mivel a CALL 3 byte-os, ezért először:
Feltételek
Vizsgált bit
NZ nem zérus
Z zérus
NC nincs átvitel
CY átvitel
PO páratlan paritás
PE páros paritás
P pozitív
M negatív
Z
Z
CY
CY
P/V
P/V
S
S
RET Művelet:
(SP) PCl
(SP+1) PCh
Feltétlen visszatérés szubrutinból. Az előzőleg CALL utasítással stackbe mentett eredeti programszámláló-tartalmat a gép a veremtárból kiemeli és a PC-be visszatölti. A flagek tartalma nem változik.

RET cc Művelet:
Ha cc igaz, akkor
  (SP) PCl
  (SP+1) PCh
Ha cc nem igaz, akkor
  PC+1 PC

Feltételes visszatérés szubrutinból. Ha a feltétel nem teljesül, akkor a gép a rutin következő utasítását hajtja végre. A feltételek megegyeznek az előző pontban leírtakkal. A flagek tartalma nem változik.


PUSH IX Művelet:
IXh (SP-1)
IXl (SP-2)
Az utasítás az IX indexregiszter tartalmát a stackbe menti. A stack aktuális tetejére mutató címet az SP regiszter tartalmazza. Az utasítás először az SP tartalmát csökkenti eggyel, és az indexregiszter nagyobb helyértékű byte-ját az így nyert cím által kijelölt memóriarekeszbe tölti, majd az SP tartalma ismét dekrementálódik és az indexregiszter kisebb helyértékű byte-ja kerül az SP regiszterpárral címzett memóriarekeszbe, azaz a stack tetejére. A flagek tartalma nem változik.

PUSH IY Művelet:
IYh (SP-1)
IYl (SP-2)
Az utasítás az IY indexregiszter tartalmát menti a stackbe, ugyanúgy, ahogy azt az IX regiszterrel kapcsolatban ismertettük. A flagek tartalma nem változik.

PUSH qq Művelet:
qqh (SP-1)
qql (SP-2)
Az utasítás a qq regiszterpárral hajtja végre ugyanazt a műveletsort, melyet az IX, IY indexregiszterekkel kapcsolatban ismertettünk. A qq lehet BC, DE, HL, vagy AF. A flag-ek tartalma nem változik.

POP IX Művelet:
(SP) IXl
(SP+1) IXh
Az utasítás a LIFO (Last In First Out - utoljára be, elsőnek ki; a LIFO mozaik szó, a stack egy másik szokásos elnevezése) stack-területről a legfelsőbb byte-párost az IX indexregiszterbe tölti. A stack aktuális tetejére mutató címet az SP regiszterpár tartalmazza. Az utasítás először az SP tartalma által kijelölt memóriarekesz tartalmát tölti az indexregiszter kisebb helyértékű byte-jába, majd az SP inkrementálódik, es az így kijelölt (a következő) memóriarekesz tartalma töltődik az IX nagyobb helyértékű byte-jába. Ezután SP ismét inkrementálódik. A jelzőbitek értéke nem változik.

POP IY Művelet:
(SP) IYl
(SP+1) IYh
Az utasítás az IY indexregiszterbe tölti a stackbe legutoljára elmentett két byte-ot. Az utasítás lefolyása a POP IX-nél ismertetett módon történik. A flagek tartalma nem változik.

POP qq Művelet:
(SP) qql
(SP+1) qqh
A qq operandus jelentheti a BC, DE, HL vagy AF regiszterpárokat. Az utasítás a stack-be legutoljára betöltött két byteot másolja át a kijelölt regiszterpárba a POP IX-nél ismertetett módon. A jelzőbitek értéke nem változik.

4 . Inkrementálás; dekrementálás; ugrások

4.1. Utasítások, programrészletek
Az inkrementálás növelést, a dekrementálás csökkenést jelent. Eggyel való növelésre az INC, eggyel való csökkentésre a DEC mnemonikot használhatjuk. Ezt a műveletet egy byte-os és két byte-os adat esetében is megengedi a Z80. Lényeges különbség azonban, hogy míg regiszterpár esetében a flag-ek tartalma nem változik, addig egy byte-ra vonatkozó INC, illetve, DEC utasítás hatására a flagek beállnak a megfelelő értékre (Id. az utasítástáblázatot).
Gyakran van szükség arra, hogy egy utasítás végrehajtása után ne azt az utasítást hajtsa végre a gép, amely a tárban utána következik, hanem egy egészen másikat. Ilyenkor ugróutasítást (jump - ugrik) használunk. Alapmnemonikja: JP. Abból a szempontból, hogy az ugrást feltétlenül végre kell-e hajtani, vagy csak akkor, ha egy bizonyos feltétel teljesül, az ugró utasításokat feltétel nélküli és feltételes ugrásokra osztjuk. A feltétel mindig valamelyik flag-nek az állapota.

NZ ugrás, ha nem nulla
ha az adat = 0 Z flag = 1 ugrás nincs
ha az adat <> 0 Z flag = 0 ugrás van
Z ugrás, ha nulla
ha az adat = 0 Z flag = 1 ugrás van
ha az adat <> 0 Z flag = 0 nincs van
NC ugrás, ha nincs átvitel (CY=0)
CY CY ugrás, ha van átvitel (CY=1)
PO ugrás, ha páratlan (P/V=0)
PE ugrás, ha páros (P/V=1)
P ugrás, ha pozitív (S=0)
M ugrás, ha negatív (mínusz) (S=1)

A feltételes ugrást így ábrázolhatjuk:

Itt ismét megjegyezzük, hogy a szubrutinhívás (CALL), és a visszatérés (RET) is lehet feltételes.
Hogy az ugrás melyik memóriacímre történjék, azt megadhatjuk abszolút módon, vagyis magában az ugróutasításban szerepel a megcélzott memóriacím. De megadhatjuk a célt relatív módon, úgy, hogy megmondjuk, hogy az aktuális JP (illetve ebben az esetben "relatív jump" JR) utasításhoz képest hány byte-ot kell átugrani. Csakhogy a relatív ugrás gépi kódja két byte hosszú. Az első az ugrás módját jelöli ki, a második byte azt a számot tartalmazza (2-es komplemensben) amivel a PC-t módosítani kell. Fejlett assemblerek esetén természetesen elég a címkéket megadni, a paramétert a fordítóprogram kiszámolja.
A relatív ugró utasítás csak a CY és a Z flaget tudja vizsgálni. Amennyiben az utasítás második byte-ja nulla, és az ugrási feltétel teljesül, akkor addig várakozik (ugrál önmagára), míg az aktuális flag értéke meg nem változik.
Egy speciális, feltételes ugrással kombinált dekrementáló utasítás a DJNZ. Ez az utasítás 1-el csökkenti a B regiszter tartalmát, majd megvizsgálja, hogy egyenlő-e nullával. Ha igen, akkor a gép végrehajtja a következő utasítást. Ha azonban a B regiszter tartalma nem nulla (még nem fogyott el), akkor a következő utasítás az lesz, amelynek címére a DJNZ operandus mezejében utalás történik.

Oldjuk meg a következő feladatot:
Van- a memóriában két 0D-0DH byte-nyi területünk. Az egyik terület (nevezzük a kezdőcímét INNEN-nek) olyan adatokat tartalmaz, amelyeket át akarunk vinni a másik, ODA kezdőcímű területre.
Byte-számlálónak nevezzük ki a B regisztert. Így a feladat blokksémája a következő lesz:



INNEN
ODA
BY
MASOL


CIKL



NOV
ORG 100H
JR MASOL
DEFM 'ALL MAR A BAL'
DEFS 0DH
EQU 0DH
LD B,BY
LD HL,INNEN
LD DE,ODA
LD A,(HL)
LD (DE),A
DJNZ NOV
RET
INC HL
INC DE
JR CIKL
END

Mint látjuk, az EQU direktíva a tárban nem foglal el helyet, csak a fordítóprogram jegyzi meg magának, hogy ahol "BY" operandust talál, oda 0DH-t kell írnia.
Memóriakép a futás után:

4.2. Inkrementálási, dekrementálási, ugró utasítások listája

INC (HL) Művelet: (HL)+1 (HL)
HL regiszterpár tartalma által megcímzett memóriarekesz tartalma inkrementálódik. A jelzőbitek állapota az utasítás végrehajtása után:
INC qq Művelet: qq+1 qq
A qq által jelölt regiszterpár tartalma 1-el nő. A jelzőbitek értéke nem változik, qq lehet IX, IY, BC, DE, HL, SP.

INC s Művelet: s+1 s
Az operandusban kijelölt byte tartalma inkrementálódik.

Az s lehet:

A jelzőbitek állapota az utasítás után

DEC qq Művelet: qq-1 qq
Egy regiszterpár tartalmának csökkentése eggyel, qq lehet: BC, DE, HL, SP, IY vagy IX. A jelzőbitek állapota nem változik.

DEC m Művelet: m-1 m
Egy byte értékének csökkentése 1-gyel.

Az m operandus lehet:

A jelzőbitek állapota az utasítás végrehajtása után

JP (qq) Művelet: qq PC
A programszámláló (PC regiszter) a qq regiszterpár tartalmával töltődik fel. A következő utasítás a PC új tartalma által meghatározott memóriarekeszből hívódik le. A qq lehet IX, IY és a HL. A jelzőbitek állapota nem változik.

JP nn Művelet: nn PC
Feltétel nélküli ugrás, a következő végrehajtandó utasítás az nn című memóriarekesz tartalma lesz. A jelzőbitek értéke nem változik.

JP cc,nn Művelet:
ha cc igaz, akkor nn PC
ha cc nem igaz, akkor PC = PC+1
Feltételes ugrás, ha a cc feltétel teljesül, akkor az utasítás az nn operandust a PC-be tölti, és a program az nn címen kezdődő utasítás végrehajtásával folytatódik. Ha a cc nem igaz, akkor normál módon a PC inkrementálódik, és a JP után következő utasítás lesz végrehajtva.

A cc a flag-regiszter valamelyik bitjét jelenti:

Feltétel cc
aktuális bit
NZ nem zérus
Z zérus
NC nincs átvitel
CY átvitel
PO páratlan paritás
PE páros paritás
P pozitív
M negatív
Z
Z
CY
CY
P/V
P/V
S
S

Az ugrás során a flagek nem változnak.

JR C,e Művelet:
ha CY igaz, akkor PC+e PC
ha CY nem igaz, akkor PC+1 PC
Az utasítás az átvitel bit vizsgálatának eredményétől függően egy más programszegmensre ugrik, relatív címzéssel. Ha CY=1, akkor a kettes komplemens formájában megadott "e" operandushoz hozzáadódik -2 (ugyanis a fenti utasítás 2 byte-os), majd az így kapott érték hozzáadódik a PC tartalmához. Így PC-be az a cím kerül, ahova a feltétel teljesülése esetén ugrani kell, vagyis amely memóriarekesz tartalmazza a következő végrehajtandó utasítás első byte-ját. Ha CY=0, akkor PC normál módon növelődik, és a JR után utasítás következik. A jelzőbitek állapota nem változik.

JR e Művelet: PC+e PC
Feltétel nélküli relatív ugrás. Az új cím kiszámítása a 2-es komplemensben ábrázolt e segítségével a JR C,e szerint történik. A jelzőbitek értéke nem változik.

JR NC,e Művelet:
ha CY igaz, akkor PC+1 PC
ha CY hamis, akkor PC+e PC
Feltételes relatív ugrás. Ugrás akkor történik, ha CY = 0. Az új cím kiszámítása a JR C,e-nél ismertetett módon történik. A flag-ek tartalma nem változik.

JR Z,e Művelet:
ha Z igaz, akkor PC+e PC
ha z hamis, akkor PC+1 PC
Feltételes relatív ugrás. Ugrás akkor történik, ha Z = 1. Az új cím kiszámítása a JR C,e-nél ismertetett módon történik. A flag-ek tartalma nem változik.

JR NZ,e Művelet:
ha Z igaz, akkor PC+1 PC
ha z hamis, akkor PC+e PC
Feltételes relatív ugrás. Ugrás akkor történik, ha Z = 0. Az új cím kiszámítása a JR C,e-nél ismertetett módon történik. A flag-ek tartalma nem változik.

5. Összehasonlítás, képernyő- és klaviatúra kezelés

5.1. Utasítások, programok
Az összehasonlításra a Z80-as processzor a CP (to compare = összehasonlít) valamelyik változatát használja. Az utasítás tulajdonképpen kivonást hajt végre anélkül, hogy az összehasonlítandó regiszterek vagy memóriarekeszek tartalma megváltozna. Csak a flag-ek állnak be a megfelelő értékre. A kisebbítendő mindig az A regiszter tartalma. Az összehasonlítás eredményét rendszerint egy feltételes (JP, CALL, RET) utasítással értékeljük ki. Pl. tételezzük föl, hogy egy memóriarekesz tartalmától függően kell valamit elvégezni, vagy nem. Így pl., ha az adat a 'kocsi vissza' karakter kódja, akkor fejeződjön be a program, ha a kód nem 'CR', akkor pedig folytatódjon.





LD HL,TESZT
LD A,0DH
CP (HL)
JR NZ TOVA
RET
; adat címe
; 0DH = kocsi vissza ASCII kódja
; összehasonlítás A = (HL)?
; ha TESZT <> 0DH akkor ugrik

Megismételjük, hogy a CP utasítás hatására sem a memóriatartalom (a példában TESZT tartalma) sem az A regisztert tartalma (0DH) nem változik meg. Csak a flag-ek állnak be a kivonás eredményétől függően. Ha a két adat egyenlő, akkor A-n = 0, tehát a Z flag = 1. Ha A>-n, akkor, A-n eredménye pozitív, így Z = 0 és S = 0. Ha A <n, akkor az A-n kifejezés értéke negatív, tehát Z = 0 és S = 1.

A képernyő és klaviatúra kezelését - mint mondottuk - a firmware, illetve az operációs rendszer látja el. Firmware esetén a kezelő rutinok CALL utasítással hívhatók. Az indítási cím természetesen géptípusonként változik, rendszerint ez valamelyik RST- (restart - újraindítás) cím. Az RST egy egy byte-os ugró utasítás, mely a következő fix címekre ugrik:

Tételezzük fel, hogy gépünkben a memóriából képernyőre író rutin a 0028H címen kezdődik.
A rutin az A regiszterben levő adatot ASCII kódnak értelmezi, s a kódnak megfelelő karaktert kiírja a képernyőn oda, ahol a kurzor éppen áll. Ha tehát képernyőre akarunk írni, akkor:

  1. karakterkód A
  2. rutinhívás

A rutin csak egyetlen karaktert ír ki, szövegábrázoláshoz tehát ciklust kell szerveznünk.
Tegyük fel, hogy a memóriában a 4560 címtől kezdődően a következő kódok vannak:

0D 41 4A 54 4F 0D
(CR) A J T O (CR)

Írassuk ki az AJTÓ szót a képernyőre!

A folyamatábrának megfelelő programlista:

1700 LD B,$06
MV HL,$4560
LD A,(HL)
CALL &0028
INC HL
DJNZ $F9 ; -7 kettes komplemense
RET

A klaviatúráról a memóriába író rutin kezdődjék a 0018-as címen. Ez a rutin a klaviatúrán leütött karakternek az ASCII kódját olvassa be az A regiszterbe. Rendszerint a rutin csak annyit csinál, hogy megvizsgálja a klaviatúra állapotát, s amelyik gombot lenyomva találja, annak kódját olvassa be.
Ha a program nem talál lenyomott gombot (mert pl. kezünk még csak közeledik a billentyű felé), akkor 0-t ír A-ba és fut tovább. Tehát a programozónak gondoskodnia kell arról, hogy a program megvárja, míg az operátor leüti a billentyűt, s csak aztán mehet tovább. Ezt úgy is elérhetjük, hogy az A regiszterbe 00-t töltünk. Ez egyetlen ASCII karakternek sem felel meg, tehát ha megtörtént a beolvasás, akkor A tartalma már nem lesz nulla. Ha azonban még mindig az, akkor újra hívjuk a beolvasó rutint, es leteszteljük a klaviatúrát.

4350 LD A,$00
CALL $0019
CP $00
JPZ $F8
RET

A rutin a billentyűzetről olvasott karaktert nem írja ki a képernyőre. Ha tehát azt akarjuk, hogy egy párbeszéd követhető legyen a képernyőn, akkor egymás után mindkét rutint meg kell hívni.

5.2. Képernyő és klaviatúrakezelés CP/M alatt
Ha a programot a CP/M "alá" írjuk, akkor az operációs rendszer szolgáltatásait vesszük igénybe. Itt a beépített HW funkciókat rendszerint a CALL 05H hívással lehet elérni, de előtte fel kell tölteni:

A konzol display kezelése a következő mórion lehetséges:

  1. Írás a képernyőre (konzol output)
    C = 02
    E = ASCII kar. kód.

  2. Olvasás a klaviatúráról (konzol input)
    C = 01
    A beolvasott karakter az A regiszterbe érkezik. A rutin megvárja a karakter elkészültét a klaviatúrán, s a beolvasott kód a képernyőn is megjelenik.

Jelenítsünk meg egy karakterstringet!

SZOV
JELEN

CIKL




IR
DEFM 'Ember kuzdj es bizva bizzal'
LD HL,SZOV
LD B,1BH
LD E,(HL)
CALL IR
INC HL
DJNZ CIKL
RET
PUSH HL
PUSH DE
PUSH BC
LD C,2
CALL 5
POP BC
POP DE
POP HL
RET

Nagyon fontos, hogy a rendszer meghívása (CALL 5) előtt a BC, HL és DE regiszterpárokat elmentsük, mert különben nem fog helyesen dolgozni a programunk.
A következő program öt byte-ot olvas be a klaviatúráról:



SZOV
OLV

CIKL




OL
ORG 100H
JR OLV
DEFS 5
LD HL,SZOV
LD B,5
CALL OL
LD (HL),A
INC HL
DJNZ CIKL
RET
PUSH HL
PUSH DE
PUSH BC
LD C,1
CALL 5
POP BC
POP DE
POP HL
RET
END

Memóriakép a program futása után:

A memóriában természetesen a betűk ASCII kódjai szerepelnek (jelen esetben az ABLAK szó karakterei). Bizonyos esetekben a célnak megfelelőbb megoldás az, hogy nem előre meghatározott számú byte-ot olvasunk be, hanem az olvasás addig tart, míg egy bizonyos karaktert, az ún. végjelkaraktert le nem üti az operátor. Ez rendszerint a "kocsi vissza" karakter szokott lenni. Így azonban könnyen előfordulhat, hogy több adatot olvasunk be, mint a beolvasási mező hossza, felülírhatunk más területet is. Ezért ajánlatos egy számlálót is alkalmazni, annak ellenőrzésére, hogy a felhasználó nem lépte-e túl aj pufferhatárt, s ha igen, akkor hibajelzés történik.

A főprogram a VEGJ címkénél kezdődik.



HSTR

OLV




KIIR






OL








IR








OLV1


VEGJ



MEZO
PUFH
ORG 100H
JR VEGJ
DEFM 'PUFFER BETELT'
DEFB 0DH
CALL OL
CP 0DH
RET Z
DJNZ OLV1
LD HL,HSTR
LD E,(HL)
CALL IR
LD A,E
CP 0DH
RET Z
INC HL
JR KIIR
PUSH HL
PUSH DE
PUSH BC
LD C,1
CALL 5
POP BC
POP DE
POP HL
RET
PUSH HL
PUSH DE
PUSH BC
LD C,2
CALL 5
POP BC
POP DE
POP HL
RET
LD (HL),A
INC HL
JR OLV
LD HL,MEZO
LD B,PUFH
CALL OLV
RET
DEFS 6
EQU 7
END

Ma már íratlan szabály, hogy a programozó interaktív módon írja meg a programokat, vagyis biztosítson társalgási lehetőséget a felhasználó és a program között. Ennek egyik tipikus megoldása az, hogy a programnak azon a pontjain, ahol a kezelőnek kell bizonyos dolgokat eldöntenie, ott a választási lehetőségeket, a "menü"-t kiírja a képernyőre, s az operátor a választott megoldás sorszámát gépeli be. Ilyen programrészt mutat a következő lista. Az operátor által kiválasztott szöveget a program megismétli a képernyőn.

  ORG 100H
JP MENU
OLV CALL OL
CP 0DH
RET Z
DJNZ OLV1
RET
KIIR LD E,(HL)
CALL IR
INC HL
DJNZ KIIR
RET
OL PUSH HL
PUSH DE
PUSH BC
LD C,1
CALL 5
POP BC
POP DE
POP HL
RET
IR PUSH HL
PUSH DE
PUSH BC
LD C,2
CALL 5
POP BC
POP DE
POP HL
RET
OLV1


KER

ELE

BRU

BOR

IZI

CEC

ABI
LD (HL),A
INC HL
JR OLV
DEFW 0D0AH
DEFM 'Usse le a valasztott sor szamat!'
DEFW 0D0AH
DEFM '1. Eleonora'
DEFW 0D0AH
DEFM '2. Brunhilda'
DEFW 0D0AH
DEFM '3. Borbala'
DEFW 0D0AH
DEFM '4. Izidora'
DEFW 0D0AH
DEFM '5. Cecilia'
DEFW 0D0AH
DEFM '6. Abigel'
DEFW 0D0AH
MENU

ISMET
LD B,MENU-KER
LD HL,KER
CALL KIIR
LD HL,SZAM
LD B,2
CALL OLV
LD A,(SZAM)
CP 31H
JP Z,EGY
CP 32H
JP Z,KET
CP 33H
JP Z,HAR
CP 34H
JP Z,NEGY
CP 35H
JP Z,OT
CP 36H
JP Z,HAT
LD B,0DH
LD HL,HI
JP ISMET
EGY

VEG


KET


HAR


NEGY


OT


HAT
LD B,BRU-ELE
LD HL,ELE
CALL KIIR
RET
LD B,BOR-BRU+1
LD HL,BRU
JR VEG
LD B,IZI-BOR
LD HL,BOR
JP VEG
LD B,CEC-IZI
LD HL,IZI
JP VEG
LD B,ABI-CEC
LD HL,CEC
JP VEG
LD B,MENU-ABI
LD HL,ABI
JP VEG
HI


SZAM
DEFW 0D0AH
DEFM 'Ismeteljen!'
DEFW 0D0AH
DEFS 2
END

A program futásakor megjelenik a menü, amiből a kezelőnek kell választani:

6. Logikai utasítások, adatláncra vonatkozó műveletek

6.1. Utasítások, programrészletek
A Z80 három logikai utasítást ismer

AND logikai ÉS
OR logikai VAGY
XOR logikai KIZÁRÓ VAGY

A műveletek egyik operandusa mindig az A regiszter. Mivel a LD utasítás nincs hatással a flagek állapotára, az AND A,A illetve az OR A,A utasítást lehet flag-beállításra is használni.

Pl. ha (A) = 3FH

  0011 1111
OR vagy AND 0011 1111
= 0011 1111

A nem változott, de Z flag = 0, S flag = 0.

A XOR A,A utasítás törli az A regisztert, mivel a kizáró VAGY eredménye csak akkor 1, ha a két operandus különbözik egymástól, egyébként pedig 0.
A CPL utasítással invertálni tudjuk az A regiszter tartalmát.

A Z80 ismer olyan utasításokat, amelyek szinte kis szubrutinok. Ezek egyik csoportja az ún. láncáthelyező utasítások. Az előző részekben már irtunk olyan rutint, amely egy memóriaterületről visz át adatot egy másik memóriaterület-re. Láthattuk, hogy bemeneti információként szükség volt:

A következő négy utasítás adatokat visz át a memória valamely területéről egy másik memória-területre:

LDI:

LDIR:

LDD:

LDDR:

Láncra vonatkozó formája a CP (összehasonlító) utasításnak is van, ilyenkor az A-ban magadott kódot keresi egy láncban.

Az összehasonlítás mindenképpen megáll, ha A = (HL), ilyenkor Z = 1 lesz, vagy ha a BC tartalma 0-ra csökkent.

CPI:

CPIR:

CPD:

CPDR:

Mindenfajta CP utasítás állítja a flag-eket!
Itt jegyezzük meg, hogy memória-periféria között is lehet láncolt átvitel, melyet az IN, illetve OUT utasítások valamelyik fajtája valósíthat meg.

Tételezzük fel például, hogy a memóriában az 1F20H címtől kezdődően 1AH db byte-ot át akarunk helyezni a 4200 címen kezdődő mezőbe.

4500 MV HL,$1F20
MV DE,$4200
MV BC,$1A
LDIR
RET

Most legyen egy olyan feladatunk, mely szerint meg kell számolnunk, hogy a fenti adatmezőben hányszor fordul elő a 4CH kód. A DE regiszterpárt most számlálónak fogjuk használni. (Bár elég egy regiszter is, mivel max. 1A db 4C lehet a mezőben. Számláljunk D-ben!)

4550


OSZH
MV HL,$1F20
MV BC,$1A
LD D,$0
LD A,4CH
CPIR
JRNZ VEG
INC D
LD A,B
AND A
JRNZ OSZH
VEG RET

6.2. Logikai és adatláncra vonatkozó utasítások listája

AND s Művelet: A and s A
Bitenkénti logikai ÉS-műveletet végez az A regiszter és az s által kijelölt byte között. Az eredményt az A regiszter tárolja.

Az s operandus lehet:

A jelzőbitek a következő módon állnak be az utasítás végén:

OR s Művelet: A or s A
Az utasítás bitenkénti logikai VAGY műveletet végez az s operandus által kijelölt byte és az A regiszter tartalma között. Az eredményt az A regiszter tárolja.

Az s operandus lehet:

Az utasítás végén a jelzőbitek a következeképp állnak be:

XOR s Művelet: A xor s A
Az utasítás bitenkénti logikai KIZÁRÓ VAGY műveletet végez az A regiszter tartalma és az s operandus által kijelölt byte között. Az eredményt az A regiszter tárolja.

Az s operandus lehet:

A jelzőbitek a következeképp állnak be az utasítás végén:

LDI Művelet:
(HL) (DE)
DE+1 DE
HL+1 HL
BC-1 BC
Az utasítás egy byte-ot helyez át a memória egyik rekeszéből a másikba, miközben a DE, HL és BC regiszterek tartalma a fenti módon változik.

A jelzőbitek értékei:

LDIR Művelet:
(HL) (DE)
DE+1 DE
HL+1 HL
BC-1 BC
Az utasítás egész byte-láncot visz át. Ciklikusan ismétlődik addig, míg BC = 0 nem lesz. Ha a BC regiszterpár tartalma már induláskor nulla volt, akkor az áthelyezett byte-ok száma 64 Kbyte lesz.

A jelzőbitek a következőképp állnak be:

LDD Művelet:
(HL) (DE)
DE-1 DE
HL-1 HL
BC-1 BC
Az utasítás egy byte-ot visz át a memória egyik rekeszéből a másikba de úgy, hogy HL és DE egy mező végére mutat.

A jelzőbitek állása az utasítás végén:

LDDR Művelet:
(HL) (DE)
DE-1 DE
HL-1 HL
BC-1 BC
Az utasítás byte-láncot helyez át a memória egyik mezejéből a másikba úgy, hogy induláskor DE és HL a mezők utolsó címeit tartalmazzák, A mező hosszát BC-ben kell megadni, ha az nulla, akkor 64 Kbyte áthelyezése történik meg.

A jelzőbitek:

CPI Művelet:
A - (HL)
HL+1 HL
BC-1 BC
Az utasítás összehasonlítja a HL regiszterpár által megcímzett rekesz tartalmát az akkumulátor tartalmával és beállítja a jelzőbiteket. A HL tartalma inkrementálódik, a BC-é pedig dekrementálódik.

A jelzőbitek értékei:

CPIR Művelet:
A - (HL)
HL+1 HL
BC-1 BC
Ez az utasítás abban különbözik a CPI-től, hogy ciklikusan ismétli önmagát mindaddig, amíg vagy
(HL) = A vagy
BC = 0 nem lesz.

A megállás okát a jelzőbitek fogják mutatni:

CPD Művelet:
A - (HL)
HL-1 HL
BC-1 BC
Az utasítás összehasonlítja a HL regiszterpár által megcímzett rekesz tartalmát az akkumulátor tartalmával és beállítja a jelzőbiteket. A HL regiszterpár tartalma dekrementálódik és a BC tartalma is.

A jelzőbitek értékei:

CPDR Művelet:
A - (HL)
HL-1 HL
BC-1 BC
Az utasítás az akkumulátor tartalmával egyező első byte-ot keresi ki egy byte-láncból úgy, hogy az összehasonlítást a mező végénél kezdi, s byte-onként halad előre. Az utasítás akkor áll le, ha vagy
A = (HL) vagy
BC = 0.

A jelzőbitek a következőképp állnak be:

Byte-láncok átvitelét memória és periféria között ld. az I/O utasításoknál.

7. Bitet állító és léptető utasítások

7.1. Utasítások, programok
A bit-műveleteknek nagy jelentősége lehet akkor, ha nagy adatmennyiséggel dolgozunk, és egy-egy paramétert lehet igennel - nemmel jellemezni. Pl. egy lakáscsere-közvetítő programnál, ahol ilyen kérdések létezhetnek: a felajánlott lakás öröklakás, tanácsi, van lift, stb. a válasz számára nem kell egész byte-ot lefoglalnunk, hanem csak egy bitet. Ez nagy memóriaterület megtakarítást jelent, ami fontos, hisz ott vannak még a keresett lakások adatai is, és ahhoz, hogy a munka hatékony lehessen, sok lakás adatának kell a memóriában lennie.
Bitet állító- és tesztelő utasítások a következők:

RES
Egy meghatározott byte meghatározott bitje 0 lesz. Pl: RES 7,A: az A regiszter 7. bitje legyen nulla.

SET
Egybe állító utasítás. Pl. SET 4,(HL): a memóriában annak a byte-nak a 4. bitje, melynek címe a HL regiszterpárban van, egybe áll.

BIT
A kijelölt bit negáltja a Z flagbe kerül. Pl. ha az A regiszter tartalma 05H, akkor a BIT 2,A hatására:

A regiszter tartalma: 0 0 0 0 0 1 0 1 (a bitek számozása: 7 6 ... 2 1 0)
A 2. bit = 1, negáltja 0; Z = 0

Írjunk egy programot, mely megszámolja, hogy egy mező byte-jai közül hány db páros. Egy szám páros, ha legalacsonyabb (nulladik) helyértékű bitje 0. A páros byte-ok száma a D regiszterben jelenjék meg.

A programrészlet a BITT címkénél kezdődik. Először leolvasunk adatokat a memóriába, a számlálás eredménye a SZAM nevű byte-ban jelenik meg.



OLV




OL








OLV1


SZAM
MEZO
PUFH
BITT
ORG 100H
JP BITT
CALL OL
CP 0DH
RET Z
DJNZ OLV1
RET
PUSH HL
PUSH DE
PUSH BC
LD C,1
CALL 5
POP BC
POP DE
POP HL
RET
LD (HL),A
INC HL
JR OLV
DEFS 1
DEFS 10H
EQU 11H
LD HL,MEZO
LD B,PUFH
CALL OLV
LD A,B
CP 0
JP Z,VIZSG
LD A,0
NL


VIZSG




BIT1

NOV




TOVA
LD (HL),A
INC HL
DJNZ NL
LD D,0
LD A,0
LD (SZAM),A
LD HL,MEZO
LD B,10H
BIT 0,(HL)
JP NZ,TOVA
INC HL
DJNZ BIT1
LD A,D
LD (SZAM),A
RET
INC D
JP NOV
END

Bitet állító utasítás meg az SCF, mely 1-es értéket ír a CY flag-be és a CCF is, mely negálja CY flaget.

A léptető utasításokat elsősorban azoknál az aritmetikai műveleteknél használjuk, amelyeket a gép alaputasításai nem tudnak (szorzás, osztás).
A léptetés (step - lépni) mindig egy bitnyi elmozdulást jelent, Háromféle lehet:

SLA

S = (step) lépés,
L = (left) balra,
A = (arithmetical) számtani

SRL

R = (right) jobbra,
L = logikai

SRA

7 bit: változatlan,
6 bit: rájön a 7. bit,
5. bit: rájön a 6. bit stb.,
Vegyük észre, hogy a C flag (CY) mindig a mozgás irányába kerül!

Rotáció (rotation = körforgás) négyféle van:

RL

RR

RLC

RRC

A digit rotáció is kétféle lehet:

RLD

RRD

A speciálisan akkumulátort mozgató utasítások a CY flag-en kívül nem érintik a tesztelhető jelzőbiteket, míg az "általános" operandussal rendelkező utasítások a flag-eket az új byte-tartalomnak megfelelően beállítják

7.2. Bitet állító és léptető utasítások listába

Beállítás.

RES b,m Művelet:
0 mb
Az utasítás 0-ba állítja az m operandus által jelölt byte b-edik bitjét. Az m operandus lehet:

A flag-ek állapota nem változik.

SET b,m Művelet:
l mb
Az utasítás 1-be állítja az m operandus által jelölt byte b-edik bitjét. Az m operandus lehet:

A flag-ek állapota nem változik.

BIT b,m  
Az utasítás hatására a kijelölt byte kijelölt bitjének negáltja megy a Z flagbe. A kijelölt byte lehet:

A flag-ek értéke:

CCF  
Az utasítás invertálja a C flag tartalmát. Jelzőbit-értékek:
SCF Művelet:
l CY
Az utasítás a CY flaget 1-be állítja. A jelzőbitek állása:

Akkumulátor rotálása

RLA
Az akkumulátor tartalma az átvitel-biten (C-CY) keresztül egy helyiértékkel balra tolódik. A jelzőbitek állapota:

RLCA
Az akkumulátor tartalma egy helyiértéket balra lep, a 7.bit a 0. bitbe kerül. Az akkumulátor léptetés előtti 7. bitje az CY bitbe is belép. A jelzőbitek állapota:

RRA
Az akkumulátor tartalma az átvitel-biten keresztül egy helyiértékkel jobbra tolódik. A jelzőbitek állapota:

RRCA
Az akkumulátor tartalma egy helyiértékkel jobbra lép, a 0. bit tartalma a 7. bitbe kerül. A 0. bit a CY-be is belép. A jelzőbitek állapota:

Nem akkumulátort rotáló utasítások

RL m
Az utasítás a CY biten keresztül egy helyiértékkel balra lépteti az m operandus által kijelölt byte-ot. Az m operandus jelenthet

A jelzőbitek értékei:

RLC m
A m operandus által specifikált byte tartalmát lépteti balra úgy, hogy az eredetileg 7. bit a byte 0. bitjének helyére lép. A 7. bit a CY flag-be is betöltődik. A m operandus jelölhet:

A jelzőbitek állapota:

RR m
Az m operandus által kijelölt byte egy helyiértékkel jobbra lép a CY biten keresztül. Az m operandus jelölhet

A jelzőbitek értékei:

RRC m
Az m operandus által meghatározott byte-ot léptetni jobbra úgy, hogy a byte 0. bitje a byte 7-es bithelyére lép. A 0-ás bit ezen kívül a C flag-be is bekerül. Az m operandus jelölhet:

A jelzőbitek értékei:

SLA m
Az utasítás az m operandus egy helyiértékkel balra történő aritmetikai léptetését végzi. A 0-ás bitbe 0 kerül, a 7. bit pedig a CY bitbe lép. Az m operandus jelölhet:

A jelzőbitek értékei:

SRA m
Egy helyiértékkel való aritmetikai jobbra léptetés. A 7-es bit eredeti tartalma változatlan marad és betöltődik a 6-os bitbe is. A 0 bit a CY-be kerül. Az m operandus jelölhet:

A jelzőbitek értékei:

SRL m
Az utasítás az m operandus tartalmát egy helyiértékkel jobbra tolja úgy, hogy a 7, bitbe 0 lép bele, a 0-ik bit pedig a C flagbe kerül. Az m által jelölt operandus lehet:

A jelzőbitek állapota:

RLD

Digitléptetés balra az akkumulátoron keresztül. A HL regiszterpár által specifikált byte kisebb helyiértéke átlép a rekesz nagyobb helyértékű digitjére, a digit eredeti tartalma pedig az A regiszter kisebb helyértékű félbyte-jára. Az A eredeti kisebb helyértékű digitje a HL által specifikált rekesz alsó félbyte-jára kerül.
A jelzőbitek a művelet végrehajtása után

RRD

Digitléptetés jobbra az akkumulátoron keresztül. Az A regiszter kisebb helyértékű digitje kerül a HL által kijelölt rekesz nagyobb helyértékű digitje helyére, a digit eredeti tartalma a kisebb helyértékű digitre lép. A HL által mutatott rekesz kisebb helyértékű digitjének eredeti tartalma az akkumulátor kisebb helyértékű digitjébe kerül. Az akkumulátor nagyobb helyértékű digitje nem változik. A jelzőbitek értéke a művelet végrehajtása után:

8. Aritmetikai műveletek

8.1. Utasítások, programrészletek
A Z80 processzor hexadecimális, fixpontos aritmetikával dolgozik. Számábrázolása a következő:

D7: előjel (+/-), D6-D0: szám (abszolút érték), tizedesvessző (,)

Vagyis egy byte ábrázolási tartománya:

-128 n 127

A negatív számokat kettes komplemensben kéri. Egy szám k biten ábrázolható egyes komplemense az a szám, amely őt a k biten ábrázolható legnagyobb abszolút értékű számra egészíti ki. Pl. 4 biten a legnagyobb abszolút értékben ábrázolható szám Fh = 15d.
Itt a 3-nak az egyes komplemense a 12d = Ch, vagyis binárisan az inverze (3+12 = 15).

0011
1100

A kettes komplemens MAX +1-re, vagyis 4 biten 16d = 10h egészít ki. (Vagyis 3-nak a 13d = 0DH).

Az akkumulátor tartalmának 1-es komplemensét a CPL utasítás, a kettes komplemensét pedig a NEG utasítás hatására számítja ki a gép.
Vegyük észre, hogy ha a szám pozitív, akkor első (legnagyobb helyértékű) bitje 0, ha szám negatív, akkor ez a bit 1-es.
Pl.:

+7d =


-7d =
0111b   pozitív szám
1000b   egyes komplemens (inverze)
+    1
1001b   kettes komplemens

(A hetet a kilenc egészíti ki 16-ra.)
A 8 biten ábrázolható számtartomány tehát a következő:

Vegyük észre, hogy az összekapcsolt számok csak legmagasabb helyértékű bitjükben különböznek.

A Z80-nak összeadásra és kivonásra van alapművelete. Byte-műveletnél az egyik tag, illetve a kivonandó mindig az A regiszter, szóműveletnél a HL regiszterpár (ill. összeadásnál IX vagy IY is).
Mindkét alapműveletnél van olyan utasítás, amely nemcsak állítja a CY-t (természetesen az aritmetikai utasítások az összes flaget állítják), hanem fel is használja. Erre akkor van szükség, amikor több byte-os adatunk van, s a CY-t ténylegesen fel kell használnunk átvitelként a magasabb helyértékű byte-ok összeadásánál (illetve áthozatként kivonásnál).
Nézzük meg a következő példákat:

Ha azt tételezzük fel pl., hogy 365Ah-t és 01DCh-t kell összeadnunk, akkor először

LD A,$5A
ADD A,$DC
LD ER1,A
LD A,$36
ADC A,$01
LD ER2,A



az utasítás hatására keletkezik CY, és ezt a magasabb helyértékhez majd hozzá kell adni

Az összeadó utasításhoz hasonlóan kivonást is két módon végezhetünk. A SUB utasítás kivonja az operandust az: A regiszter tartalmából, az SBC pedig az A -n-CY műveletet hajtja végre.
Az eredmény kiértékeléséhez még szükséges a túlcsordulás flag vizsgálata is. Túlcsordulás akkor keletkezik, ha az aritmetikai művelet következtében az eredmény abszolút értéke olyan nagy lesz, hogy az 7 biten már nem fér el, hanem "rácsordul" az előjelbitre és azt így meghamisítja. Pl.:

127d + 1d = 128d
0111 1111b + 1b = 1000 0000b

Az eredmény "mínusz nulla" (illetve kettes komplemensben -128), ami hibás. Ilyenkor a P/V = 1 (két pozitív szám összege nem lehet negatív).

8.2. BCD számok helyreállítása
Mint mondottuk, az aritmetika hexadecimális számokra számít, ezért ha decimális számokat adunk neki, hibás lesz az eredmény. Van azonban egy utasítás, a DAA, mely korrigálni tud a következőképpen:

Művelet
"CY a DAA
előtt
Az eredmény
felső bitjei (7-4)
"H" a DAA
előtt
Az eredmény
alsó bitjei (3-0)
A byte-hoz
hozzáadott szám
C az összeadás
után
ADD
ADC
INC
0
0-9
0
0-9
00
0
0
0-8
0
A-F
06
0
0
0-9
1
0-3
06
0
0
A-F
0
0-9
60
1
0
9-F
0
A-F
66
1
0
A-F
1
0-3
66
1
1
0-2
0
0-9
60
1
1
0-2
0
A-F
66
1
1
0-3
1
0-3
66
1
SUB
0
0-9
0
0-9
00
0
SBC
0
0-8
1
6-F
FA
0
DEC
1
7-F
0
0-9
A0
1
NEG
1
6-F
1
6-F
9A
1

Tehát, ha az A regiszter és a HL regiszterpár által mutatott memóriarekesz decimálisán kódolt bináris számot tartalmaz, és velük aritmetikai műveletet kell végeznünk, akkor vagy átalakítjuk a számokat hexadecimálisan kódolt számmá, és ilyenkor gond nélkül alkalmazhatjuk az ADD, illetve a SUB utasítások valamelyik fajtáját, s az eredményt (szükség esetén) a kiírás előtt visszaalakítjuk decimálissá, vagy hagyjuk a decimális számrendszert, de mindig ügyelünk a DAA használatára.
Pl., ha A tartalma be van töltve:

SBC A,(HL)
DAA

Példaként adjuk össze a "33" és "44" BCD számokat!

  LD A,33H
ADD A,44H
DAA
LD (CIM),A

Eredményként "77"-et kapunk, amit a CÍM nevű címen tárolunk.

Aritmetikai művelet a NEG utasítás is, mely egy szám kettes komplemensét állítja elő oly módon, hogy kivonja a számot 0-ból.

Pakolt formátumú binárisan kódolt decimális számnak nevezzük azt a számot, mely egy digiten egy decimális számjegyet tartalmaz. (BCD formátum).
Pl.:

03
16
54
1-1 byte

= 31554d

8.3. Aritmetikai műveletek listája

DAA Művelet:
az akkumulátor decimális korrekciója
Az utasítás a BCD számok összeadásakor (ADD, ADC, INC) és kivonásakor (SUB, SBC, NEG) szükségessé váló akkumulátor-tartalom korrekciót végzi el. A jelzőbitek állása a művelet után:
ADD A,s Művelet:
A+s A
Az utasítás hozzáadja az s operandusban kijelölt byte-ot az A regiszter tartalmához. Az eredményt az A regiszter tárolja.
Az s operandus lehet:

A jelzőbitek állapotai a művelet után:

ADD HL,ss Művelet:
HL+ss HL
Az utasítás az ss által kijelölt regiszterpár (BC, DE, HL, SP) tartalmát adja hozzá a HL regiszterpár tartalmához, az eredményt a HL regiszterpár tárolja.
A jelzőbitek állapota a művelet után:
ADD IX,pp Művelet: IX+pp IX
ADD IY,rr Művelet: IY+rr IY
Az utasítás összeadja az operandusban kijelölt regiszterpárok tartalmát. Az eredményt az aktuális indexregiszter tárolja.

A jelzőbitek állása megegyezik az ADD HL,ss utasításnál ismertetettekkel.

ADC A,s Művelet:
A+s+CY A
Az utasítás hozzáadja az A regiszter tartalmához az operandusban kijelölt byte-ot és az átvitelbitet is. Az eredmény az A regiszterben fog megjelenni. Az s operandus lehet:

Jelzőbitek állapota a művelet végén:

ADC HL,ss Művelet:
HL+ss+CY HL
Az utasítás összeadja az operandusban kijelölt regiszterpárok tartalmát és az átviteli bitet. Az eredményt a HL regiszterpár tárolja.
Az ss operandus lehet: BC, DE, HL, SP
A jelzőbitek állapota a művelet végén:
SUB s Művelet:
A-s A
Az utasítás kivonja az A. regiszter tartalmából az s operandus által jelzett byte-ot. Az eredményt az A regiszter tárolja. Az s lehet:

A jelzőbitek értékei:

SUB A,s Művelet:
A-s-CY A
Az utasítás kivonja az A regiszter tartalmából az operandusban kijelölt byte-ot és a CY flag tartalmát. Az eredményt az A regiszter tárolja. Az s operandus lehet:

A jelzőbitek az utasítás végén:

SBC HL,ss Művelet:
HL-ss-CY HL
Az utasítás a HL regiszterpárból kivonja az ss regiszterpár és a CY flag tartalmát. Az eredményt a HL regiszterpár tárolja. Az ss lehet, BC, DE, HL vagy SP. A jelzőbitek állását az utasítás végrehajtása után ld. az SBC s utasításnál.

NEG Művelet:
0-A A
Az utasítás az akkumulátor tartalmának 2-es komplemensét képezi és az eredményt az akkumulátorba tölti. A jelzőbitek állapota:
CPL  
Az utasítás invertálja az A regisztertartalmát (1-es komplemensét képezi), az eredmény az A regiszterben jelenik meg. A jelzőbitek:

8.2. 8-bites összeadás
Az első példa igen egyszerű: Adjunk össze két 8-bites operandust, az OP1-et és az OP2-öt, amelyek a memóriában a CIM1 és CIM2 címen találhatók. Az összeget nevezzük el ER-nek és legyen a címe a memóriában CIM3.

  LD A,(CIM1)
LD HL,CIM2
ADD A,(HL)
LD (CIM3),A

Az összeg az akkumulátorban keletkezik. A Z80 mikroprocesszornál az aritmetikai műveletek eredményei általában az akkumulátorba kerülnek.
Az OP1 és OP2 összegét tehát az összeadás eredményét az akkumulátor tartalmazza. A programunk akkor teljes, ha az eredményt kivisszük az akkumulátorból egy konkrét memória címre (CIM3), azaz itt tároljuk az eredményt. Ez a negyedik utasítás a programunkban:
LD (CIM3), A

Meg kell jegyeznünk, hogy a mikroprocesszor bármely regiszterének, valamint bármely memóriabeli helynek a tartalma nem változik (azaz ugyanaz marad) az olvasási utasítás végrehajtása után. Csak és kizárólag a regiszterbe való írás esetén változik meg annak tartalma. Ebben a példában, a CIM1 és CIM2 című memóriahelyek tartalma változatlan marad végig a programban. De az ADD utasítás végrehajtása után az akkumulátor tartalma megváltozik, mivel az akkumulátorba beíródik az aritmetikai és logikai egység (Arithmetic -Logical Unit-ALU) kimenete, így az akkumulátor korábbi tartalma elvész.

8.3. 16-bites összeadás
A 8-bites összeadás csak 8-bites számok összeadását teszi lehetővé, azaz a 0 és 255 közé eső számokét, ha abszolút binárisokat használunk. A legtöbb gyakorlati alkalmazás szükségessé teszi a 16 bites vagy még annál több bites számok összeadását. Két Példát mutatunk a 16-bites számok aritmetikájára is. Mindezek kiterjeszthetők 24,32 vagy még több bitre (mindig 8 többszöröseire). Tegyük fel az első operandus a CIM1 és a CIM1-1 memóriahelyeken van tárolva. Mivel az OP1 egy 16-bites szám, ezért két 8-bites memóriahelyet foglal el. Hasonlóan, az OP2 és CIM2 és a CIM2-1 címeken lesz. Az eredmény pedig a CIM3 és a CIM3-1 című memóriahelyek re kerül.
A program logikai felépítése pontosan olyan mint az előzőnél. Először, a két operandus alsó felét adjuk össze. Az alacsonyabb biteken keletkező átvitelt automatikusan tárolja egy belső átvitel (carry) bitben ("C"). Ezután összeadjuk a két operandus felső felét, valamint az átvitelt, az eredmény pedig a memóriában keletkezik.

  LD A,(CIM1)
LD HL,CIM2
ADD A,(HL)
LD (CIM3),A
LD A,(CIM1-1)
DEC HL
ADC A,(HL)
LD (CIM3-1),A

A program első négy utasítása teljesen azonos az előbbi 8-bites összeadás utasításaival. Ez eredményezi az OP1 és OP2 operandusok legkisebb helyiértékű 8 bitjének az összeadását (0-7 bit). Az eredmény a CIM3 című memóriarekeszben lesz elhelyezve.
Bármely összeadás során a keletkező átvitelbit (ami vagy 0 vagy 1), betöltődik a jelző regiszter (F) C átvitel bitjébe. Ha két szám összeadásakor átvitel is keletkezik, akkor a C bit 1-gyel lesz egyenlő, amennyiben két 8-bites szám összeadásakor nem keletkezik átvitel, akkor a C értéke 0 lesz.
A program következő négy utasítása lényegében hasonló mint a 8-bites összeadó program. Itt az OP1 és OP2 operandusok magasabb helyértékű részeit adjuk össze és még ezeken kívül hozzáadjuk az átvitelt és az eredményt tároljuk a CIM3-1 címen. Ennek a programnak a végrehajtása után a 16-bites eredmény megtalálható a CIM3 és a CIM3-1 címeken a memóriában.
Láthatjuk, hogy a programban a két összeadó utasítás eltér egymástól, az első esetben ADD utasítást használtunk, míg a második esetben ADC utasítást. Az előzőnél a két operandust az átvitel tekintetbevétele nélkül adtuk össze, míg az utóbbinál hozzáadtuk az átvitelt is. Ez szükséges a helyes eredményhez. Felvetődik a kérdés, hogy mi történik akkor, ha az operandusok magasabb helyiértékű részének összeadásakor átvitel keletkezik? Két lehetőség van, vagy hibajelzésnek tekintjük, minthogy az eredmény nem fér el 16 biten, vagy a program még magában foglal egy átvitelhozzáadást, egy még magasabb helyiértékű bájthoz.
Az előbbiekben bemutatott programok mindig az akkumulátort használták a műveletek végrehajtásakor. Most példát mutatunk egy olyan 16-bites összeadásra, amikor nem az akkumulátort, hanem más regisztereket alkalmazunk.

  LD HL,(CIM1)
LD BC,(CIM2)
ADD HL,BC
LD (CIM3),HL

Ebben az esetben a Z80 a H és L regisztereket, mint egy 16 bites akkumulátort használja.

8.4. Számok kivonása
A 8-bites kivonás rendkívül egyszerű.

  LD A,(CIM1)
LD HL,CIM2
SUB A,(HL)
LD (CIM3),A

Most nézzük meg a 16-bites kivonás végrehajtását. Mint eddig is, tehát OP1 és OP2 operandusok a CIM1 és CIM2 memóriarekeszben vannak.

  LD HL,(CIM1)
LD DE,(CIM2)
AND A
SBC HL,DE
LD (CIM3),HL

Meg kell jegyeznünk, hogy a Z80 utasítás készletében a dupla regiszteres összeadásra két utasítás van, az ADD és ADC, míg kivonás csak egy van: az SBC.
A különbség a két 16-bites művelet programjában egyrészt az SBC és az ADD utasítás valamint az AND A utasítás, amely az átviteljelzőt nullázza, mivel itt a SBC utasítás (substract with carry - kivonás átvitellel) automatikusan átvitellel számol, így a kivonás végrehajtása előtt azt nullázni kell.

8.5. 16-bites BCD összeadás
A 16-bites BCD összeadás pontosa olyan egyszerű, mint a 16 bites bináris összeadás.

  LD A,(CIM1)
LD HL,CIM2
ADD A,(HL)
DAA
LD (CIM3),A
LD A,(CIM1+1)
INC HL
ADC A,(HL)
DAA
LD (CIM3+1),A

9. Egyéb utasítások

9.1. Utasítások, programrészletek
Gyakran előfordul az az eset, hogy "elfogynak" a regiszterek. Annyiféle adattal kell regiszterben dolgoznunk, hogy szükségünk van a regisztertartalmak mentésére. Ilyenkor használjuk az EXX és EX AF utasításokat.
Logikailag más eset, amikor funkció miatt kell cserélnünk. Pl. tételezzük fel, hogy a HL regiszterpárban van egy konstans, állandó érték. Fut egy program, amely időnként rákerül a következő programágra:

Észrevehettük már, hogy míg 8 bites adatok esetén az A regiszter az akkumulátor, 16 bites adatok esetén ez a szerep a HL regiszterpárra van kiosztva. Így pl. aritmetikai műveletek esetén az eredmény a HL regiszterpárban keletkezik.
Nincs olyan utasítás, hogy DE + HL DE
csak olyan, hogy DE + HL HL.
Ezért szükséges a két regiszterpár tartalmának felcserélése, majd visszaállítása.

JP NZ,valahova
EX DE,HL
ADD HL,DE
EX DE,HL

A NOP utasítás ún. üres utasítás. Olyan esetekben használják, ha pl. a memóriába beírnak egy programrészt, pl. ugrásnál, megbecsülik, hogy 4508-ra esik majd az az utasítás, ahová ugrani kell. Aztán kiderül, hogy már a 4505-ön befejeződik az előző szegmens, ilyenkor valamivel össze kell kötni a két részt. Erre jó a NOP.





4505


4508
JP 4508
-
-
-
LD A,B
NOOP
NOOP
ADD A,(HL)

Egyébként a NOP memóriafrissítő műveletet végez, amelyre a dinamikus memóriáknál van szükség. Ugyanis gyakori a mikrogépeknél a dinamikus memóriák használata. Ezekben egy-egy bit tároló elemét - jó közelítéssel! - egy kondenzátornak foghatjuk fel, amelynek töltése a bit értékétől függ. A kondenzátor idővel kisül, elveszti töltését, vagyis elveszik a bit értéke. Ezért időnként újra kell tölteni a kondenzátort, vagyis a memóriát frissíteni kell. A processzor minden utasítása végez ilyen frissítést, természetesen a software számára láthatatlan módon. Azt, hogy ez a frissítés éppen hol tart, a memória melyik rekeszénél, azt a gép az R regiszterben tárolja.

Minden programot valamilyen "állj" utasítással kell befejezni. Ez természetes, hiszen ha nem lenne, akkor a gép venné a program utolsó utasítása utáni memóriacímet, kiolvasná a tartalmát (hogy mi van ott, azt nem tudjuk, lehet, hogy pl. az előzőleg futott program adatai) ezt a tartalmat utasításnak fogná fel, végrehajtaná... vagyis "elszállna" a programunk. Állj utasításnak általában egy RET utasítást használnak, ami visszatérést jelent a firmware-be (ill. az operációs rendszerbe). Ugyanis a monitorból egy CALL utasítással lépnek a felhasználói programra, vagyis a felhasználói program felfogható, mint a monitor egy külső szubrutinja.
Létezik a gépben egy HALT nevű utasítás, ez azonban nem állj-t, hanem várakozást jelent, méghozzá mindig külső, fizikai jelre, amely vagy IT lehet, vagy egy RESET (általános törlés).

9.2. Egyéb utasítások listája

EX (SP),qq Művelet:
qql (SP)
qqh (SP+1)
A qq regiszterpár kisebb helyértékű byte-ja az SP regiszterpár tartalma által meghatározott memóriacímen levő byte tartalmával cserélődik ki, a nagyobb helyértékű byte pedig a következő memóriacímen (SP+1)-en levő byte-tal cserélődik ki.
A qq által jelzett regiszterpár lehet a HL, IX vagy IY.
A flagek állapota nem változik.

EX AF,AF' Művelet:
AF AF'
Az AF és AF' regiszterpárok (akkumulátor és flag-regiszter) tartalma felcserélődik.
A flag-ek állapota nem változik.

EX DE,HL Művelet:
DE HL
A HL és a DE regiszterpárok tartalma felcserélődik.
A flag-ek állapota nem változik.

EXX Művelet:
(BC) (BC')
(DE) (DE')
(HL) (HL')
Mindhárom regiszterpár tartalma felcserélődik vesszős párjának tartalmával. A flag-ek állapota nem változik.

HALT Művelet:
memóriafrissítés
Az utasítás a következő IT-kérés vagy törlésparancs beérkezéséig felfüggeszti a processzor működését. Várakozás közben memóriafrissítést végez.

NOP Művelet:
memóriafrissítés
Üres utasítás, a CPU csak memóriafrissítést végez.

IV. Input-output műveletek

1. Adatátviteli módok
Eddig a mikroprocesszoros rendszer belső szervezésével, műveletvégzésével és programozásával foglalkoztunk. A rendszer azonban nem elszigetelten működik, hanem kapcsolatban van a külvilággal. A rendszer hatékonysága szempontjából igen lényeges, hogy ezt a kapcsolatot milyen módon hozzuk létre és tartjuk fenn a processzor és a többi készülék között. A CPU jó kihasználásához az adatátvitelnek gyorsnak kell lennie.
A be- és kimeneti információk általában két csoportba tartoznak:

Adatátviteli irány lehet:

Az átvitelt mindig a CPU kezdeményezi.
A perifériákat gyártó cégek különböző készülékeket állítanak elő. Pl. másféle parancsjeleket és időzítéseket követel a LOGABOX mátrixnyomtatója mint a MANESMANN-é, nem is szólva arról, hogy másképp kell vezérelni egy mágnesszalagos egységet mint egy nyomtatót. A CPU és a periféria kapcsolatát egy csatolóegység (IF, interface vagy controller) biztosítja. Az IF-ek a mikroprocesszortól kapott jeleket úgy alakítják át, hogy a rájuk kapcsolt periféria megértse azokat, és a periféria jeleit, úgy alakítják át, hogy a processzor megértse azokat.
Magyarországon ahány géptípus, annyi féle I/O megoldás létezik. Így pl. a Z80-as processzorhoz INTEL által gyártott illesztő, vagy saját tervezésű nagyintegráltságú csatoló, memóriarekeszként felfogott perifériacím és külön címkiterjesztés, stb. Mivel egy-egy I/O megoldás önmagában is egy külön kis könyv lehetne, ezért itt a legalapvetőbb és legáltalánosabb megoldás ismertetésére vagyunk kénytelenek szorítkozni.
Az adatátviteli módok több szempont szerint csoportosíthatók:

Az adatátviteli folyamatot mindig a CPU kezdeményezi, de a tényleges adatátvitelnél nem mindig kell az adatnak a CPU-n áthaladnia.

Indirekt adatátvitel:

Direkt memóriahozzáférés:

Attól függően, hogy egy CPU parancs hatására hány adat átvitele történik meg, lehet bites, byte-os, vagy blokkos adatátvitelről beszélni.

2. Z80 busz
Azt a kiépített pályát, melyen az adat és parancsjelek haladnak busz-nak nevezzük. A Z80-as processzor busza három részre osztható:

3. A megszakítás

3.1. A megszakítás elve
Mielőtt az egész átviteli folyamatot áttekintenénk, ismerkedjünk meg az IT = interrupt = megszakítás fogalmával. Az IT fizikai esemény, amely a csatolón egy flip-flop bebillenését okozza, ami IT kérést indít el a CPU felé. Ennek hatására - ha a feltételek megvannak hozzá - a CPU megszakítja éppen futó programját és egy másik - az IT-t kérő perifériával foglalkozó - programot kezd futtatni. A csatoló IT kérést akkor ad ki, ha

Az IT hatására elindult program egy RETI utasítással fejeződik be, amely deaktiválja az IT-t és módot ad arra, hogy az előzőleg megszakított program folytathassa munkáját.
Egy átvitel általában a következőképp zajlik (pl. egy OUTPUT):

  1. A CPU /SW utasítás hatására pl. OUT/ felszólítja a csatolót (IF-t) működésre, megadja az átviteli módot.
  2. Az IF ellenőrzi, hogy a periféria kész-e az adat fogadására. Ha nem, akkor jelez, ha igen, továbbengedi a folyamatot (és a perifériát foglalttá nyilvánítja).
  3. Ha a periféria szabad, akkor az IF átveszi a kiírandó adatot a CPU-tól és továbbítja a perifériának.
  4. A CPU programja tovább fut, a periféria eközben feldolgozza a kapott adatot (pl. nyomtat).
  5. Ha a periféria kész (vagy hiba történt), ezt közölni kell a központi egységgel, ezért a periféria megkéri a saját csatolóját, hogy kérjen IT-t a CPU-tól.
  6. Ha minden feltétel megvan, akkor a csatoló IT-t kér, és a CPU elfogadja az IT kérést.
  7. Az IT hatására a gép áttér az IT-t kérő periféria IT programjának végrehajtására, amely eldönt(het)i, hogy engedélyezni lehet a további munkát, pl. új paranccsal újabb adat küldését, vagy szükséges-e valamilyen módon változtatni a feldolgozáson (pl. hiba esetén kiírni a konzolon, hogy baj van).
  8. Az IT rutin végén kötelezően egy RETI családba tartozó utasítás van, mely egyrészt jelzi a csatolónak, hogy IT-rutinja lefutott, másrészt törli a periféria foglaltságát, mely most már képes új adat vagy parancs fogadására, a gép pedig visszatér a főprogramra.

Többször említettük, hogy ha "minden feltétel megvan" fogadja el a CPU az IT kérést. Ugyanis megtörténhet, hogy pl. a

Hogy a CPU elfogadja-e ezt az IT kérést, az attól függ, hogy ki a priorabb (fontosabb). A csatolókat ugyanis felfűzik egy prioritási láncra, amely biztosítja, hogy mindig a legpriorabb IF IT kérése jusson be a CPU-ba. Ha egy IT kérés bejutott a CPU-ba, és érvényre jutott, akkor az IT alprogram lefut.
Ezt a futást egy újabb, másik IT csak a következő esetekben szakíthatja meg:

  1. Ha az aktuális, a futó IT programban kiadunk egy ún.: El, IT engedélyező utasítást.
    El hatására, amennyiben az aktuális IT-nél priorabb IT kérés lép fel, az megszakíthatja a futó programot.
  2. Nem maszkolható IT kérés jut a CPU-ba.

A fenti ábra egy olyan esetet mutat, amikor valamennyi IT program engedélyezi, hogy őt nagyobb prioritású IT megszakítsa. Az ábrán először IT2 érkezett be a CPU-ba, s megszakította a főprogram futását. A gép az IT2-es programra tért át. Miközben azon dolgozott, egy 1-es jelű periféria is elkészült, de mivel neki kisebb a prioritása, IT-kérése nem jutott érvényre. A prioritási lánc a kérést nem engedi be a CPU-ba. IT3 azonban képes volt bejutni és meg is tudta IT2-t szakítani. Zavartalanul végig is tud futni, a végén kötelezően RETI utasítással fejeződik be. Ennek hatására a gép visszatér a legmagasabb prioritású várakozó programra, jelenleg a IT2-re, s mikor azt befejezte, csak akkor jut be a processzorba IT1. IT1 végrehajtása után a CPU folytatja a megszakított főprogramot, pl. azzal, hogy most adatot kér valamelyik perifériától.
A prioritási láncot a gép összeszerelésekor a hardware által határozzák meg. Pl. a mágnesszalagos egységnek van a legnagyobb prioritása, utána következik a sornyomtató stb.
Előfordulhat azonban a használat során olyan eset, hogy a programozó szeretné felülbírálni ezt a prioritási sort, mert a program során neki pl. a sornyomtató nagyon fontos, és azt akarja, hogy annak programját ne szakíthassa meg egyetlen más periféria sem, tehát a mágnesszalagos egység sem. Erre ad lehetőséget az ún. IT maszkolás. Ha egy IT programban nem adjuk ki az újabb megszakítást engedélyező El utasítást, akkor az azt jelenti, hogy hiába kérne IT-t egy csatoló, akár az aktuális IT-kérőnél priorabb is, a kérés bár a processzorba bejut, de nem kerül elfogadásra. Az ugyanis, hogy a mikroprocesszor elfogad egy IT kérést, egyben a többi letiltását (maszkolását) is jelenti. Ha az IT programban az El-t nem adjuk ki, akkor a következő IT csak a RETI utasítás után kerül elfogadásra. Nem az összes létező megszakítás maszkolható. Pl. hálózatkimaradásnál, amikor a feszültség egy meghatározott szintre csökken, még a gépnek van ideje néhány utasítás végrehajtására. Ezért ebben az esetben fellép egy ún. hálózatkimaradási IT, amelynek programja pl. a regiszterek tartalmát elmenti a memóriába. Ha a hálózat visszatér, akkor a memóriából egy ún. hálózatvisszatérési IT program feltölti a regisztereket, s a program dolgozik tovább, mintha nem is lett volna hálózatkimaradás. (Ez persze csak akkor működik, ha a gép rendelkezik olyan irható memóriaterülettel, amely hálózatkimaradás esetén nem veszti el az információt). Szóval, hiába maszkolnánk a hálózatkimaradási IT-t, azzal csak azt érnénk el, hogy a programunk teljesen elveszik, mert a kikapcsolás nyilván teljesen környezetfüggő dolog. Ezért vannak ún. nem-maszkolható IT-k is.

3.2. A Z80 IT-rendszere
Amit itt elmondhatunk, az egy meglehetősen fejlett hardware rendszert tételez fel, s ún. 2. IT osztálynak felel meg. Ugyanis a Z80 három IT osztályt különböztet meg software-szempontból, attól függően, hogy hol található az az utasítás vagy program, amelyet IT hatására végre kell hajtani.
A nullás IT osztálynál (IM0 utasítással lehet beállítani) a gép IT hatására azt az adatot fogadja el utasításnak, amelyet a csatoló küld be az adatvonalakon. A főprogram fut. Beérkezik az IT-kérés. IT kérés-vizsgálatot a CPU általában csak egy utasítás végrehajtása után végez, tehát az épp végrehajtás alatt álló pl. LD IX,04H utasítást még befejezi, a PC-t megnöveli (így az a következő utasításra mutat), s csak ekkor nézi meg, hogy jött-e IT. /Bár a teljesség kedvéért megjegyezzük, hogy van néhány kivétel. A nagyon hosszú, karakterlánccal foglalkozó utasításokat meg lehet bizonyos pontokon IT-vel szakítani. Ha jött IT, akkor a processzor elolvassa a buszon érkezett kódot, azt végrehajtja, majd folytatja a megszakított főprogramot.
Egyes IT módban (IM 1) az IT hatására végrehajtandó program a memóriában van, méghozzá fix címen, a 0038H rekeszben kezdődik. (Tulajdonképpen RST 7).

A főprogram fut. IT kérés érkezik. A folyamatban levő utasítás befejeződik. PC+1-PC. IT vizsgálat, ha jött IT, és egyes IT mód van beállítva, akkor a gép a PC tartalmát (mely a következő főprogram-utasításra mutat, illetve, ha az utolsónak végrehajtott utasítás ugrásparancs volt, akkor arra a címre, ahova ugrani kell) elmenti a stackbe, PC-be betölti a 0038H-t és ugrik PC tartalmára, vagyis az IT program feldolgozásába fog. Ha RETI utasítást talál, akkor dezaktíválja az IT-t, a stack tetején levő adatot, amely a főprogram következő utasításának a címe, visszatölti a PC-be, és a főprogram fut tovább.

Legfejlettebb a 2-es IT üzemmód. Ilyenkor létezik a prioritási lánc, több periféria kérhet IT-t, és természetesen mindegyikhez külön IT program tartozhat.
A gépnek ki kell tudnia választani, hogy melyik IT programra kell áttérnie. Erre szolgál az IT vektor.

A memóriában valahol - tetszőleges helyen - elhelyezzük pl. a 3-as IT szintű perifériához tartozó IT programot. Ennek a programnak a címe AIT3, és - mivel cím - természetesen 2 byte hosszúságú. Az összes létező IT programok címeit összegyűjt(-het)jük egy ugrótáblába, ahol első helyen áll mindig a cím alacsony helyértékű byte-ja, utána a magas helyértékű byte.
Az I. regiszter tartalmazza annak a memórialapnak a címét, ahol az IT programok címeit tartalmazó táblázat van. (Egy memórialap = 256 byte).
Az IT vektor az a szám, amely megmutatja, hogy a lap hányadik byte-ján kezdődik a program elejére mutató két byte-os cím. A periféria indítása előtt szükséges egy csatoló inicializálás, amely rutin - többek között - az IT vektort is közli a csatolóval.

A főprogram fut, elindít egy perifériát, melynek parancsokat, adatokat küld (vagy adatokat kap). Mikor a periféria végrehajtotta a kapott parancsot, akkor a csatoló IT-t kér, és az adatvonalakra teszi az inicializáló rutintól előzőleg már megkapott IT-vektort. A CPU elmenti a főprogram következő utasításának a címét a stack-be, beolvassa az IT vektort.
A processzor az I regiszter és az IT vektor által meghatározott memóriacímről kiolvassa a tartalmat, és azt, mint címet beteszi a PC regiszterbe. Ezután azt az utasítást hajtja végre, amelyre a PC mutat, tehát az aktuális IT programot. Amikor El utasítást talál, akkor szabaddá válnak az eddig esetleg várakozó IT-k, a RETI utasitás pedig dezaktíválja a végrehajtott IT-t. A processzor a várakozó legnagyobb prioritású program aktuális címét olvassa a PC-be.
Kiemelt eset, amikor egy nem maszkolható eszköz kér IT-t. Ez az 1-es IT módhoz hasonlít, ugyanis az IT program ebben az esetben is fix címen kezdődik, a 0066H-n.

4. A Z80 I/O utasításai

4.1. Adatbeviteli utasítások

IN A,(n) Művelet:
(n) A

Az utasítás abból a perifériaregiszterből, melynek címe a következőképp áll elő:

címbusz:
A-8 - A15 - A regiszter
A0 - A7 - n

beolvas az A regiszterbe egy adatbyte-ot.
A jelzőbitek értéke nem változik.

IN r,(C) Művelet:
(C) r

Az utasítás beolvas egy adatot egy perifériáról (pontosabban egy I/O-port regiszteréből).

címbusz:
A-8 - A15 - B regiszter
A0 - A7 - C

Az adatbuszon keresztül beküldött adat-byte az r operandus által kijelölt regiszterbe kerül (A, B, C, D, E, H vagy L).
A flag-ek állása az utasítás végrehajtása után:

Mikrogépek esetében ritka az a helyzet, amikor 256 db-nál több perifériaregiszter van, úgy hogy gyakorlatilag a B regiszter tartalma vagy nem kerül ki a buszra, vagy ha ki is kerül, nem használják fel. Ezért is építették be a következő utasításokat:

INI Művelet:
(C) (HL)
B-1 B
HL+1 HL

Egyetlen adatot olvas be a perifériáról.

Az utasítás végrehajtása során:

Jelzőbitek értékei:

INIR Művelet:
[ (C) (HL)
B-1 B ]   B=0-ig
HL+1 HL

Az utasítás végrehajtása során:

Az utasítás végrehajtása akkor fejeződik be, ha annyi byte beolvasása történt meg, hogy B regiszter tartalma nulla lett. Megjegyezzük, hogy az utasítás (a B-1 <> 0 következtében a PC visszaléptetése után) IT által megszakítható.
A jelzőbitek állása:

IND Művelet:
(C) (HL)
B-1 B
HL-1 HL

Az utasítás egyetlen byte-ot olvas be a perifériáról, miközben B is és HL tartalma is dekrementálódik.
A flag-ek állása:

INDR Művelet:
[ (C) (HL)
B-1 B ]   B=0-ig
HL-1 HL

Az utasítás egy egész blokkot olvas be a memóriába, közben B és HL tartalma dekrementálódik. Az utasítás végrehajtása akkor fejeződik be, ha a B regiszter tartalma nulla lett. Az utasítás megszakítható.
A jelzőbitek állása:

4.2. Adatkiviteli utasítások

OUT (n),A Művelet:
(A) (n)

Kivitel a perifériára.

Címbusz:
A8 - A15 - A
A0 - A7 - n

Az A regiszter tartalma az adatbuszra is rákerül, és az n által kiválasztott perifériaregiszterbe jut.

OUT (C),r Művelet:
r (C)

Az r operandusban megjelölt regiszterből kerül ki az adat a C által kiválasztott perifériaregiszterbe.
A jelzőbitek állapota nem változik.

OUTI Művelet:
(HL) (C)
B-1 B
HL+1 HL

Az utasítás egyetlen byte-ot visz ki, miközben
B: dekrementálódik,
HL: inkrementálódik.

A jelzőbitek állása:

OTIR Művelet:
[ (HL) (C)
B-1 B ]   B=0-ig
HL+1 HL

Az a különbség OUTI utasításhoz viszonyítva, hogy ez az utasítás kiviszi az egész blokkot a perifériára, vagyis az utasítás csak akkor ér véget, ha B = 0 lesz.
Az utasítás megszakítható.
A jelzőbitek értékei:

OUTD Művelet:
(HL) (C)
B-1 B
HL-1 HL

Egyetlen byte-ot visz ki. OUTI utasításhoz képest az a különbség, hogy HL is (mint a B) dekrementálódik.
A jelzőbitek értékeit ld. OUTI utasításnál.

OTDR Művelet:
[ (HL) (C)
B-1 B ]   B=0-ig
HL-1 HL

Blokk kivitele, HL és B tartalma dekrementálódik, amíg B = 0 nem lesz.
Az utasítás megszakítható.
A jelzőbitek értékeit ld. OTIR utasításnál.

4.3. IT-vel kapcsolatos utasítások

IM 0
Az utasítás 0-ás IT módot állít be. A jelzőbitek állapota nem változik.

IM 1
Az utasítás 1-es megszakítási üzemmódot állít be. A jelzőbitek állapota nem változik.

IM 2
Az utasítás 2-es megszakítási üzemmódot állít be. A jelzőbitek állapota nem változik.

EI Művelet:
1 IFF

Az utasítás engedélyezi, hogy a maszkolható megszakítások az utasítás végrehajtása után érvényre juthassanak.
A jelzőbitek állapota nem változik.

DI Művelet:
0 IFF

Az utasítás mindaddig tiltja a maszkolható megszakítások érvényre jutását, amíg a tilalmat egy EI utasítás fel nem oldja.
A flag-ek állapota nem változik.

RETI Művelet:
Visszatérés programmegszakításból.

Az utasítás visszaállítja a PC tartalmát a megszakítás előtti érték +1-re és jelzi az IT-kérő eszköznek, hogy a megszakítását lekezelő rutin befejeződött.
A flag-ek állapota nem változik.

RETN Művelet:
Visszatérés nem maszkolható programmegszakításból.

A nem maszkolható IT programok végén használt visszatérési utasítás, melynek hatására a gép:

A jelzőbitek állapota nem változik.

5. Az átviteli lánc elemeinek kapcsolata
Nézzünk egy valóságos feladatot! Tételezzük fel, hogy ki akarunk nyomtatni a sornyomtatón egy karaktersort. A nyomtatót PlO-val csatolták a CPU-hoz.
A PIO, párhuzamos adatátviteli illesztő, két portot tartalmaz (két periféria ellátását végezheti) és ún. "handshake" vonalakkal van ellátva.

5.1. Kapcsolat a PIO és a nyomtató között
A handshake (hendsék) kézfogást jelent, nagyon gyakori módja a periféria és illesztő kapcsolatának. Lényege két készülék "párbeszéde". (A tényleges fizikai megvalósítás természetesen bonyolultabb, pl. órajel ütemezi a jeleket, lehet pozitív logika, inverz megoldás stb., mi csak az elvet ismertetjük.)
Mivel most egy adatkivitelt vizsgálunk, legyen az egyik, a nyomtató által generált jel neve ADATKÉSZ, ez jelzi, hogy a nyomtató képes adat fogadására. A másik jelet, a PIO által generáltat, most nevezzük ADATAD-nak. Ez legyen egy mintavételező jel, mely jelzi, hogy a nyomtatandó adatok most rajta vannak az adatvonalakon.
Mikor a vizsgálatot elkezdjük, a nyomtató épp szabad és várakozó helyzetben van. Azt, hogy kész az adat fogadására, azzal jelzi, hogy ADATKÉSZ jelét aktív állapotban tartja. A processzorban fut egy program, mely egyszer csak egy, a nyomtatónak szóló OUT utasításra lép. Adatot küld a nyomtató PlO-ján keresztül. A nyomtató észreveszi, hogy az ADATAD jel, mely eddig inaktív (pl. alacsony szinten) volt, most aktív lett, jelezvén, hogy a processzor most adja az adatokat. A nyomtató ezután az ADATKÉSZ jelet inaktív állapotba helyezi.

Most a nyomtató nekilát az adat vételéhez. Ez nem szükségszerűen jár a tényleges azonnali nyomtatással, mert a legtöbb nyomtató rendelkezik egy saját pufferrel, ahol összegyűjti az érkezett adatokat. Ennek oka az, hogy vagy lassú a nyomtató mechanikája, s fel kell készülnie arra, hogy a következő karakter odaér mielőtt az előzőt kinyomtatta volna, vagy olyan a mechanikai megoldása (pl. karakterhenger), hogy egész sort nyomtat kvázi-egyszerre. Ilyenkor egy vezérlőjel (pl. soremelés) beérkezése indítja el a mechanikus nyomtatást.
Tehát, ha a karakter beérkezett, akkor a nyomtató megszüntette ADATKÉSZ jelét és pl. elrakja a kapott adatot a saját memóriájába. Ha elkészült, akkor ismét felemeli ADATKÉR-t, amely jelzi a PlO-nak, hogy "elkészültem a karakterátvétellel". Ezen kívül az INT, IT-kérő jel is aktívvá válik, melynek hatására a PIO IT-t kér.
A processzor áttér a nyomtató IT programjára, így értesül arról, hogy a periféria átvette tőle az adatot.
Amennyiben a processzornak még van kiküldendő információja, az IT dezaktíválása után újra meghívja a csatolót egy OUT utasítással, s kiadja neki a következő karaktert, amely most legyen 0DH (soremelés). A PIO ezt átveszi és továbbítja a nyomtatónak.
A nyomtató - érzékelve az ADATAD felfutását - elolvassa a D0-D7-en érkező biteket, s észreveszi, hogy ez nem nyomtatandó karakter, hanem végrehajtandó karakter. ADATKÉSZ-t inaktíválja, s most csak akkor teszi újra 1-be, ha a teljes sor kinyomtatásával, és a soremelés végrehajtásával (a papírmozgatással) is készen van. PIO az ADATKÉSZ és az INT aktiválásán keresztül értesül arról, hogy a nyomtató végrehajtotta a parancsot, IT-t kér. A CPU áttér az IT programra, és mivel - pl. - byte-számlálója is kiürült, tudja, hogy a nyomtató kinyomtatta a sort.

5.2. Kapcsolat a PIO és a CPU között
Ahhoz, hogy a PIO a fent leírt követelményeknek eleget tudjon tenni, a PlO-t a CPU-nak inicializálnia kell, azaz be kell állítani a megfelelő üzemmódokat. A PIO a következő üzemmódokkal rendelkezik:

Mi most a byte-os OUTPUT-ot használjuk. Ezt a mikroprocesszornak közölnie kell a PIO-val oly módon, hogy beállítja a vezérlő regiszterét:

M1
M0
x
x
1
1
1
1

1 1 1 1 - vezérlő regiszter azonosítója

üzemmód:

M1 M0  
0 0 kimeneti üzemmód
0 1 bemeneti üzemmód
1 0 kétirányú adatátvitel bitenként
1 1 programozott üzemmód

Megszakításkérés vezérlése:

EI
AND
H
MASK
0
1
1
1
DI
OR
L

0 1 1 1 - IT vezérlés azonosítója

EI/DI =1, akkor engedélyeztük az IT kérést (A közbülső három bit csak kétirányú adatátvitelnél érvényes.)

Az IT vektort is ki kell küldeni. Mint tudjuk, az IT vektor (V) azt mutatja meg, hogy az II00-hoz képest hányadik byte-on van elhelyezve az aktuális IT program címe. Pontosabban V a cím első byte-jára mutat, (alacsony helyérték), tehát mindig páros. Vagyis az IT vektor utolsó bitje mindig 0. A fenti két byte mindegyike 1-re végződik, tehát az, hogy az utolsó bit = 0, fogja jelezni a PIO-nak, hogy ez az IT vektor.
Hogy ezekből a hardware folyamatokból mivel kell a felhasználói programnak törődnie, az attól függ, hogy milyen operációs rendszer, illetve firmware alatt fut a program. Nézzünk először egy olyan esetet, amikor a firmware csak az alapokat intézi el.

6. Programozási példa fejletlen operációs rendszer esetén

  1. A firmware legalább a következőket végzi el:
    beállítja a 2-es IT üzemmódot
    felölti az I regisztert.

  2. Tételezzük fel, hogy az I arra a memórialapra mutat, ahol az IT program van.
    ITVECT-et a programozónak kell definiálnia, ITVECT címke tartalma lesz az IT program kezdőcíme. Az inicializáló rutin a következőket végzi:
    - feltölti a PIO parancsregiszterét. Mivel kimeneti üzemmód, ezért az ÜM byte: 0 0 X X 1 1 1 1
    - kiadja az IT vektort,
    - kiadja az IT engedélyezést: ITENG = 1 X X 0 1 1 1
    Annak ellenőrzésére, hogy az IT beérkezett-e már, szokásos módszer egy jelző-byte használata. Ez a jelző-byte induláskor 0.
    Mielőtt a főprogram az adatot a perifériának kiadná, a kiadandó karaktert ebbe a jelző-byteba is beírja.
    Az IT program nullázza ezt a byte-ot.
    A főprogram tehát úgy vizsgálhatja meg azt, hogy volt-e már IT (teljesítették-e a parancsot), hogy megvizsgálja a jelző-byteot. Az inicializáló rutinnak ezen jelző-byte kezdeti értékét nullára kell állítania.

  3. A főprogram lesz az, amely kiküldi az adatot a perifériára (erre a célra külön kiírató rutint fogunk készíteni) és elvégzi az adminisztrációt.
    Mivel a byte-szám csökkentése és vizsgálata a tényleges művelet előtt van a programba iktatva, ezért B-be a byte-szám+1-et kell beírni.

  4. Az IT program feladata lesz a jelzőt kinullázni a Z80 PIO nem tud állapotot olvasni a perifériáról, ezért itt hibajelzéssel nem foglalkozhatunk).

Megjegyezzük még, hogy a mikroprocesszoros rendszerekben gyakori megoldás, hogy egy perifériának - illetve ténylegesen a csatolójának - több címe van. Általában külön címre küldjük a csatolónak szóló parancsot és a perifériának szóló adatot (PIOPAR, PIODAT).

  ORG 100H
JP FOPR
PIODAT
PIOPAR
ITVEKT
UM
ITENG
JELZO
KIPUF
CPUF
PUFH
INIC
EQU 20H
EQU 21H
DEFW ITPR
EQU 0FH
EQU 87H
DEFS 1
DEFS 50H
DEFW KIPUF
EQU 51H
LD C,PIOPAR
LD A,UM
OUT (C),A
LD A,(ITVEKT)
OUT (C),A
LD A,ITENG
OUT (C),A
LD HL,CPUF
LD B,PUFH
XOR A
LD (JELZO),A
RET
FOPR
VIZSG
CALL INIC
LD A,(JELZO)
OR A
JR NZ,VIZSG
DJNZ MELO
RET
MELO



KIIR



ITPR
CALL KIIR
LD (JELZO),A
INC HL
JR VIZSG
LD C,PIODAT
LD A,(HL)
OUT (C),A
RET
XOR A
LD (JELZO),A
RETI
END

Az olyan gépek esetében, amelynek akár a firmware-je, akár az operációs rendszere rendelkezik handler-rel, ne kísérletezzünk a fenti programmal, mert nem fog menni. Ugyanis az ilyen gépekre implementált fordítók rendszerint az eddig tárgyaltakon kívül még egy szempont szerint osztják fel az utasításokat:

7. Perifériamozgatás CP/M alatt

Ha operációs rendszer segítségével dolgozunk, akkor az előbb ismertetett hardware-függéssel nem kell foglalkoznunk, mert ez az ún. handler, (illetve disc esetében driver) -programok elvégzik.

A handlerek általában két részből állnak:

A CP/M hardware-közeli rutinjait az ún. BIOS és bizonyos mértékig a BDOS op. sys. program szegmens tartalmazza. Ez a része a CP/M-nek minden gépen más és más, de a BIOS hívása mégis minden programból a következőképp történhet:

A funkciószám alapján dönti el a BIOS, hogy melyik perifériát és mi módon kell működtetnie. Pl. puffercím, kiviendő adat, stb. A fenti regiszterek feltöltése után kell meghívni a BlOS-t, melynek pointere általában a memória 0005-ös címén helyezkedik el: CALL 5.
Az átvitel eredményét, illetve hiba esetén a hibakódot az A, illetve a HL regiszterpárban kapjuk vissza.
A fentiek miatt szokjuk meg, hogy a perifériakezelő programrészünk hívása előtt mindig mentsük el a DE, HL és BC regiszterpárt a stackbe. A CP/M a 0003H byte-on képez egy ún. I/O byte-ot. Ez a byte lehetővé teszi a logikai perifériakezelést. A rendszerben természetese minden perifériának a handlere bent van. Az I/O byte határozza meg, hogy egy bizonyos programutasítás hatására melyik perifériahandlert kell indítani.

Ha az I/O byte utolsó két bitjének állása 01, akkor a program nyomtatásparancsára a CRT (Cathode Ray Tube - katódsugárcső) képernyőre történik a listázás, ha a fenti két bit értéke 10, akkor a printer handlerét indítja el az operációs rendszer. Az I/O byte standard kiosztása a következő:

A következőkben néhány beépített CP/M I/O funkció feltöltési paramétereit ismertetjük, a teljes listát lásd a mellékletben.

Rendszer reset in: C = 0 out: -

A rendszer klaviatúráról parancsot váró alapállapotba kerül.

Konzol input in: C = 1 out: A = ASCII karakter

Egy karakter beolvasása konzolról, mely a képernyőre is kiíródik. A rutin megvárja a karakter leütését a klaviatúrán.

Konzol output in: C = 2
     E = kivivendő kód
out: -

Az E regiszter tartalmát viszi ki a konzolra.

OLVASÓ input in: C = 3 out: A = ASCII karakter

Beolvassa az A regiszterbe az I/O byte által olvasónak kijelölt perifériáról a következő karaktert.

Lyukasztó output in: C = 4
     E = lyukasztandó ASCII karakter
out: -

Az I/O byte által lyukasztónak kijelölt perifériára kiviszi az E regiszter tartalmát.

Lista output in: C = 5
     E = nyomtatandó ASCII karakter
out: -

Az I/O byte által listázónak kijelölt eszközre nyomtatja az E regiszterben levő karaktert.

I/O byte olvasása in: C = 7 out: A = I/O byte

Az I/O byte tartalmát kérdezi le.

I/O byte állítása in: C = 8
     E = I/O byte
out: -

I/O byte módosítása az E regiszter tartalma szerint.

Karaktersor listázás $-ig in: C = 9
     DE = listázandó mező kezdőcíme
out: -

A funkció konzolra írja ki a megadott memóriamező tartalmát mindaddig, amíg $ jelet (24H) nem talál.

A fentiek alapján most már írhatunk egy programot.
Tételezzük fel, hogy van a memóriában a PUF címen egy ASCII kódú karaktersor, melynek végjelkaraktere a $. Csakhogy olyan funkció, amely stringet listáz $ jelig csak a konzolra van! Ha mi a $-al végződő szöveget printerre akarjuk listázni, akkor a teljes adminisztrációt el kell végeznünk. Ezen kívül ellenőrizni fogjuk, hogy az I/O byte LISTÁZÓ elemnek valóban a sornyomtatót jelöli-e ki. Tehát I/O byte-ot is állítunk.



SZOVEG
IOB
ORG 100H
JP IOB
DEFM 'Kiiras az IO-byte altal kijelolt periferiara$'
LD HL,SZOVEG
  RES 6,A
SET 7,A
PUSH HL
PUSH BC
PUSH DE
LD E,A
LD C,8
CALL 5
POP DE
POP BC
POP HL
IOB1 LD A,(HL)
CP 24H
RET Z
PUSH HL
PUSH DE
PUSH BC
LD C,5
LD E,A
CALL 5
POP BC
POP DE
POP HL
INC HL
JP IOB1
END

V. Példarutinok
Programjainkat célszerű kisebb egységekre felosztani, a főprogram tulajdonképpen a rutinok egybeszervezéséből is állhat. Ebben a fejezetben néhány, gyakran szükséges feladatot elvégző rutint mutatunk be.
Mint mondottuk, pontosan meg kell határoznunk a ki- és bemeneti feltételeket, tehát, hogy

Két alapvető rutin a konzolról olvasó, és az oda adatot kiíró rutin. Mindkét alprogramot két részre osztottuk:

A SORKI rutin a következő bemenő paramétereket igényli:

A SORBE alprogram a következő bemenő paramétereket igényli:

A rutin visszatérésekor a memóriában találhatók a beolvasott adatok, s ha a beolvasott byte-ok száma kevesebb, mint a mező hossza, akkor az üres rész nullákkal lesz feltöltve.
A fenti rutinok listája az ASCII kódot tömörített kóddá átalakító rutin listáján szerepel.

1. ASCII kód - tömörített kód átalakítás
Mint tudjuk, ha klaviatúráról beolvasunk a memóriába karaktert, ott nem maga a hexadecimális jegy, hanem annak ASCII kódja fog megjelenni. Márpedig számok esetén ez nem jó.
Ha megnézzük az ASCII táblázatot, láthatjuk, hogy a számjegyek karakterkódja 30H-39H-ig, a nagybetűk kódja pedig 41H-5AH-ig terjed. A hexadecimális számjegyekre 0-F-ig vagyis 30H-46H-ig (40H kiszűrésével) lehet szükség. Írnunk kell egy olyan rutint, amely megvalósítja pl. a következő feladatot. Ha leütünk (és beolvasunk)

Ha az ASCII kódú mező páratlan számú byte-ot tartalmaz, akkor keletkezne egy fél byte. Ezért ebben az esetben a tömörített mező legnagyobb helyértékű digitjét kinullázzuk.

Hívási interface:

Visszaadás:

A karaktermező hossza kétszerese kell legyen a tömörített mező hosszának. Amikor a karakterek közül egyet előveszünk, a karaktermező címét (a HL regiszterpár tartalmát) inkrementálnunk kell. A tömörített kódú mező címregiszterét azonban (DE regiszterpár tartalmazza) csak minden második karakter feldolgozása után kell növelni, hisz egyetlen karakter feldolgozásakor még nincs kész egy teljes tömörített kódú byte. Hogy egy tömörített kódú teljes byte-unk van-e, vagy sem azt a B regiszter vizsgálatával dönthetjük el.

Egy karakter feldolgozásánál azt kell figyelembe venni, hogy szám vagy betűkarakter érkezett-e. Ugyanis ha szám, akkor a byte nagyobb helyértékén 3, a kisebben pedig maga a szám van. Betűkarakter esetén a nagyobb helyértéken 4, a kisebben azonban 9-el kevesebb mint a szám. (Azt azért figyeljük, hogy nem nulla-e a karakter értéke.)
A nagyobb helyérték tartalmának meghatározására ún. maszkot fogunk használni. A maszknak és a maszkolandó byte-nak az "ÉS" kapcsolata a byte-nak csak azt a részét engedi át, ahol a maszk "ablaka" van. Pl.:

Most már az A regiszterben egy olyan byte van, amelynek kisebb helyértékű digitje a szükséges hexadecimális kódot tartalmazza, nagyobb helyértéke pedig felesleges. Hogy az értékes digit jó helyen van-e, az attól függ, hogy a byte-számláló értéke páros-e vagy páratlan.
Ha páros (pl. 4), akkor a digitet 4 bittel balra kell tolni, mert kiinduló példánk szerint ez az a 36-os kód, amelyből az első byte (6C tartalmú byte) nagyobb helyértékű digitje lesz (6). Ha viszont B = 3 (páratlan), akkor ez a "4C", amelyből a 4-et-nulláznunk kell, s helyébe (a már elkészített, tárolt) 6-ot kell tenni.

Mivel olyan utasítás, mely a DE regiszterpár által kijelölt memóriacím tartalma és az A regiszter között hajt végre logikai VAGY kapcsolatot, nincsen, ezért egy tartalékbyte-ot használunk a memóriában, ahova az A regiszter tartalmát eltesszük (a FELRE címkéjű rekeszbe). Ezt a byte-ot az IX regiszterpár segítségével fogjuk címezni.
Az átalakító rutin sémája tehát a következő:



SORBE




SORBE1


SORBE2

SORBE3



KARBE










KARKI










SORKI




SORKI1

ELS

MAS


ELSA
ELST
MASA
MAST
FELRE
ASTOM








ASTOM1






PAR


ASTOM3



VEG







BETU


LAN






FOPR
ORG 100H
JP FOPR
CALL KARBE
CP 0DH
JR Z,SORBE2
DJNZ SORBE1
RET
LD (HL),A
INC HL
JP SORBE
DJNZ SORBE3
RET
LD A,0
LD (HL),A
INC HL
JP SORBE2
PUSH HL
PUSH DE
PUSH BC
PUSH IX
LD C,1
CALL 5
POP IX
POP BC
POP DE
POP HL
RET
PUSH HL
PUSH DE
PUSH BC
PUSH IX
LD C,2
CALL 5
POP IX
POP BC
POP DE
POP HL
RET
LD E,(HL)
CALL KARKI
LD A,E
DJNZ SORKI1
RET
INC HL
JP SORKI
DEFW 0D0AH
DEFM 'Kerem az elso adatot! '
DEFW 0D0AH
DEFM 'Kerem a masodik adatot '
DEFW 0A0DH
DEFS 010H
DEFS 8
DEFS 010H
DEFS 8
DEFS 1
PUSH IX
PUSH HL
PUSH DE
PUSH BC
LD IX,FELRE
BIT 0,B
JP Z,ASTOM1
LD A,0
LD (DE),A
LD A,(HL)
AND A
JP Z,PAR
AND 0F0H
CP 30H
JR NZ,BETU
LD A,(HL)
BIT 0,B
JP NZ,LAN
LD C,4
SLA A
DEC C
JP NZ,ASTOM3
LD (DE),A
INC HL
DJNZ ASTOM1
POP BC
POP DE
POP BC
POP IX
SRL B
RET
LD A,(HL)
ADD A,9
JP PAR
AND 0FH
LD (IX+0),A
LD A,(DE)
OR (IX+0)
LD (DE),A
INC DE
JP VEG
LD HL,ELS
LD B,18H
CALL SORKI
LD HL,ELSA
LD B,011H
CALL SORBE
LD HL,MAS
LD B,1AH
CALL SORKI
LD HL,MASA
LD B,011H
CALL SORBE
LD HL,ELSA
LD DE,ELST
LD B,10H
CALL ASTOM
LD HL,MASA
LD DE,MAST
LD B,10H
CALL ASTOM
RET
END

2. Bináris szám ASCII kód átalakítás
Az előző rutin fordítottja. A memóriában hexadecimális formájú számokat tételezünk fel, amelyeket ASCII kódra alakítunk át.

6A2C 36 41 32 43

A peremfeltételek híváskor:

Output:

A rutin fő váza hasonlít az 5.1. fejezetben megismerttel. A belső felépítésben azonban figyelnünk kell arra, hogy míg ott két byte-ból csináltunk egyet, itt egyetlen input (bemenő) byte-ot kétszer dolgozunk fel. Ennek jelzésére használjuk a C regiszter 7. bitjét. Kezdeti beállításnál

A bővítés most a következőt jelenti: ha a digit értéke 0-9 között van, akkor egy 3-ast írunk a nagyobb helyértékre, ha a digit értéke A-F-ig tart, akkor a nagyobb helyértékre 4-es kerül, az alsó digitből (az eredetileg A-F-ből) kivonunk 9-et. (Pl. A = 41)

A léptetésre most nem érdemes ciklust szervezni, ugyanis valamennyi elől álló regiszternek meg van a szerepe, tehát pl. regisztercserét kéne végrehajtanunk. Ez azonban nagyobb memóriaterületet és hosszabb futási időt igényelne, mint a - kétségtelenül nem elegáns - négyszeri SRLA leírása.

BINASC


BINAS1
BINAS2




BINAS3


BINAS4










BINAS5


BINAS6
PUSH HL
PUSH DE
PUSH BC
LD C,0
LD A,(HL)
BIT 7,C
JR Z,BINAS6
RES 7,C
AND 0FH
CP 9  ; betu?
JR NC,BINAS5
OR 30H
LD (DE),A
INC DE
BIT 7,C
JR NZ,BINAS2
INC HL
DJNZ BINAS1
POP BC
POP DE
POP HL
SLA B
RET
SUB 9   ; betu
OR 40H
JR BINAS4
SET 7,C   ; magas helyertek
SRL A
SRL A
SRL A
SRL A
JR BINAS3

3. Sorbarendezés
Tételezzük fel, hogy van a memóriában egy mező, mely egy byte hosszúságú adatokat tartalmaz. Az a feladatunk, hogy ezeket a hexadecimálisnak értelmezett számokat sorbarendezzük úgy, hogy elől álljon a legnagyobb szám, leghátul pedig a legkisebb.
A módszerünk az lesz, hogy:

Pl:

04, 02, 05, 03

az alső csere 02 - 05:

04, 05, 02, 03

Láthatjuk, hogy az első "menetben" így a mező legkisebb száma a legutolsó lesz, de a mezőn belül még nem lineáris a sorrend. Az előző példa alapján:

04, 05 ,03 ,02

Ezért ha a cserét jelző flagünk 1-ben áll, akkor még egyszer átmegyünk a soron, de nem végig. Hisz tudjuk, hogy a legutolsó már a legkisebb szám, elég (byte-szám-1)-ig hasonlítani.
A rutin bemenő adatai:

Ugyanezek a kimenő adatok is.

Mint látjuk, egyik feladatunk biztosítani mindig két byte kiemelését összehasonlítás céljából. Tipikus megoldás erre a kiterjesztett címzés. Az első helyen levő adatot nevezzük ki mindig bázisnak. Bázisregiszternek az IX vagy IY regisztert használhatjuk. Ha pl. IY a bázis, akkor az első byte címe IY+00H, a másodiké IY+01H.A következő összehasonlításhoz csak IY-t kell inkrementálnunk. IY-ba tehát át kell írnunk a HL-ben érkező mezőcímet. Ilyen közvetlen utasítás azonban nincs, ezért közvetítőt kell használnunk, ami a memória kell, hogy legyen. Vagy lefoglalunk erre a célra egy szót, vagy használhatjuk a stack-et is: Ebben az esetben azonban állandóan vissza kéne állítani újrahívhatóság céljából. Inkább a DEFW-t használjuk.
Memóriában levő tartalmakat csak láncolt utasítással tudunk összehasonlítani, ami azonban most nem megfelelő, mivel a CPR bármelyik formája változtatja a HL, BC, DE regisztereket is, ami nekünk (a báziscímzés miatt) nem megfelelő. Ezért az összehasonlításhoz egy-egy regiszterbe kérjük be az adatokat. A HL üres, ezért az első byte-ot a H-ba, a második byte-ot az L-be hozzuk.

SORBA




SORBA1


SORBA2







SORBA3



SORBA4




SORBA5
PUSH IY
PUSH BC
PUSH DE
PUSH HL
LD C,B
RES 7,D   ; flag-allitas
POP IY
PUSH IY
LD H,(IY+0)
LD A,H
LD L,(IY+1)
CP L
JR NC,SORBA3
LD (IY+0),L   ; csere
LD (IY+1),H
SET 7,D
INC IY   ; kovetkezo adat
DJNZ SORBA2
BIT 7,D
JR SORBA5
POP HL
POP DE
POP BC
POP IY
RET
DEC C
JR Z,SORBA4
LD B,C
JR SORBA1

4. Bináris számok szorzása
Bitenként fogunk szorozni. Az egyszerűség kedvéért az algoritmust 4 bites példán mutatjuk be.
Mint tudjuk minden egyes bitlépés balra 2-vel való szorzást jelent.

2-vel való szorzást jelent az is, ha valamely számot önmagához hozzáadjuk. (n+n = 2*n)
Kombináljuk a két állítást. Tételezzük fel, hogy 3*5 szorzást kell elvégeznünk:

Ezekből a részeredményekből azonban csak az kerül bele a végeredménybe, amely biten a szorzó 1-et tartalmaz. Vagyis jelenleg a 0. és a 2. bit.

3+12 = 15
3h+Ch = Fh

A program során ki kell számítanunk az n. bithely szorzatát. Ha a szorzó bitje itt 1-es, akkor ezt a részletszorzatot hozzáadjuk ahhoz a regiszterhez, ahol az eredményt "gyűjtjük" Ha a bit értéke 0, akkor az összeadás elmarad, a következő bithelyre lépünk azzal, hogy a fent említett részletszorzatot szorozzuk kettővel, így kapjuk a következő bithelynek megfelelő részletszorzatot.
Példarutinunkban két, előjel nélküli, külön-külön max. 16 bites számot fogunk összeszorozni, de az eredmény nem lehet nagyobb FFFFh-nél.
Részletszorzatnak nevezzük az egyes biteknek megfelelő aktuális szorzatértékét, az előző példában pl. az 1-es részletszorzat 6, a kettes bitnek megfelel a 12, stb. Részösszegnek nevezzük azt a számot, amely az eredményhez az aktuális pillanatban már összegyűlt. Pl. az 1. bit vizsgálatának pillanatában a részösszeg már 3, ehhez a 2. bit feldolgozása során hozzájön a 12-es részletszorzat, úgyhogy a harmadik bitre lépéskor a részösszeg már 15, ami majd a végeredmény is lesz.
Indulásnál a szorzót a DE regiszterpárban találjuk. Ezt - léptetés céljából - áttöltjük a C és A regiszterbe.

Mivel nincs regiszterpárt léptető utasítás, ezért C és A léptetését külön oldjuk meg. Összekötőkapocs a CY. Először C-ből a legkisebb helyértékű bit CY-be lép, a második léptetésnél ez a bit belép A regiszter legnagyobb helyértékére.
A résszorzat mindig a HL-ben képződik oly módon, hogy

2*HL = HL+HL

A részösszeget DE tárolja.
Ha a szorzó bitje 1, akkor növeljük a részösszeget úgy, hogy DE+HL legyen. Csakhogy az összeadás eredménye HL-ben keletkezik, mert olyan utasítás nincs, amely a DE-hez adja hozzá a HL-t és az eredmény DE-ben mutatkoznék. Ezért kénytelenek vagyunk regisztercseréhez folyamodni.

  1. részszorzat HL-ben
  2. csere:
       HL = részösszeg,
       DE = mentett részszorzat
  3. léptetés: bit = 1?
       - ha igen, akkor DE+HL HL = új részösszeg
       - ha nem, akkor a HL-ben levő részösszeg nem változik.
  4. Csere vissza
       HL-ben részszorzat, lehet az új részszorzatot előállítani (HL+HL)
       DE-ben a részösszeg
  5. 2-es ponttól ismétlés, míg B tartalma el nem fogy.
SZOROZ




SZOR1



SZOR2
LD B,010H
LD C,D
LD A,E
EX DE,HL
LD HL,0
SRL C
RRA
JR NC,SZOR2
ADD HL,DE
EX DE,HL
ADD HL,HL
EX DE,HL
DJNZ SZOR1
RET

5. Bináris számok osztása
Azt tételezzük fel, hogy a legnagyobb osztandó és osztó két byte-on helyezkedik el.

A program az osztást kivonásra vezeti vissza. Az osztandóból addig vonogatja ki az osztót, amíg a maradék 0 vagy negatív nem lesz. Ha a maradék negatív, akkor hozzáadja az osztót, így kapjuk a helyes maradékértékeket.
Pl. osszunk el egy, a HL-ben levő számot öttel:

HL
...
 7-5 =  2  
számláló +1
 2-5 = -3  
már negatív!
-3+5 =  2  
2 a tényleges maradék

Az eredmény az elvégezhető kivonások száma lesz.
A kivonásokat a nagyobb helyiértékkel kezdjük. A HL-t elmentjük a stack-be, elvégezzük a nagyobb helyiérték osztását, HL-t visszaolvassuk a stack-ből, elvégezzük a kisebb helyiérték osztását.
Pl.:

Az osztó természetesen lehet két byte-os ezért a kivonás során (HL-DE) szintén két lépésre van szükség:

SHB L-E és
SBC H-D

Mivel kivonni csak az A regiszterből lehet, ezért L-t és H-t is át kell írnunk A-ba.

Látjuk, hogy magát az osztás procedúrát kétszer kell végrehajtanunk. Ilyen feladatunk már volt, ott flag-állítással döntöttük el, hogy kell-e újra ismételnünk a közös ágat vagy nem. Most nézzünk egy másmilyen megoldást. A különbség az "odakerülés", a ráfutás módjában van:

OSZTAS







OSZT1
OSZT2




OSZT3
PUSH HL
LD L,H
LD H,0
CALL OSZT1
LD B,C
LD A,L
POP HL
LD H,A
LD C,0FFH
INC C
CALL OSZT3
JR NC,OSZT2
ADD HL,DE
RET
LD A,L   ;vonogatás
SUB E
LD L,A
LD A,H
SBC A,D
LD H,A
RET

Vagy nézzünk egy rövidebb megoldást ASD-vel:

4600



460D



4616
MV HL,$ osztandó címe
MV DE,$ osztó címe
MV BC,$00   ; itt lesz az eredmény
MV IX,$4700 ; ide tesszük az eredményt
SBC DE ;(hl-de)
JRM $4616
INC BC
JR $460D
ABC DE; (hl+de)
LD A,B
LD (IX+$00),A
LA A,C
LD (IX+$01),A
RET

Az osztás INTEL mnemonikokkal:


OSZTAS







OSZTK
OSZTB






VONOG






START









ERED
JMP START
PUSH H
MOV L,H
MVI H,0
CALL OSZK
MOV B,C
MOV A,L
POP H
MOV H,A
MVI C,0FFH
PUSH PSW
INR C
POP PSW
CALL VONOG
JNC OSZTB
DAD D
RET
MOV A,L
SUB E
MOV L,A
MOV H,A
SBB D
MOV H,A
RET
LXI H,0195H
LXI D,01AH
CALL OSZTAS
LXI D,ERED
MOV A,B
STAX D
INX D
MOV A,C
STAX D
RET
DS 2
END

6. Binárisan kódolt decimális (BCD) számok összeadása
BCD számok, esetén egy számjegy egy digiten foglal helyet, vagyis egy byte két számjegyet tartalmaz. Az a rutin, melyet írunk, több byte hosszúságú számok összeadására készül fel.

Bemeneti adatok:

Kimenet:

Vegyük észre, hogy a legnagyobb helyiértékű byte címe kisebb, mint a legkisebb helyiértékűé.

Ez a rutin túlcsordulást, előjelváltozást nem vizsgál. Megtehetjük, hogy DATA1-et egy byte-tal megnöveljük, és oda tesszük le a CY értékét.

BCDADD
BCDAD1
XOR A
LD A,(DE)
ADC A,(HL)
DAA
LD (HL),A
DEC HL
DEC DE
DJNZ BCDAD1
RET

7. BCD számok komplemensképzése
Mint tudjuk egy szám kettes komplemensét úgy képezzük, hogy a számot kivonjuk nullából. Ezt hajtja végre a NEG utasítás. Ha számláncról van szó, akkor azonban figyelni kell a CY átvitel jelzőbitre is, ezért a következő programot javasoljuk a fenti célra.

INPUT:

OUTPUT:

BCDCOM LD A,0
SBC A,(HL)
DAA
LD (HL),A
DEC,HL
DJNZ BCDCOM
RET

A programot szűkített INTEL mnemonikokkal is megírtuk. Ebben az esetben a következőkre kell figyelnünk:
Mivel ez a fordító nem ismeri a DJNZ utasítást, a byte-szám csökkentését és ellenőrzését külön kell megoldanunk. A B dekrementálása után a B tartalmát A-ba töltjük, egy logikai "ÉS" kapcsolattal állítjuk be a jelzőbitek értékét, majd a Z flag állásától függően folytatódik a ciklus, illetve kilépés belőle. Csakhogy a komplemensképzésnél a következő byte kivonásához szükséges az előző byte kivonásakor keletkezett áthozat is.
Míg a regiszterpár dekrementálása (DEC HL, DCX H) nem befolyásolja a flag-eket, a byte-dekrementálás és a logikai "ÉS" utasítás igen, tehát a következő byte kivonásakor már "elrontott" flag-eket találnánk. Ezért szükséges, hogy a byteszám-procedúra előtt elmentsük a flagek állapotát a stackbe. Ugyanezért nem ugorhatunk vissza az XRA A utasításra, hanem kell egy külön MVI A,0, mely, szemben az XRA-val, nem változtatja a flagek állapotát.
Az Intel mnemoniknál a "PSW" az A és az F regiszterből álló regiszterpárt jelenti. A byteszám-csökkentés és vizsgálat után az indikátorok visszaállítását a POP PSW utasítás biztosítja. Ha már B=0, és kilépünk a ciklusból, AF-et akkor is ki kell olvasnunk a stack-ből, mert egyébként a hibás stack-kezelés ugrási bonyodalmakat okozna. (A POP HL hatására PSW menne a HL-be, és a RET a HL tartalmára ugrana, vagyis a komplementált adatokra, nem vissza a főprogramba.)

BCDCOM

BCDCO1
PUSH H
XRA A
MVI A,0
SBB M
DAA
MOV M,A
PUSH PSW
DCX H
DCR B
MOV A,B
ANA A
JNZ BCDCO2
POP PSW
POP H
RET
BCDCO2 POP PSW
JMP BCDCO1

8. Lebegőpontos számábrázolás
Mint tudjuk, a Z80-as processzor csak egész számokkal dolgozik, és csak szűk értéktartományon belül. Ezért a nagyobb számokat byte-sorozattá kell átalakítani. Ha törtszámokat is fel akarunk dolgozni, akkor használjuk a lebegőpontos számokat. Egy számot felírhatjuk a következő alakban:

N = k*n^t

ahol

Pl. a tízes számrendszerben:

40.61 = 40.61 * 10^0

vagy ezzel egyező értékek:

4.061   * 10^1
0.4061 * 10^2
406.1  * 10^-1
4061   *10^-2

A tizedesvesszőt egy hellyel balra víve a számot 10-el osztottuk, és ahhoz, hogy a szám értéke ne változzék, ugyanakkor a kitevőt eggyel növelnünk kellett

40.61*1 = 4.061*10

Ugyanígy a tizedesvesszőt jobbra víve 10-el szoroztunk, s a kitevőt eggyel csökkentenünk kellett.
Azt a felírási módot, amikor a tizedesvessző előtt 0, mögötte pedig az első értékes jegy áll, a szám normalizált alakjának nevezzük. Példánkban 0.4061*10^2 .
Bármely számrendszerben dolgozhatunk a fenti formátummal, de a DAA utasítás léte miatt a legkényelmesebb a decimális rendszer alkalmazása. (Ez egyébként egyezik a nemzetközi konvenciókkal.) A szám elhelyezésére a gépben több hagyomány létezik, de mikrogépen (jelenleg) a legelterjedtebb ábrázolás a következő:

Ezt az esetet (amikor a számot 8 byte-on ábrázoljuk) dupla-pontosságúnak nevezzük (az egyszeres pontosságú a négy byte-on ábrázolt szám). A mutatott módszer kb. tizennyolc decimális jegy pontosságú.
A túlcsordulás digitje alapállapotban egyezik az előjel értékével. Akkor változik meg, ha aritmetikai művelet következtében a mantissza rácsordul.
Az előjeldigit 0, ha az eredmény pozitív, és 9, ha az eredmény negatív.
A karakterisztikát komplemens kódban ábrázoljuk. Mint láttuk, a karakterisztika négy BCD jegyet foglal el. Ez azt jelenti, hogy 10000 féle karakterisztikánk lehet. Ebből 5000 pozitív, és 5000 negatív. Tekintsük át a következő példát:

N = 3.625452 normalizálvaI 0.3625452*10^1

Lebegőpontos alakja: 5001 00 3625452000 (karakterisztika, előjel, mantissza)

A szám kettes komplemense:


(A karakterisztika nem változik)

9. Lebegőpontos számok összeadása
Adjunk össze két lebegőpontos számot:

215 + 3.14 = 218.14

Normalizált alakjuk:
215 = 0.215 * 10^3
3.14 = 0.314 * 10^1

A két mantisszát így nem adhatjuk össze, mert a nem egyező helyiérték miatt hibás eredményt kapnánk. Először olyan alakra kell hozni a számokat, hogy karakterisztikáik megegyezzenek. A kisebb szám karakterisztikáját növeljük (szorzunk), a hozzátartozó mantisszát jobbra léptetjük (osztunk).

0.314 * 10^1 0.0314 * 10^2 0.00314 * 10^3

Most már a karakterisztikák egyeznek, elvégezhetjük a mantisszák összeadását.
Az összeadás blokksémája tehát a következő lesz:

Mivel a karakterisztika két byte, írjunk egy rutint BCD számok inkrementálására (növelésére eggyel).

INPUT:

OUTPUT:

BCDINC





BCDIN1
DEC B
LD A,(HL)
INC A
DAA
LD (HL),A
DEC HL
LD A,(HL)
ADC A,0
DAA
LD (HL),A
DEC HL
DJNZ BCDIN1
RET

A másik feladat a mantissza jobbra léptetése de úgy, hogy "behúzza" maga elé az előjel-byte jegyét. Az RRD utasítást fogjuk használni. Először az előjelbyte-ot betöltjük az A regiszterbe, majd következik a ciklus.
Pl.:

5002 99 564231...

Indulásnál kijelölt byte következő byte mantissza
A: 9 9 5 6 4 2 99 564231...
1. RRD után
9 6

9 5

4 2
99 954231...
2. RRD után
9 2

6 4

3 1
99 956431...
3. RRD után
9 3

2 3

...
99 654423...

A mi esetünk most speciális, ugyanis a kezdő byte 00 vagy 99, vagyis az induló byte mindkét digitje egyenlő. Ha azonban azt akarjuk, hogy a rutin minden esetben jó működjék, akkor az RRD ciklus előtt be kell építenünk egy akkumulátor byte-cserét (akkor bármi is az első byte, a byte első digitjét, az előjelet húzza be a gép és nem a túlcsordulásnak megfelelő digitet).

INPUT:

OUTPUT:

BCDLEP PUSH HL
PUSH DE
LD E,B
LD D,0
XOR A
SBC HL,DE
INC HL
RRC (HL)
RRC (HL)
RRC (HL)
RRC (HL)
BCDLE1 RRD
INC HL
DJNZ BCDLE1
POP DE
POP HL
RET

Most már hozzáláthatunk az összeadóprogram összeállításához.
Legyen a két szám beviteli mezeje ELSA és MASA. Ha az ASCII kódú számot átalakítottuk, akkor ELST+MAST ELST.
A mantissza hossza: MANTH = 5

Szükségünk lesz egy előjel-normalizáló részre, ha ugyanis az összeadás következtében túlcsordulás lép fel, akkor a mantisszát jobbra kell léptetni, a karakterisztikát pedig inkrementálni kell. A vizsgálatot az ELOJNO címkéjű rész fogja végezni, ennek menete a következő:

  1. Az eredmény előjelet és túlcsordulást tartalmazó byte-ját betöltjük az A regiszterbe és elmentjük a C regiszterbe is.
  2. Négy db. RRC utasítással felcseréljük az A regiszter két digitjét.
  3. A túlcsordulás digitjét maszkolással töröljük, s az eredményt elmentjük a D regiszterbe.
  4. Az előzőleg C regiszterbe mentett eredeti byte-ot visszahozzuk az A-ba, és most az előjelet töröljük.
  5. Összehasonlítás: A = D?
    Ha a két regiszter tartalma (azaz az előjel-digit és a túlcsordulás digit) egyenlő, akkor változás nem történt, nem kell túlcsordulás miatt normalizálni.


SORBE




SORBE1


SORBE2

SORBE3



KARBE
ORG 100H
JP FOPR
CALL KARBE
CP 0DH
JP Z,SORBE2
DJNZ SORBE1
RET
LD (HL),A
INC HL
JP SORBE
DJNZ SORBE3
RET
LD A,0
LD (HL),A
INC HL
JP SORBE2
PUSH HL
PUSH DE
PUSH BC
PUSH IX
LD C,1
CALL 5
POP IX
POP BC
POP DE
POP HL
RET
KARKI PUSH HL
PUSH DE
PUSH BC
PUSH IX
LD C,2
CALL 5
POP IX
POP BC
POP DE
POP HL
RET
SORKI LD E,(HL)
CALL KARKI
LD A,E
DJNZ SORKI1
RET
SORKI1

ELS

MAS

OSSZEG

ELSA
ELST
MASA
MAST
ELSUAC
ELSUA
MASUAC
MASUA
FELRE
MANTH
BINASC


BINAS1
BINAS2
INC HL
JP SORKI
DEFW 0D0AH
DEFM 'Kerem az elso adatot:   '
DEFW 0D0AH
DEFM 'Kerem a masodik adatot: '
DEFW 0A0DH
DEFM 'Osszeg: '
DEFS 010H
DEFS 8
DEFS 010H
DEFS 8
DEFW 0D0AH
DEFS 010H
DEFW 0D0AH
DEFS 010H
DEFS 1
EQU 5
PUSH HL
PUSH DE
PUSH BC
LD C,0
LD A,(HL)
BIT 7,C
JR Z,BINAS6
RES 7,C
AND 0FH
BINAS3


BINAS4
CP 0AH ;betu?
JR NC,BINAS5
OR 30H
LD (DE), A
INC DE
BIT 7,C
JR NZ,BINAS2
INC HL
DJNZ BINAS1
POP BC
POP DE
POP HL
SLA B
RET
BINAS5


BINAS6
SUB 9 ;betu
OR 40H
JR BINAS4
SET 7,C ;magas helyiertek
SRL A
SRL A
SRL A
SRL A
JP BINAS3
RET
ASTOM PUSH IX
PUSH HL
PUSH DE
PUSH BC
LD IX,FELRE
BIT 0,B
JP Z,ASTOM1
LD A,0
LD (DE),A
ASTOM1 LD A,(HL)
AND A
JP Z,PAR
AND 0F0H
CP 30H
JR NZ,BETU
LD A,(HL)
PAR


ASTOM3



VEG
BIT 0,B
JP NZ,LAN
LD C,4
SLA A
DEC C
JP NZ,ASTOM3
LD (DE),A
INC HL
DJNZ ASTOM1
POP BC
POP DE
POP BC
POP IX
SRL B
RET
BETU


LAN
LD A,(HL)
ADD A,9
JP PAR
AND 0FH
LD (IX+0),A
LD A,(DE)
OR (IX+0)
LD (DE),A
INC DE
JP VEG
BCDADD
BCDAD1
XOR A
LD A,(DE)
ADC A,(HL)
DAA
LD (HL),A
DEC HL
DEC DE
DJNZ BCDAD1
RET
BCDCOM LD A,0
SBC A,(HL)
DAA
LD (HL),A
DEC HL
DJNZ BCDCOM
RET
BCDINC DEC B
LD A,(HL)
INC A
DAA
LD (HL),A
DEC HL
BCDIN1 LD A,(HL)
ADC A,0
DAA
LD (HL),A
DEC HL
DJNZ BCDIN1
RET
BCDLEP PUSH HL
PUSH DE
LD E,B
LD D,0
XOR A
SBC HL,DE
INC HL
RRC (HL)
RRC (HL)
RRC (HL)
RRC (HL)
BCDLE1 RRD
INC HL
DJNZ BCDLE1
POP DE
POP HL
RET
FOPR LD HL,ELS
LD B,1AH
CALL SORKI
LD HL,ELSA
LD B,011H
CALL SORBE
LD HL,MAS
LD B,1AH
CALL SORKI
LD HL,MASA
LD B,011H
CALL SORBE
LD HL,ELSA
LD DE,ELST
LD B,10H
CALL ASTOM
LD HL,MASA
LD DE,MAST
LD B,10H
CALL ASTOM
FLOTAD



KAROSZ
PUSH IX
PUSH IY
LD IX,ELST
LD IY,MAST
LD A,(IX+0)
CP (IY+0)
JP M,DA1LEP
JP NZ,DA2LEP
LD A,(IX+1)
CP (IY+1)
JP M,DA1LEP
JP NZ,DA2LEP
JP ADMAN
DA1LEP



KOZOS


DA2LEP
LD HL,ELST+7
LD B,MANTH+1
CALL BCDLEP
LD HL,ELST+1
LD B,2
CALL BCDINC
JP KAROSZ
LD HL,MAST+7
LD B,MANTH+1
CALL BCDLEP
LD HL,MAST+1
JP KOZOS
ADMAN



ELOJNO
LD HL,ELST+7
LD DE,MAST+7
LD B,MANTH+1
CALL BCDADD
LD A,(ELST+2)
LD C,A
RRCA
RRCA
RRCA
RRCA
AND 0FH
LD D,A
LD A,C
AND 0FH
CP D
JP Z,HALAD
LD HL,ELST+7
LD B,MANTH+1
CALL BCDLEP
LD HL,ELST+1
LD B,02
CALL BCDINC
HALAD LD HL,ELST
LD DE,ELSUA
LD B,8
CALL BINASC
LD HL,OSSZEG
LD B,ELSA-OSSZEG
CALL SORKI
LD HL,ELSUAC
LD B,12H
CALL SORKI
POP IY
POP IX
RET
END

10. Lebegőpontos számok szorzása, osztása
Ha lebegőpontos számokat szorozni akarunk, akkor

A karakterisztikák összeadásánál figyelemmel kell lennünk arra, hogy az első számérték a karakterisztika előjele. Tehát ha pl.:

5001 00 1 2 3 4 5 6 7 8 9 0 * 5002 00 1 2 3 4 5 6 7 8 9 0

a feladat, akkor

  1. 5001+500 = 10003 hibás, mert nincs értelme. Ezért 5000-et le kell vonnunk az összegből.
  2. 10003-5000 = 5003 helyes
  3. Mantisszák szorzása (figyelni kell a keletkező szám hosszára!)
  4. Mantissza előjel-helyreállítás
  5. Normalizálás.

Osztásnál:

  1. Az osztandó karakterisztikájából kivonjuk az osztó karakterisztikáját.
  2. Keletkezett karakterisztika + 5000.
  3. Mantisszák osztása.
  4. Mantissza előjel helyreállítás.
  5. Normalizálás.

A BCD alakú mantissza szorzásánál használhatunk egy, a bináris szorzáshoz hasonló algoritmust, csak természetesen itt a szorzót digitenként léptetjük, digitenként vizsgáljuk, és a szorzandó aktuális értékét annyiszor adjuk hozzá a részszorzathoz, amennyi a szorzó aktuális digitjének értéke. Ne felejtsük el az összeadások után a DAA utasítás használatát, és figyeljünk a túlcsordulás digitjére is.
A BCD számok osztását kivonásra vezethetjük vissza.

VI. Adatkezelés

1. Fizikai és logikai adategységek
Mint mondottuk, az operációs rendszerek egyik fontos feladata az adatkezelés.
Az adatok kezelésének kérdésében két szempont ütközik. Az ember ugyanis valós világból származó adatokkal találkozik, s azokat igyekszik valamilyen rendszerbe foglalni. Pl. tételezzük fel, hogy egy lakáscserét lebonyolító programról van szó. Ilyenkor egy ügyfélhez - egyszerű esetben - két lakás tartozik. Az egyik az, amelyet cserére felajánl, a másik, amilyent kapni szeretne. Így két csoport alakul ki:

Mindkét állomány lakásokra oszlik, és a lakásokat mindkét helyen egy csomó paraméter jellemzi: hogy hol van - hol szeretné, hogy legyen; hány szobás - hány szobásat szeretne; milyen a fűtés - milyen fűtést szeretne, stb.
A másik szempont az adathordozó eszközök fizikai tulajdonságaiból ered. Mert pl. a memóriában egyetlen bitet is elérhetünk, magnón legfeljebb egy byte-ot, diszken pedig csak egy szektort, ami - típustól függően - 128 byte, 256 byte, stb. lehet. Vagyis az adategységek lehetnek logikaiak és fizikaiak. Összehangolásuk az adatkezelő rendszer feladata.
Ezt a feladatot az operációsrendszer a file-kezelő rutinok és a handler-ek segítségével oldja meg.

Logikai adategységeink az előző példa felhasználásával:

 

Fizikai adategységek:

A logikai mező tehát a legkisebb logikai adategység. A mező tetszőleges hosszú lehet, hosszát a programozó határozza meg.
A logikai rekord az összetartozó mezők összessége. A fizikai és a logikai rekord között nem szükséges semmiféle egymással való megfeleltetés, pl. egy logikai rekord elfoglalhat több szektort, egy szektort, vagy több logikai rekord is lehet egy szektorban. (Példánkban valószínűleg egy lakás adatai elférnek egy fél szektorban, tehát egy diszkolvasás során egy szektort, azaz két lakás adatait tudjuk a tárba hozni.)
A file logikailag összetartozó adatok gyűjteménye, egy vagy több fizikai rekordot foglal el a háttértárolón. A file-oknak mindig van nevük. Jellemzőit részben a programozó, részben a rendszer határozza meg. Pl. van olyan file-kezelő rendszer, amely nem feltétlenül folytonosan, egymás után következő szektorokba rak le egy file-t, hanem oda, ahol a lemezen hely van. (Ilyenkor természetesen a rendszer gondoskodik arról is, hogy "összegyűjtse" a lemezről behívás esetén a file szétszórt részeit.)
Gyakori az az eset is, hogy több logikailag összetartozó rekordot blokká fognak össze. Pl. a felajánlott lakások file-jában blokká fogjuk össze a budapestieket, egy másik blokk a vidéki nagyvárosok, stb. így van szervezve a CP/M is, ahol:

n: kettő egészszámkitevős hatványa kell hogy legyen, tehát 1, 2, 4, 8, stb.
Több blokk alkot egy file-t.

A logikai kötet az ugyanazon a fizikai adathordozón elhelyezkedő file-ok összessége. Nem feltétlenül van logikai kapcsolat közöttük. Pl. ugyanazon a lemezen lehet maga a CP/M, annak segédprogramjai szervezés szempontjából önálló file-ok néhány felhasználó által irt program, szintén külön file-ok. Ugyanakkor az is előfordul, hogy egy logikailag összetartozó adatcsomag nem fér el egyetlen lemezen. (Pl. ha olyan sok lakáscsere ügyfelünk lenne.)
Jellemzője az adatállománynak, hogy milyen kódban van tárolva. A tipikusan karakter-stringeket (karakterláncokat) feldolgozó munkák általában ASCII kódban hagyják az adatokat, de ennek hátránya, hogy a string nagy tárterületet foglal el. Lehet a tárolt adat tömörített formátumú (pl. RADIX-50 ilyenkor 16 biten három karakter helyezkedik el) és természetesen bináris is.
Egy program is lehet file! Ezeket tárkép formátumú fileok-nak (memory image, save image) nevezzük, ugyanis a tárolt adatokat a memóriába töltve megkapjuk a futtatható formátumú programot.
Mint mondottuk, minden file-nak van neve, ezeket a neveket az operációs rendszer azon a diszkpaketten tárolja, amelyen a kérdéses file található. (A diszknek ezt a részét könyvtárnak nevezzük.) Természetesen egy köteten nem lehet több azonos nevű file, mert az operácios rendszer így nem tudná, melyikről van szó. Azt, hogy az aktuális file program-file, szokták a névben is jelölni. CP/M esetén pl. a file-név felépítése a következő:

filenév.típus

Ahol a filen-év 1-8 karakter, a típus 0-3 karakter lehet.
Szokásos típusnevek pl.:

COM: futtatható formátumú program,
ASM: forrásnyelvű program,
PRN: listázható formátumú file,
SYM: szimbólumtáblát tartalmazó file.

(A programozó bármilyen típusmegjelölést használhat, vagy akár el is hagyhatja!)

2. File-struktúrák, hozzáférési módok
A programozó tehát file-okat definiál, amelyeket rekordokra oszt. Egyszerre általában csak egy rekordra van szüksége a file-ból, pl. a "Felajánlott lakások" filej-ából a 3. lakásrekordot hasonlítja össze a "Kereső lakások" file-jának 4. lakásrekordjával. Felesleges egyszerre mindkét file-nak az operatív memóriában lennie - nagyon nagy helyet foglalna! elég csak az épp összehasonlítandó rekordok jelenléte. Vagyis az operációs rendszernek biztosítania kell azt a lehetőséget, hogy egy-egy rekord is elérhető legyen. Hogy ezt mi módon lehet megtenni, az megintcsak függ az adathordozó fizikai tulajdonságaitól. A mágnesszalagos egységnél nyilván végig kell olvasnunk az első, második és harmadik rekordot is, míg a negyedikhez elérünk. Ugyanez diszken nem kötelező, mert ha tudjuk, hogy a 4. rekord a 6-os sáv ötödik szektorában van, akkor az olvasó/írófejet közvetlenül ráállíthatjuk a keresett szektorra. Ezért a mágnesszalagos egységen csak soros, ún. szekvenciális elérésű file-ok lehetnek, míg a diszk biztosítja az ún. random elérési módot is.
Tételezzük fel, hogy programunk miután a két file elemeit összehasonlította egymással, kapott egy táblázatot, melyben szerepel, hogy pl. az 1-es ügyfél és a 6-os ügyfél egymásnak megfelelő cserepartnerei lennének. Most tehát értesíteni kell őket, ezért a számítógép tulajdonosa nyomtatni akar egy levelet, melyben az 1-es ügyfelet értesíti a 6-os ügyfél nevéről és a felajánlott lakás jellemzőiről. Tehát újra szüksége van a "Felajánlott lakások" file-jának 6. rekordjára, de most nem lépésről lépésre jut oda, nem a "következő" rekord kell neki, hanem mindjárt a 6. logikai rekord. Ez akkor lehetséges, ha operációs rendszere ismeri az ún. indexszekvenciális hozzáférési módot. Ha indexszekvenciális hozzáférésnél nem sorszámot, hanem egy nevet, alfabetikus karakterekből álló rekordnevet, ún. kulcsot is megadhatunk, akkor kulcs szerinti random hozzáférési módról beszélünk.
Logikai szempontból tehát egy rekord elérési módja lehet:

A CP/M operációs rendszer a szekvenciális és indexszekvenciális formát ismeri.
Hogy a fenti logikai szervezést fizikailag megvalósíthassák, az operációs rendszer file-kezelő rutinjai létrehoznak egy ún. könyvtárat (gyakran ezt a részt is file-ként kezelve), melyet mindig az adathordozón helyeznek el, s amely tartalmazza a köteten levő minden file adatát. Ebben a könyvtárfile-ban (vagy katalógusban) egy-egy rekord egy-egy, a köteten levő file adatait tartalmazó katalógusbejegyzés.
A file-ok rekordjainak elhelyezkedése az adathordozón lehet folyamatos vagyis a file blokkjai fizikailag is egymás után, a logikai sorrenddel megegyező módon helyezkednek el. Nehezen bővíthető, de gyors elérést biztosító eljárás. A katalógus bejegyzés a file hosszát és kezdőcímét tartalmazza.

Az ún. linked (láncolt) file blokkjai az adathordozón elszórtan helyezkedhetnek el, s minden blokkban egy mutató jelzi, hogy hol van a file logikailag következő blokkja.
Előnye a jó tárolóterület kihasználás és a bővíthetőség, hátránya a hosszú kezelési idő.

A két módszert kapcsolja össze a mapped (leképzett) file-kezelés. Ebben az esetben a file egy ún. fejblokkal kezdődik, amelyben benne foglaltatik az, hogy melyik logikai rekord fizikailag hol helyezkedik el. Vagyis a rekordok szétszórtan helyezkedhetnek el a diszken, így könnyen bővíthető a rendszer, ugyanakkor nem kell minden rekordot végigolvasni, hogy megtudjuk a következő helyét, hisz azt az ugróblokk (ugrórekord) megmutatja.

Ha kulcsszerinti random elérés van, akkor a fejblokk a kulcsokat is tartalmazza. Lehet aztán tovább szervezni a szinteket, pl. a fejblokk is önálló file-lá alakul, lesz egy főállomány, felhasználói file-csoport, stb., de ezeket a rendszereket Magyarországon - ma még - mikrogépek közül csak néhány professzionális kivitelű ismeri.

3. File-kezelés CP/M alatt
A CP/M egy ún. file control blokk (FCB) segítségével tartja fenn a kapcsolatot a diszk és a felhasználó programja között. Azonkívül megjegyezzük, hogy bár a felhasználó úgy érzi, ő a diszkről közvetlenül kapja (vagy oda írja) a rekordot, a valóságban az operációs rendszer tart a területén egy periféria-puffért, s az adatok azon keresztül közlekednek a periféria és a felhasználói program között.

A programozónak a saját programjában definiálni kell egy FCB-t (szekvenciális hozzáférés esetén 33 byte, random hozzáférés esetén 36 byte), amelyben meg kell mondania:

0 byte:
  melyik drive-on van a file
1-8 byte:
  file-név (ASCII kódban)
9-11 byte:
  file-név kiterjesztés (ASCII kódban)
12 byte:
  hányadik bejegyzés erre a file-ra
13 byte:
  op.sys status
14 byte:
  kötetszám
15 byte:
  rekordszámláló
16-31 byte:
  blokkcímek
32 byte:
  aktuális írandó-olvasandó rekordszám
33-35 byte:
  random-szám

A 12-35 byte-ot a felhasználónak nem kötelező kitöltenie, általában nullával töltjük fel, használatkor a rendszer bemásolja oda a katalógusból a hiányzó adatokat.
A file-ok kezelése során a következő műveleteket kell (illetve lehet) elvégezni:

  1. File létrehozás: ilyenkor az operációs rendszer az FCB-ben megadott adatok alapján a katalógus első üres helyére megteszi a kért katalógusbejegyzést, vagyis a file-ot "beveszi a leltárba", holott az még üres, egyelőre csak a neve létezik.
  2. File nyitás: mielőtt egy valamikor már létrehozott, diszken létező file-hoz valamilyen célból hozzáfordulnánk, a file-t meg kell nyitni. Ez azt jelenti, hogy az operációs rendszer ellenőrzi, hogy létezik-e a hívott file, s ha igen, akkor a katalógusbejegyzést átmásolja a saját és a felhasználói program FCB-jébe. Meg nem nyitott file-hoz nem lehet hozzáférni, viszont egy file-t elég a program futása során egyszer megnyitni, majd ha már nem lesz rá szükségünk, le kell zárni.
  3. File keresése a könyvtárban. Lehet
    - első keresés, a könyvtár elejétől
    - keresés "következőtől".
  4. File törlés. Törli a katalógusból a file-nak megfelelő bejegyzést.
  5. File zárás: a különböző mutatók alapállapotba helyezése, "rendrakás", hogy a legközelebbi nyitásnál ne legyen bonyodalom.

A rekordokhoz - mint már mondottuk - csak megnyitott file esetén lehet hozzáférni. A CP/M szekvenciális és indexszekvenciális hozzáférést tesz lehetségessé. Random hozzáférés esetén az FCB 33-35 byte-jában meg kell adni a keresett rekord számát. A hozzáférés lehet:

Táblázatok, listák

ASD mnemonikájú utasítások szintaktikája
(Csak az eltéréseket ismertetjük.)

standard ZILOG ASD
ADC HL,regiszterpár
ADD A,(HL)
BIT 0,regiszter
BIT 1,regiszter
BIT 2,regiszter
BIT 3,regiszter
BIT 4,regiszter
BIT 5,regiszter
BIT 6,regiszter
BIT 7,regiszter
CALL C,cím
CALL M,cím
...
CALL Z,cím
ADC regiszterpár
ADD (HL)
BIT0,regiszter
BIT1,regiszter
BIT2,regiszter
BIT3,regiszter
BIT4,regiszter
BIT5,regiszter
BIT6,regiszter
BIT7,regiszter
CALLC cím
CALLM,cím
...
CALLZ,cím
feltételes JP, JR és RET utasításoknál ugyanígy.
IM 0
IM 1
IM 2
IN A,(n)
IN reg,(c)
OTIR
OTDR
RETN
IM0
IM1
IM2
IN (n)
IN regiszter
OUTIR
OUTDR
RETR

A két byte-ot mozgató, vagy abszolút címre hivatkozó LD utasítások LD helyett MV használatos. (Az LD a szimpla regiszterek közti átvitelre van korlátozva.)

Direktívák

Fordítási módra vonatkozó direktívák:

NOLIST fordítási lista nyomtatását tiltja
LIST megszünteti a NOLST hatását a nyomtatást új lapon folytatja
PAGE vagy
FORM
a nyomtatást új lapon folytatja.
SPACE vagy
LINE
Egy sort kihagy a fordítási listán.
CMD (kifejezés) - ha a (kifejezés) értéke nem nulla, eredmény nélkül fordít tovább
- ha a (kifejezés) = 0, akkor a következő utasításokat átugorja, nem fordítja le egész addig, míg egy ENDC direktívát nem talál.
ENDC Feltételes fordítás esetén az esetleg kihagyandó rész végét jelzi.

Adatleíró direktívák:

EQU A címkemezőben levő azonosítónévhez hozzárendeli az operandusmezőben levő kifejezés értékét. A tárgykódba nem kerül bele, csak a fordítás során él az azonosság, de akkor mindvégig.
SET vagy
DEFL
Az EQU-tól abban különbözik, hogy a fordítás során újradefiniálható, vagyis az eredeti címkéhez fordítás közben egy másik operandus rendelhető.
DEFB vagy
DB
Byte-generálás. Az operandusmezőben megadott byte-okat a folytonosan következő memóriacímekre rakja.(Néha csak 8 bites adható meg vele.)
DEFW vagy
DW
Szógenerálás. A memóriába (ill. a tárgykódba) építi az operandusmezőben megadott kétbyte-os értéket, de úgy, hogy a megcímzett memóriaszó első byte-jába az elhelyezendő kifejezés kisebb helyiértékű 8 bitje, míg az eggyel nagyobb című memóriabyte-ba a kifejezés nagyobb helyiértékű 8 bitje töltődik.
DEFM vagy
DB
Üzenet elhelyezés a memóriába. Az operandusmezőben a szöveget aposztrófok közé kell tenni. A string karaktereinek ASCII kódját teszi a direktíva a memóriába.
DEFT DEFM-től abban különbözik, hogy a memóriába, az első byte-ba, a szöveg hosszát is leteszi.
DEFS vagy
DS
Annyi helyet foglal le a tárban, amennyit az operandusmezőben megadunk.

Külső definíciókra vonatkozó direktívák:

ORG Az operandusmezőben levő 16 bites értékre mint címre fog az ORG utáni első utasítás vagy adat a memóriába kerülni.
GLOBAL Az operandusmezőben ilyenkor címkeneveket sorolunk fel, s azt jelenti, hogy ezekre a címkékre más fordítási modulból lehet hivatkozni (pl. ide akarunk ugrani).
EXTERNAL Az operandusmezőben ilyenkor címkeneveket sorolunk fel, s azt jelenti, hogy azok nem ebben, hanem egy másik fordítási modulban vannak definiálva, s itt csak hivatkozunk rájuk (pl. oda akarunk ugrani).

Programlezáró direktíva:

END A fordító itt képezi a forrásprogram végét. Az esetleg ez után következő szöveget figyelmen kívül hagyja.

Makrók:
Az assembly szintű programozásban gyakran előfordul, hogy több helyen szükséges ugyanaz, vagy hasonló programrészlet. Ilyenkor kétféleképpen járhatunk el. Vagy a programrészletből szubrutint készítünk, és ahol szükség van rá, ott egy CALL utasítással hívjuk, vagy többször leírjuk, amivel a program helyigénye ugyan nő, de a végrehajtási idő csökken.
Ez utóbbi esetben segít a makró, amelynél a rövid, ismétlődő részeket mégsem kell többször leírni, azt elvégzi az assembler. A makrókhoz két direktíva tartozik:

MACRO A makró definíció kezdetét jelzi.
ENDM A makró definíció végét jelzi.

A két direktíva közé kell zárni a makró definíciót, azaz azt a programrészletet, amelyet az assembler majd a különböző helyekre bemásol. A MACRO direktíva előtt címkének kell állnia. Ahova pedig be kell másoltatni a programrészletet, ott az utasításkód mezőbe kell írni a MACRO címkéjeként szereplő szimbolikus nevet. Az egyes program részletek el is térhetnek egymástól. A megfelelő változat kiválasztását, előállítását a makro paraméterek segítségével lehet megoldani.

Ismételten megjegyezzük, hogy a felsorolás csak tájékoztató jellegű, s hogy egy aktuális fordítóprogramban egy direktíva pontosan mit jelent, illetve egy adott funkciót milyen mnemonikú direktíva lát el, azt mindig az aktuális fordítóprogram leírásából kell kihüvelyezni. (Pl. elképzelhető, hogy van lehetőség a program szegmentálására - logikailag összefüggő részek egységbe szervezésére, illetve különválasztására. Ilyenkor léteznek szegmensleíró direktívák. Bár a Z80 hardware felépítése nem támogatja ezt a programszervezési módot, ez inkább a 16 bites társzervezésű processzorok sajátja.)

A CP/M beépített funkciói

0: rendszer reset

IN: C = 0
OUT: -
  A rendszer parancsbevétel szintjére kerül, újra inicializálódik, mint a rendszer betöltésekor. Az A drive van kiválasztva.

1: konzol input

IN: C = 1
OUT: A = ASCII karakter
  Egy karaktert olvas be a konzolról. A beolvasott karakter a konzolra is kiíródik. Az I, P, S, M, J, H végrehajtásra kerül. A karakter beérkezéséig a funkció nem tér vissza a hívási helyre.

2: konzol output

IN: C = 2
E = A kivivendő ASCII karakter
OUT: -
  Az E regiszterben megadott byte-ot írja ki a konzolra.

3: olvasó input

IN: C = 3
OUT: A = ASCII karakter
  Az I/O szerinti olvasóról a következő karaktert az A-ba olvassa. A karakter beérkezéséig a funkció nem tér vissza a hívási helyére.

4: Lyukasztó output

IN: C = 4
E= ASCII karakter
OUT: -
  Az I/O byte szerinti lyukasztóra egy karaktert küld ki az E regiszterből.

5: Lista output

IN: C = 5
E= ASCII karakter
OUT: -
  Az I/O byte szerinti list. eszközre egy byte kiküldése az E regiszterből.

6: konzol input-output

IN: C = 6
E = 0FFH input esetén,
E = ASCII karakter output esetén
OUT: A = karakter vagy státusz
  Egy karakter ki- vagy bevitele, a kontrol funkciók végrehajtása nélkül. Input esetén akkor is visszatér, ha nincs készen karakter, ilyenkor A = 0.

7: I/O byte olvasás

IN: C = 7
OUT: A = I/O byte
  Az aktuális I/O byte lekérdezése.

8: I/O byte állítás

IN: C = 8
E= I/O byte
OUT: -
  Az I/O byte állítása az E regiszter szerint.

9: string listázás

IN: C = 9
DE = string kezdőcíme memóriában
OUT: -
  A kezdőcímtől a $ karakterig listázza a konzolra a karaktereket. Az I, P, S végrehajtódik.

10: konzol puffer olvasás

IN: C = 10
DE = puffer kezdőcím
OUT: -
  A funkció lehetőséget ad a konzolon egy szerkesztett sor elkészítésére. Az ASCII karakterek bevitele mellett lehetőség van a DEL; H, R, U és X használatára. A C a sor elején újrainicializáló hatású, egyébként ugyanúgy a pufferbe kerül, mint más karakterek.
Az E letevődik a pufferbe, a funkció megszakítása nélkül.
Az M és J hatására 0 kerül a pufferbe, és a funkció visszatér.

A konzol puffer felépítése:

1 0 1 2 .............. n

ahol

11: konzol státusz lekérdezés

IN: C = 11
OUT: A = konzol státusz
  Ha a konzolon volt leütött karakter, az A = 1, egyébként A = 0.

12: verziószám lekérdezés

IN: C = 12
OUT: HL = verziószám
  A visszatérési érték jelzi, hogy a futó operációs rendszer alatt melyik verzió funkciói használhatók. Ha pl. CP/M 2.2 típusú a rendszerünk, akkor visszatéréskor HL = 22H.

13: diszk rendszer reset

IN: C = 13
OUT: -
  Lehetővé teszi a C leütése nélkül a lemezcserét. Végrehajtása után a diszk irható és az A drive van kiválasztva. Az aktuális puffereim 80H-ra van állítva.

14: drive szelektálás

IN: C = 14
E = kiválasztandó diszk
OUT: -
  Az E-ben megadott drive-ot jelöli ki a funkció aktuális meghajtóvá, a foglaltsági térkép aktualizálásával.
E = 0 esetén az A drive van kijelölve,
E = 1 esetén a B drive van kijelölve,
...
E = 15 esetén a P drive van kijelölve.

15: file nyitás

IN: C = 15
DE = FCB kezdőcím
OUT: A = DIR-kód vagy hibajelzés
  A diszken lévő file aktiválását veszi file-írás és olvasás előtt. Meg nem nyitott file-hoz nem lehet hozzáférni. Ha az FCB alapján a rendszer megtalálja a kijelölt file-t, a katalógusból átmásolja a könyvtársort, a DE-vel kijelölt FCB-be. A visszatérési érték a file megnyitása esetén a DIR-kód, sikertelen megnyitás esetén 0FFH.
A DIR-kód nem más mint a katalógus tétel sorszáma az adott katalógusrekordon belül. (0-3 egy szám, amely mutatja, hogy melyik szektorban szerepel a kijelölt file katalógusbejegyzése.)

16: file zárás

IN: C = 16
DE = FCB kezdőcím
OUT: A = DIR-kód vagy hibajelzés
  A file a diszkre csak lezáráskor kerül fel, ezért írás után ezt a funkciót mindenképpen meg kell hívni. Visszatérési értékei a sikeres lezárás esetén a 0...3 értékű DIR-kód, vagy sikertelenség esetén FFH.

17: file keresés a könyvtár elejétől (első keresés)

IN: C = 17
DE = FCB kezdőcím
OUT: A = DIR-kód vagy hibajelzés
  A FCB-ben megadott nevű file-t keres a könyvtárban. Ha megtalálta, bemásolja a könyvtársort az FCB-be és kitölti a visszatérési kódot a megtalált file DIR-szektoron belüli sorszámával, a DIR-kóddal.
Sikertelen keresés esetén a visszatérési érték A = 0FFH.
A 0 pozícióban lévő "?" hatástalanítja az általános diszk kiválasztó funkciót és bármely file-hoz hozzáférést tesz lehetővé az USER számtól függetlenül az aktuális drive-on.

18: file keresés adott belépési ponttól (keresés következőtől)

IN: C = 18
DE = FCB kezdőcím
OUT: A = DIR-kód vagy hibajelzés
  Egyező az előző funkcióval, de nem a könyvtár elején kezdi a keresést, hanem ott, ahol előzőleg abbahagyta egy 17-es, vagy 18-as funkcióhívás.

19: file törlés

IN: C = 19
DE = FCB kezdőcím
OUT: A = DIR-kód vagy hibajelzés
  A kitöltött FCB szerinti file-t letörli a lemezről. Visszatérés azonos az előző file-funkcióknál megismertekkel.

20: sorrendi olvasás (szekvenciális olvasás)

IN: C = 20
DE = FCB kezdőcím
OUT: A = visszatérési kód
  A megnyitott vagy kreált file-t olvassa a diszkről a memóriába, az aktuális puffer-címre rekordonként. A cr-mezőt minden olvasásnál automatikusan növeli, ezzel készíti elő a következő olvasáshoz a soron következő rekord kijelölését.
Sikeres olvasás esetén visszatéréskor A=0, file-on kívüli olvasás esetén (EOF) A=1.

21: sorrendi írás (szekvenciális írás, új rekord hozzátétele a file-hoz)

IN: C = 21
DE = FCB kezdőcím
OUT: A = visszatérési kód
  A megnyitott vagy kreált file-ba felír egy rekordot az aktuális puffer-címet véve figyelembe. A cr-mezőt automatikusan növeli, ezzel biztosítja a következő íráshoz a rekord kijelölését. Ha a hivatkozott cr-hez tartozó rekordban már volt információ, a funkció ezt felülírja.
Sikeres írás esetén visszatéréskor A=0, egyébként:
A = 1: file-on kívüli hivatkozás
A = 2: a diszk tele van.

22: file létrehozás (kreálás)

IN: C = 22
DE = FCB kezdőcím
OUT: A = DIR-kód, vagy hibajelzés
  A funkció az első szabad DIR helyen egy üres file-t hoz létre az FCB-ben megadott néven és drive-on.
Sikertelen végrehajtás esetén a visszatérési érték:
A = 00FH, egyébként pedig A = 0...3 között, a file DIR-szektorbeli sorszáma szerint.
A funkció megengedi egy, már létező file nevével való újbóli kreálást, nem törli az első file-t.

23: file átnevezés

IN: C = 23
DE = FCB kezdőcím
OUT: A = DIR-kód, vagy hibajelzés
  Ebben a funkcióban az FCB-t speciálisan kell kitölteni. A rendszer az FCB első 16 byte-jában megadott nevű összes file-t átnevezi az FCB második 16 byte-jában megadott névre.
Sikertelen végrehajtás esetén nem találta az input file-t a visszatérési érték:
A = 0FFH, egyébként A = 0...3

24: drive on-line státusz lekérdezése

IN: C = 24
OUT: HL = on-line státusz
  A HL regiszter bitjei közt levő 1-esek jelölik az online drive-okat. Az L regiszter legkisebb helyiértékű bitje az A drive-nak, a H regiszter legnagyobb helyiértékű bitje a P drive-nak felel meg.
Egy drive akkor van on-line állapotban, ha az utolsó rendszerinicializálás óta volt kiválasztva, azaz aktualizálódott a foglaltsági térképe.

25: aktuális drive lekérdezés

IN: C = 25
OUT: HL = az aktuális drive kódja
  A funkció az aktuális drive kódját adja az A regiszterben: a 0 felel meg az A drive-nak, a 15 a P drive-nak.

26: aktuális puffer-cím beállítása

IN: C = 26
DE = aktuális puffer-cím
OUT: -
  Annak a címnek a beállítására szolgál, ahová a lemezolvasási funkció olvas, illetve ahonnan az írás-funkció veszi a felírandó karaktereket. A cím értékét az operációs rendszer 80H-ra állítja reset vagy betöltés után.

27: diszk foglaltsági térkép kezdőcímének lekérdezése

IN: C = 27
OUT: HL = diszk foglaltsági térkép kezdőcíme
  A diszken még meglévő szabad hely meghatározását teszi lehetővé a funkció az aktuális drive-on. Egy adott blokk foglaltságát 1 értékű bit jelenti. A lemezen lévő blokkok a foglaltsági bitekkel sorrendben vannak összerendelve.

28: írástiltás a diszken

IN: C = 28
OUT: -
  A funkció az aktuális diszkre ideiglenes, a következő rendszer-újratöltésig érvényes írásvédelmet biztosít, írási kísérlet esetén az operációs rendszer hibajelzéssel figyelmeztet az írástiltás tényére.

29: drive R/O lekérdezés

IN: C = 29
OUT: HL = R/O vektor
  A HL regiszterpár 1 értékű bitjei jelentik az adott drive-ok csak olvasható állapotát: az L regiszter lég kisebb helyiértékű bitje tartozik az A drive-hoz, a H regiszter legnagyobb helyiértékű bitje a P drive-hoz.
Az operációs rendszer lemezcsere, vagy C esetén állítja be a biteket, a felhasználó a 28. funkcióval módosíthatja.

30: file státusz állítás

IN: C = 30
DE = FCB kezdőcím
OUT: A = DIR-kód, vagy hibajelzés
  A funkció a DE-ben megadott FCB-szerint a hivatkozott file READ Only, SYS file és felhasználói státuszát állítja.
Sikertelen végrehajtás esetén visszatéréskor A = 0FFH egyébként A = 0...3 értékű.

31: diszk paraméter-tábla kezdőcímének lekérdezése

IN: C = 31
OUT: HL = kezdőcím
  A funkció az aktuális diszk-paraméterek lekérdezésére vagy pillanatnyi átírására ad lehetőséget, a leíró blokk kezdőcímének megadásával.

32: USER kód lekérdezése és állítása

IN: C = 32
E = lekérdezés esetén: 0FFH
E = állítás esetén: USER kód (mod 8-cal)
OUT: A = lekérdezés esetén: aktuális USER kód
A = állítás esetén: érdektelen
  Az aktuális USER kód lekérdezését vagy megváltoztatását teszi lehetővé.

33: file random olvasása (direkt hozzáférés)

IN: C = 33
DE = FCB kezdőcím
OUT: A = visszatérési kód
  A random olvasás a file egy, az FCB 33 ... 35. byte-ja alapján kiválasztott rekordját olvassa a megadott puffer-címre, ezzel lehetővé teszi a nem sorrendi file hozzáférést. Az r0 tartalmazza a legkisebb helyiértékű byte-ot az r2 legnagyobb helyiértékűt.
A file DIR-ben való dokumentálása érdekében célszerű először r0 = r1 = r2 = 0 értékkel meghívni a funkciót.
A funkció a cr-t nem növeli, ezért ismételt hívás esetén a cr növeléséről gondoskodni kell.
Visszatéréskor az A = 0 érték jelenti a sikeres végrehajtást, egyébként:
A = 1 nem felírt blokk olvasási kérése,
A = 3 sikertelen lezárás,
A = 4 nem létező rekord keresése.

34: file random írása (direkt hozzáférés)

IN: C = 34
DE = FCB kezdőcím
OUT: A = visszatérési kód
  A kijelölt puffer-cím által mutatott memóriahelytől egy rekordot felír az r0 . r2-vel megadott helyre. Az r2 byte reprezentálja a legfelső helyiértéket, az r0 a legalsót.
A funkció a cr-mezőt nem növeli, így ismételt hívás esetén, ha a felhasználó nem növeli a cr-t, ugyanezt a rekordot fogja írni.
Random írás esetén a file logikai méretét nem a ténylegesen elfoglalt rekordok: száma, hanem az utolsó és a 0. rekordok közötti különbség adja.
Sikeres végrehajtás esetén A = 0 a visszatérési érték. A hibajelzések:
A = 3 sikertelen lezárás,
A = 4 nem létező rekord keresése,
A = 5 a directory megtelt.

35: file méret számítás

IN: C = 35
DE = FCB kezdőcím
OUT: -
  A funkció a DE regiszterben megadott GCB random mezőjét állítja be. A név alapján megkeresett file utolsó utáni rekordjának címe kerül az r0 . r2 byte-okba, lehetőséget adva ezzel a file vége utáni írásra vagy sorosan irt file esetén a fizikai méret számítására.

36: random mező beállítás

IN: C = 36
DE = FCB kezdőcím
OUT: -
  A funkció a DE regiszterben megadott FCB-ben mutatott könyvtári kötet sorszáma, a rendszer által használt aktuális könyvtársor és a könyvtári soron belüli rekordszámláló adatai alapján kitölti az r0...r2 byte-okat az adott rekordnak megfelelően, lehetőséget adva ezzel random operációra való áttérésre a soros file-oparációról.

240...256:

IN: C = 240...256
  Ezen funkciók paramétereit és jellemzőit a felhasználó definiálhatja saját igényei szerint.

Vissza