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
a

 

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ó un. 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. (Az INTEL mnemonikok listáját ld. a VII.2. fejezetben).
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 helyé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 helyérték
kisebb helyé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 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 un. 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
LD(SPO),SP
LD SP,500H
JP MENU
SPO
OLV
DEFS 2
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
LD SP,(SPO)
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

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:

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 un. ü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 un. 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 un. 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 un. 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

 

 

Vissza