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
s

 

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 helyiértékű byte-ja, mint a nagyobb:

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

LD A,(BC)

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

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

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

LD A,(IY+03H)

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

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

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

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

A program:

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

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

(nn) L
(nn+1) H

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

nagyobb 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 helyié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 helyiértékű byte-jába, míg az nn+1 című memóriarekesz tartalma az IX indexregiszter nagyobb helyié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 helyié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 helyié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 helyiértékű byte-jának, az L regiszternek a tartalma az nhnl című memóriarekeszbe, a nagyobb helyié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 helyiértékű byte-ja az nhnl című memóriarekeszbe, a nagyobb helyié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 helyiértékű byte-ja az nhnl című memóriarekeszbe, a nagyobb helyié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 helyié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 helyié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 helyié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 helyié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 helyié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 helyié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:

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:

 

 

Vissza