Gépi kódú programozás
(Assembly)
Tartalom
I. rész - Alapfogalmak Hajnal Csaba |
I. rész Matusa István HSOFT |
A most induló sorozatban az assembly programozás rejtelmeibe szeretném bevezetni az érdeklődőket. Megismerkedünk az alapfogalmakkal, a processzorral és utasításkészletével, a számítógép felépítésével, operációs rendszerével. Lehet, hogy sokan már e rövid bevezető után tovább lapoznak, mert néhány kifejezés jelentésével nincsenek tisztában. Ne tegyék, már kezdjük is!
Számrendszerek, bitek, byte-ok
Elsőként a kettes (bináris) számrendszerrel ismerkedünk meg. A kettes számrendszer csak két számjegyet használ: a nullát és az egyest. A kettes számrendszerben ugyanúgy lehet számokat ábrázolni, mint a jól megszokott tízesben (decimális). A kettes számrendszerbeli számjegyeket a számítástechnikában biteknek nevezik (BIT=BInary digiT, kettes számrendszerbeli szám). A bitek növekvő sorszámozása jobbról balra történik. Ábrázoljuk példaképpen a 147-es számot bináris számként!
Bitsorszám: | 7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
Értéke | 128 |
64 |
32 |
16 |
8 |
4 |
2 |
1 |
|
147= | 1 |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
b |
A szám jegyet követő "b" betű a bináris számrendszer jelzésére szolgál. Az adott bit decimális értékét úgy határozzuk meg, hogy kettőt az adott hatványra emeljük. A hetedik bit értéke 128, mert 2^7=128 stb. A 147-et a következőképpen kapjuk meg:
1*2^7+0*2^6+0*2^5+1*2^4+0*2^3+0*2^2+ 1*2^1+1*2^0=128+16+2+1=147.
(A ^ a hatványozás jele.)
Nem véletlenül ismertettem a bitek ilyen nyolcas fel állását. Ezt szokás ugyanis byte-nak nevezni. A byte-ban tehát nyolc bit helyezkedik el, így rajta 11111111b=255 és a 00000000b=0 tartomány ábrázolható. A biteket másképpen is lehet értelmezni, de erről majd később lesz szó.
E
gyszerre két byte-on is ábrázolhatunk számokat, ezt szokás szónak (word, szó) nevezni. Egy szón 65535 és 0 közötti számok férnek el. Írjuk fel gyakorlásképpen az 57269-et!
Bitszám: | 15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
57269= | 1 |
1 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
1 |
1 |
0 |
1 |
0 |
1 |
b |
A kettes számrendszerben felírt számok sokjegyűek, nehezen megjegyezhetők. Éppen ezért célszerűbb a 16-os (hexadecimális) számrendszer használata. Ebben sincs semmi különös, adott helyiértéken 16-nak megfelelő hatványa szerepel. A hexadecimális számrendszerben 16 számjegy van: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F.
A 147 hexadecimális ábrázolásához bináris megfelelőjéből induljunk ki!
147 = 1 0 0 1 0 0 1 1 b
Vizsgáljuk meg a felső és az alsó 4 bitet önmagában. A felső 4 bit: 1001b, értéke 9h (a "h" a hexadecimális számot jelöli), az alsó 4 bit: 0011b, értéke 3h. így tehát 147=93h.
Nézzük meg a 16 biten ábrázolt 57269-et hasonló szempontok alapján!
57269 = 1 1 0 1 1 1 1 1 1 0 1 1 0 1 0 1 b
Osszuk fel ezt is 4 bites csoportokra! Az első: 1101b=Dh, a második: 1111 b=Fh, a harmadik: 1011b=Bh, végül a negyedik: 0101b=5h. Ezek szerint 57269=DFB5h.
Ebben a formájukban a számok sokkal jobban kezel hetők, megjegyezhetők. Egy byte értéket kétjegyű, egy szó értékét négyjegyű hexadecimális számon ábrázolhatjuk. Szeretném hangsúlyozni, hogy a hexadecimális számrendszer használata elsősorban kényelmi célokat szolgál.
Vázlatosan a számítógépről
A számítógép "motorja" a mikroprocesszor (CPU=Central Processing Unit, központi feldolgozó egység). Az ENTERPRISE-ban a Zilog cég Z80A típusú CPU-ja dolgozik. E sorozat célja, hogy a Z80-as CPU programozását bemutassa, így erről még bőven lesz szó.
A számítógép tárában tárolódnak az adatok. A táraknak két alapvető típusa van: az írható-olvasható tár (RAM-Random Access Memory, közvetlen hozzáférésű memória) és a csak olvasható tár (ROM-Read Only Memory, csak olvasható memória).
Az általunk írt programok a RAM-ban tárolódnak. A RAM-ok a tápfeszültség kikapcsolásakor elvesztik tartalmukat, ezért kell minden programunkat kazettára, lemezre menteni.
A ROM-okban tárolt információk a tápfeszültség megszűnése után is megőrződnek. Ilyen tárakba nem tudunk adatokat beírni, csak a gyártók által beégetetteket lehet onnan kiolvasni. ROM tartalmazza a gép működéséhez szükséges alapprogramokat.
A tárat tárrekeszek sorozataként lehet elképzelni, a rekeszek egyenként 8 bitet tartalmaznak, tehát egy tárrekesz egy byte-nak felel meg.
A számítógép a perifériáin keresztül kommunikál a külvilággal. Ilyen periféria például a képernyő, a billentyűzet, a lemezegység stb. Az ENTERPRISE egyik erőssége éppen a perifériakezelésben rejlik.
A CPU alapvetően két részre osztható: a vezérlőegységre (Control Unit) és az aritmetikai-logikai egységre (ALU=Aritmetical-Logical Unit, aritmetikai-logikai egység). A vezérlőegység a memóriának egy adott helyétől kezdve adatokat olvas be. A beolvasott byte-ot, mint utasítást bitenként értelmezi, az utasításban minden egyes bitnek saját jelentése van. (Természetesen egy utasítás nemcsak egy, hanem több byte-ból is állhat.) Az utasítástól függően a CPU sokféle műveletet végezhet. Például újabb adatokat (operandusokat) olvas be a tárból, hogy valamilyen műveletet végezzen velük az ALU segítségével, vagy valamelyik perifériához fordul stb. A vezérlőegység tehát az utasításnak megfelelően vezérli a többi egység (ALU, tár, perifériák stb.) működését. Miután végrehajtotta az utasítást, beolvassa és végrehajtja a következőt. A processzor ilyenkor egy gépi kódú programot hajt végre. A gépi kódú program tehát nem más, mint a memóriában tárolt bináris számok sorozata. A processzor a program utasításainak megfelelően értelmes feladatokat tud elvégezni.
Az első számítógépek az 1940-es évek második felében jelentek meg. Ezeket a gépeket eleinte csak közvetlenül gépi kódban lehetett programozni. Képzeljük csak el az akkori feladat nagyságát: programot kellett írni a processzor saját nyelvén, az embertől teljesen idegen nullák és egyek sorozataként! E tarthatatlan helyzeten segített az assembly (assembly-összeállítás) nyelv kialakulása.
Minden gépi kódú utasításnak van egy ún. mnemonikja. A mnemonikok használatával egyszerűbb a programozó dolga, mert emberközelibbekké válnak az utasítások. Nézzünk egy Z80-as példát, mindenféle egyéb magyarázatot nélkülözve! Feladatunk, hogy a processzor belsejében egy bitet 1-re állítsunk. A szükséges utasítás hossza 1 byte, a gépi kódja: 00110111b=37h. Ez így nem sokat mond! Ugyanennek az utasításnak az assembly megfelelője, a mnemonikja: SCF. Az SCF a "Set Carry Flag" rövidítése. Ez a három betű "nagyságrendekkel" egyszerűbbé teszi az utasítás megjegyzését.
Az assembly-ről tényszerűen
A sorozat címe szándékosan "Assembly", nem "Gépi kód" vagy valami hasonló. Nem gépi kódban fogunk programozni, hanem assembly-ben. Az assembly-ben megírt programot egy fordító programmal, az ún. assemblerrel fogjuk gépi kóddá lefordítani. Mielőtt azonban a programozásba belefognánk, néhány ténnyel tisztában kell lennünk.
Ritkán fordul elő, hogy egy kezdő azonnal assembly programokat ír. Ezt szinte mindig megelőzi egy másik, magasabb szintű programozási nyelv elsajátítása (Pascal, Basic). Előbb-utóbb azonban valami olyan feladatot kellene megoldani az adott nyelven, ami azon eleve lehetetlen, vagy ha ráerőszakolható is a nyelvre, akkor azt lassan végzi el a gép. Ekkor nyújthat segítséget az assembly.
A gépi kódú program minimális helyet foglal a tárban, és nincs nála gyorsabb kód. Szeretném mindjárt hangsúlyozni, hogy az előbbi kijelentéseket nem lehet általánosítani! Semmi akadálya nincs a hosszú, lassú és buta gépi kódú programok írásának.
Az assembly alacsony szintű programozási nyelv. A géphez a lehető legközelebb kerülünk, "lealacsonyodunk" hozzá, programunk veszi át a gép irányítását. Nincs köztünk a Basic értelmező, vagy a Pascal fordító. Ennek persze megvannak a maga hátrányai is. Elveszítünk sokfajta kényelmes lehetőséget, amit a Basic vagy a Pascal biztosít. Az assembly programok elkészítési ideje lényegesen hosszabb, mint az említett nyelveken lenne. Nem lehet az adott gép felépítésének alapos ismerete nélkül igazán kihasználni lehetőségeit.
Minden mikroprocesszor típusnak mások a gépi kódú utasításai. Az ENTERPRISE-on megírt programot az IBM AT nem tudja lefuttatni, bármilyen okos is. Még a Z80-ra épített ZX Spectrum sem fogja "megenni" a kódot, mert teljesen más a két gép szervezése. Továbbá az ENTERPRISE esetében néhány dologgal finoman kell bánni, mert esetleg az angol gépen "kihegyezett" programra a német gép furcsán reagál. (Ezek ún. kompatibilitási problémák. A kompatibilitás összeférhetőséget jelent. Ha két gép teljesen kompatibilis, akkor képesek egymás programjait fogadni, és azokat rendesen végrehajtani.)
Az assembly nem varázsszer! Mielőtt bármiféle assembly program írásába belefognánk, gondoljuk végig a következőket:
A felmerülő kérdéseket még hosszan lehetne sorolni. Basic-ben és főleg Pascal-ban nagyon jó programokat lehet készíteni. Semmiféle problémát nem jelent Basic programunkat gépi kódú rutinokkal megtámogatni. További lehetőség, hogy a belőtt Basic programot lefordítjuk. A Hi-Soft Pascal több függvénnyel, eljárással támogatja a gépi kódú rutinok kezelését. Szeretném megemlíteni, hogy létezik egy programozási nyelv, amelyre a "magasszintű assembly" jelzőt szokták ragasztani. Ezt a programozási nyelvet C-nek hívják. A C programok képességeikben csak kismértékben maradnak el az assembly-ben írt programoktól, ugyanakkor nagymértékben kiküszöbölik azok fejlesztési hátrányait. Kifejezetten az ENTERPRISE-hoz illeszkedő C fordító még nem készült, de az ISDOS-on háromféle is futtatható. (Az IS-DOS az ENTERPRISE bővített lemezes operációs rendszere.)
A gépre koncentrálva
Mint már említettem az ENTERPRISE számítógépet a Zilog cég Z80A típusú CPU-ja vezérli, amelyet 4MHzes órajel éltet. A 4MHz-es órajelnek köszönhetően a gépben minden gépi kódú utasítás néhány mikroszekundum alatt lefut. A Z80A-nak 16 bites címbusza van. Ez azt jelenti, hogy a CPU 2^16, azaz 65536 tárrekeszt tud közvetlenül elérni, megcímezni. A busz egyébként párhuzamos vezetékek halmazát jelenti, amelyekben adatok áramlanak. A busznak 3 fajtája van: címbusz, adatbusz, vezérlőbusz. A címbusz tehát 16 vezetéket jelent, amelyek a CPU-t összekötik (egyebek között) a tárral.
A rekeszt a tárban elfoglalt sorszáma alapján lehet megcímezni. Az első rekesz a 0-ás, a legutolsó a tár tetején, a 65535-ös címen van. Hogy ne kelljen mindig nagy számokról beszélnünk, ismerkedjünk meg a kilobyte (KB) fogalmával. A kilobyte a várttól eltérően nem 1000 byteot, hanem 1024 byte-ot takar. Ebben a bináris számrendszer a bűnös (1024=2^10).
A 65536 byte 64KB-ot jelent (64*1024=65536), a CPU tehát 64KB nagyságú memóriát lát egyszerre. Az ENTERPRISE gépben van egy bonyolult memórialapozó áramkör, ezért képes a gép akár 4096 KB-os, azaz 4MBos (MB=MegaByte, 1MB=1048580 byte=1024KB) tár kezelésére is. A CPU által látott memóriatartományt 4, egyenként 16 KB-os részre, ún. lapra osztották. Egy lapra (page, lap) sorszámával szokás hivatkozni. A lapok elhelyezkedése a következő:
lap | címtartomány |
P0 P1 P2 P3 |
0000h - 3FFFh 4000h - 7FFFh 8000h - BFFFh C000h - FFFFh |
(A P0 a Page0, nullás lap jelölése stb.)
A tár "szeletelése" is ehhez a logikához illeszkedik. A memóriát 16KB-os darabokra, ún. szegmensekre tagolták. A memóriát tehát ilyen szegmensek sorozata alkotja. Minden szegmensnek saját száma van, ez alapján lehet azonosítani. Az ENTERPRISE-ban 256 szegmens fér el, ez képezi a 4096 KB-os maximális memóriát. A CPU valamelyik lapjára a sorszámával azonosított szegmenst lapozhatjuk. A gép nevében a 128K az összes RAM memóriára utal. A 128KB-os RAM tehát 8 szegmenst jelent.
Az ENTERPRISE számára a legfontosabb program az EXOS. Az EXOS (EXOS=ENTERPRISE eXpandable Operating System, ENTERPRISE bővíthető operációs rendszer) a gép operációs rendszere. Az EXOS-ra az ENTERPRISE-nak mindig szüksége van (ez adja a gép "jellemét"), ezért magától értetődően az EXOS-t ROM-ba égették. A CPU az EXOS rutinjait végrehajtva vezérli az ENTERPRISE-t. Az EXOS-ról a sorozatban még bőven lesz szó. Azok a kártyák, amelyek a gép ROM-BAY csatlakozójába illeszthetők, ROM-okat tartalmaznak. Ezek szerint a Basic értelmező is egy ROM-ba égetett program.
A Z80 adatbuszának szélessége 8 bit, a Z80 ún. 8 bites mikroprocesszor. A CPU egyszerre 8 bitet, azaz egy byte-ot tud mozgatni (a tárból beolvasni vagy oda kiírni).
A vezérlőbusz 14 vezetékből áll. Ezekről egyenlőre csak annyit, hogy közöttük van az órajelet szállító vezeték is.
A Z80 regiszterei
A regiszterek a processzor belsejében elhelyezkedő tárrekeszek, amelyekben különféle aritmetikai, logikai műveleteket végezhetünk. Ezek az A, B, C, D, E, H, L regiszterek. Közülük legfontosabb az A regiszter, amelyet akkumulátornak is szokás nevezni. Sok műveletet csak az akkumulátor közreműködésével lehet elvégezni, mert ez adja a művelet egyik operandusát. A regiszterek 8 bitesek, de lehetőség van az összevonásukra, és így kapjuk a 16 bites BC, DE, HL regisztereket. Az akkumulátor "párja" az F (Flag) regiszter, amely szintén 8 bites. A regiszter bitjei a legutoljára elvégzett művelet eredményére vonatkozó információkat szolgáltatnak. A teljes flag regiszterben csak 6 bit van kihasználva:
S |
Z |
H |
P/W |
N |
C |
Programunkban a flag regiszter bitjeit vizsgálva végezzük a különféle aritmetikai műveleteket, feltételes elágazásokat. Egy "átlagos" program leggyakrabban a Z (Zero) és a C (Carry, átvitel) biteket szokta figyelni, kezelni. A jelzőbitek használatát példákban fogjuk látni.
A processzorban két, azonos elemekből álló regiszterkészlet van, a másik csoportot az ún. vesszős regiszterek alkotják: A', F', B', C', D', E', H', L'. A két készletet csak külön-külön lehet használni, köztük az átkapcsolás egy utasítás kiadásával lehetséges.
A regiszterkészlethez tartoznak még a PC (Program Counter, programszámláló), SP (Stack Pointer, verem mutató), IX (Index X), IY (Index Y) 16 bites regiszterek.
Van még két speciális regiszter: az R (Refresh, frissftés), amelyet a RAM memóriák frissítésére használ a CPU, és az I (Interrupt, megszakítás) a Z80 egy különleges (megszakításos) üzemmódjában szolgáltatja a végrehajtandó rutin címének egyik részét.
Eddigi ismereteink már elégségesek ahhoz, hogy nekifogjunk az utasítások megismerésének. A nyelv ismerete nem azonos az utasítások ismeretével, a nyelv programozási technikáját kell elsajátítani.
Adatmozgató utasítások
Ezekkel az utasításokkal adatokat tölthetünk a regiszterekbe, tárhelyekbe. A töltés az LD (LoaD, tölt) utasítással lehetséges. Lássuk azokat az utasításokat, amelyekkel értéket adhatunk egy adott regiszternek!
LD A,n | LD B n | LD C,n | LD D,n | LD E,n | LD H,n | LD L,n |
Az utasítást közvetlenül az érték követi, n egybyte-os adatot jelenti. Például az
LD L,27
hatására decimális 27 kerül az L regiszterbe. A 16 bites regiszterekbe töltő utasítások hasonlóak az előzőkhöz:
LD BC,nn | LD DE,nn | LD HL,nn | LD SP,nn | LD IX,nn | LD IY,nn |
Az utasítást az nn 16 bites adat követi, amelynek regiszterbeli elhelyezkedését még nem ismerjük: a 16 bites adat 2 regiszterben tárolódik (ezt már tudjuk), az alsó (Low) és a felső (High) 8 biten. A felsőn tárolódik (egész számként) az adat 256-al osztott része, az alsón pedig az osztás maradéka lesz. Legyen 57269 a 16 bites adatunk, nézzük meg a felső és az alsó byte-ok értékét!
High=57269/256=223
Low=57269-223*256=181
57269=181+223*256
A BC, DE, HL regisztereknél mindig az első regiszter (B, D, H) a magas, a másik az alacsony. Ezek után világos, hogy az
LD HL,57269
utasítás hatására H-ban 223, L-ben 181 lesz. (Az első részben felírtuk hexadecimális formában az 57269-et, ami DFB5h-val volt egyenlő. Vegyük észre, hogy a hexadecimális formátumnál azonnal adódik a felső és az alsó byte értéke: DFh=223, B5h=181!)
Regiszterből regiszterbe tölthetünk adatot a következő utasításokkal:
LD A,A | LD B,A | LD C,A | LD D,A | LD E,A | LD H,A | LD L,A |
LD A,B | LD B,B | LD C,B | LD D,B | LD E,B | LD H,B | LD L,B |
LD A,C | LD B,C | LD C,C | LD D,C | LD E,C | LD H,C | LD L,C |
LD A,D | LD B,D | LD C,D | LD D,D | LD E,D | LD H,D | LD L,D |
LD A,E | LD B,E | LD C,E | LD D,E | LD E,E | LD H,E | LD L,E |
LD A,H | LD B,H | LD C,H | LD D,H | LD E,H | LD H,H | LD L,H |
LD A,L | LD B,L | LD C,L | LD D,L | LD E,L | LD H,L | LD L,L |
LD A,I | ||||||
LD I,A | ||||||
LD A,R | ||||||
LD R,A |
Az utasításban szereplő első regiszterbe töltődik a második regiszter tartalma. Az
LD A,H
utasításnál tehát az A-ba töltődik a H regiszter tartalma, miközben H változatlan marad. Csak a veremmutatóba tölthetünk 16 bites regiszterből adatot, az
LD SP,HL |
LD SP,IX |
LD SP,IY |
utasításokkal. Triviális, hogy az
LD SP,IX
hatására SP egyenlő lesz IX értékével. A tárból is tölthetünk adatot regiszter(ek)be, 1 byte esetén csak az akkumulátorba tudunk ilyen közvetlen címzéssel adatot mozgatni:
LD A,(addr) |
Az addr jelenti azt a tárcímet (0 <= addr <= 65535), ahonnan az adatot megakarjuk szerezni. Az
LD A,(57269)
hatására az 57269-es tárrekesz tartalma az akkumulátorban lesz. A tárcímet mindig zárójelek közé kell tennünk, ez jelzi a tárcímre hivatkozást. A tárból 16 bites regiszterbe töltő utasításból több is van:
LD BC,(addr) |
LD DE,(addr) |
LD HL,(addr) |
LD IX,(addr) |
LD IY,(addr) |
LD SP,(addr) |
Itt a 16 bites regiszter alsó byte-ba az addr, a felső byte-ba az addr+1 címen levő adat kerül. Például ha az addr egyenlő 57269-el, és az 57269-es címen 145, az 57270-esen pedig 17, akkor az
LD HL,(57269)
után HL egyenlő lesz 4497-el (H=17, L=145; L+256*H=4497). A tárcímet 16 bites regiszterben is megadhatjuk, így már több regiszterbe mozgathatunk a tárból adatot.
LD A,(BC) | LD A,(DE) | LD A,(HL) | LD A,(IX+d) | LD A,(IY+D) |
LD B,(HL) | LD B,(IX+d) | LD B,(IY+d) | ||
LD C,(HL) | LD C,(IX+d) | LD C,(IY+d) | ||
LD D,(HL) | LD D,(IX+d) | LD D,(IY+d) | ||
LD E,(HL) | LD E,(IX+d) | LD E,(IY+d) | ||
LD H,(HL) | LD H,(IX+d) | LD H,(IY+d) | ||
LD L,(HL) | LD L,(IX+d) | LD L,(IY+d) |
Az előzőek után nem kell sokat magyarázni: az adott regiszterbe kerül annak a tárrekesznek a tartalma, amelynek címét a zárójelek közé tett 16 bites regiszter tartalmazza. Annál inkább magyarázatra szorul például azonban az
LD C,(IY+d)
utasításban szereplő "d". Az ilyen utasításokban az indexregiszterek "mellé" is tudunk címezni. A "d" egy 8 bites eltolási (displacement, elmozdítás) értéket jelent, fixen beírva az utasításba. Az eltolással az indexregisztertől a tárban lefelé 128, felfelé 127 byte-al tudunk eltérni. A 8 bitnek efajta értelmezését kettes komplemens ábrázolásnak nevezik, amellyel a Z80 aritmetikánál részletesebben megismerkedhetünk. Ezek szerint az
LD C,(IY-89)
hatására a C regiszterben lesz az IY értékénél 89-el kisebb tárrekesz tartalma.
Nézzük meg az előzőek fordítottját: írjuk ki a tárba a regiszterek tartalmát! Ismét csak az akkumulátor tartalmát tudjuk közvetlenül a tárba tölteni az
LD (addr),A |
utasítással, amelynek végrehajtása után az "addr" című rekeszben lesz az akkumulátor tartalma. Az ide tartozó 16 bites regisztereket "letöltő" utasítások:
LD (addr),BC | LD (addr),DE | LD (addr),HL |
LD (addr),IX | LD (addr),IY | |
LD (addr),SP |
A végrahajtás után az "addr" címen a regiszter alsó, az "addr+1" címen a felső része lesz. Így ha BC-ben 4497 van, és a cím 57269-el egyenlő, akkor az
LD (57269),BC
után 57269-en 145, az 57270-en pedig 17 lesz. Amikor 8 bites regiszter tartalmát akarjuk kiírni, és a tárcím értéke 16 bites regiszterben van, akkor az
LD (BC),A | LD (DE),A | LD (HL),A |
LD (HL),B | LD (HL),C | |
LD (HL),C | LD (HL),E | |
LD (HL),D | LD (HL),L |
utasítások között választhatunk. Ugyanezt az indexregiszterekkel is megtehetjük:
LD (IX+d),A |
|
LD (IX+d),B |
LD (IX+d),C |
LD (IX+d),D |
LD (IX+d),E |
LD (IX+d),H |
LD (IX+d),L |
LD (IY+d),A |
|
LD (IY+d),B |
LD (IY+d),C |
LD (IY+d),D |
LD (IY+d),E |
LD (IY+d),H |
LD (IY+d),L |
Biztos vagyok benne, hogy nem szükséges elmagyarázni az utasítások hatását. Inkább lássuk helyette az adatmozgató utasítások utolsó három darabját!
LD (HL),nn |
LD (IX+d),nn |
LD IY+d),nn |
Az utasításokat átfutva az láthatjuk, hogy az akkumulátornak tényleg kitüntetett szerepe van, minden utasításformában előfordul. Vannak furcsára sikeredett utasítások is. Mire jó például az
LD B,B
utasítás? Tulajdonképpen semmire: a regiszterrel semmi sem történik, a jelzőbiteket sem állítja az F-ben. Egy valamire azonban tökéletes: bárminek az elrontása nélkül lehet vele az "időt húzni", azaz időzíteni. Egy ilyen utasítás az ENTERPRISE esetében 1 mikroszekundumos végrehajtási időt jelent. Működőképes-e az
LD L,(HL)
utasítás? Igen, mert a címképzésben szereplő HL a regiszterbe olvasáskor már nem él, az L-be normálisan betöltődik a rekesz tartalma. Érdekes az
LD (HL),L
viselkedése is. A HL által címzett rekeszbe írhatjuk saját címének alsó (illetve H esetén felső) byte-ját.
Adatcserélő utasítások
A két regiszterkészlet közötti átváltást az EX (EXchange, csere) utasításokkal tehetjük meg. Az
EX AF,AF' |
utasítással váltogathatunk az akkumulátorok között. Az
EXX
végrehajtása után a másik B', C', D', E', H', L' regiszterekkel dolgozhatunk. Persze az, hogy melyik regiszterkészletet tekintjük vesszősnek, teljesen mindegy. Ha ilyen cserélő utasításokat használunk, nem szabad belekeverednünk abba, hogy éppen melyikre van szükségünk. Gyors csere valósítható meg a HL és a DE regiszterek között az
EX DE,HL |
végrehajtásával. A BC regiszter elrontásától eltekintve (ettől elég nehéz eltekinteni...) ugyanezt valósítja meg az
LD B,D
LD C,E
LD D,H
LD E,L
LD H,B
LD L,C
utasítássor is. A BC regiszterre DE átmeneti tárolására van szükség, hiszen DE-t közvetlenül felülírjuk a HL tartalmával, BC HL-be töltése előtt. Az eredmény lényegében ugyanaz, csak éppen a hat utasítás 6 mikroszekundum alatt fut le a Z80ban, a kivesézett utasítás 1 mikroszekundumos végrehajtási idejével szemben. Ehhez a csoporthoz tartoznak a veremhez kapcsolódó utasítások is:
EX (SP),HL |
EX (SP),IX |
EX (SP),IY |
A művelet végrehajtását követően a verem tetején található adat betöltődik az illető regiszterbe, annak eredeti tartalma pedig a verem tetején lesz.
Aritmetika
Regiszter, regiszterpár és tárrekesz tartalmát az INC (INCrement, növelés) utasítással tudjuk eggyel növelni:
INC A |
INC B |
INC C |
INC D |
INC E |
INC H |
INC L |
Például ha az akkumulátorban 38 van, és végrehajtjuk az inkrementáló utasítást, akkor az akkumulátorban 39 lesz, ilyenkor a Z bit értéke végig 0. Amikor a regiszterben a legnagyobb ábrázolható szám (255) van, az utasítás végrehajtását követően a regisztertartalom nullával lesz egyenlő. Ekkor a Z bit jelzi l-be billenésével a zérus eredményt. A 16 bites regiszterek tartalmát az
INC BC |
INC DE |
INC HL |
INC IX |
INC IY |
INC SP |
utasításokkal tudjuk eggyel növelni. Ezek az utasítások egyik jelzőbit állapotát sem változtatják meg. Tárrekesz tartalmát növelhetjük az
INC (HL) |
INC (IX+d) |
INC (IY+d) |
utasításokkal. Ha a megcímzett tárrekesz tartalma nullázódik, a Z bit 1-be billen.
A növelő utasítások kiegészítői az egyesével csökkentő DEC (DECrement, csökkentés) utasítások. 8 bites regiszterek tartalmát lehet csökkenteni a
DEC A |
DEC B |
DEC C |
DEC D |
DEC E |
DEC H |
DEC L |
utasításokkal, amelyek ugyanúgy állítják a Z bitet, mint a növelők. Például amikor az akkumulátorban 1 van (Z bit ekkor még 0), az utasítás kiadása után az akkumulátor tartalma 0 lesz (ekkor a Z bit 1-be billen, jelezve a zérus eredményt). Ha ezt követően ismét dekrementáljuk a regisztert, annak értéke 255 lesz (és a Z bit ismét nullázódik). A 16 bites regiszterek tartalmát csökkentő
DEC BC |
DEC DE |
DEC HL |
DEC IX |
DEC IY |
DEC SP |
utasítások növelő párjaikhoz hasonlóan szintén nem állítják a jelzőbiteket. Állítják viszont a Z bitet a
DEC (HL) |
DEC (IX+d) |
DEC (IY+d) |
utasítások, melyekkel az eggyel növelő, csökkentő utasítások sorát le is zárhatjuk.
Összeadás és kivonás 8 biten
Ezeknél az aritmetikai műveleteknél mutatkozik meg leginkább az akkumulátor kiemelt szerepe. Mindig az A regiszter tartalmazza a művelet egyik operandusát, és az eredmény is az akkumulátorba kerül, felülírva annak eredeti tartalmát. Az összeadást az ADD (ADD, összead) utasításokkal tehetjük meg. Közvetlenül az akkumulátor tartalmához adhatjuk az "n" 8 bites számot az
ADD A,n |
utasítással. Ha a művelet elvégzése előtt az A regiszter tartalma 67, akkor az
ADD A,43
után az akkumulátorban 110 lesz. Ha az akkumulátor értéke nulla, akkor ezt a Z bit 1-es értéke jelzi. Az összeadó utasítások a C bitet is állítják, ennek mechanizmusa a következő: tegyük fel, hogy az A egyenlő 200-zal, a hozzáadandó érték pedig 110-zel. A két szám összege 310, de ezt nyilván nem lehet egyetlen byte-on ábrázolni, a művelet elvégzésekor az akkumulátor tartalma ennek megfelelően túlcsordul, ezt jelzi a C bit 1-es értéke. Ekkor a C bittel együtt az akkumulátor tartalmazza a pontos eredményt, ha szükséges, akkor a túlcsordult bit további összeadásokhoz felhasználható (ezek ún. többszörös pontosságú aritmetikai műveletek). Példánkban a művelet előtt az A tartalma:
1 |
1 |
0 |
0 |
1 |
0 |
0 |
0 |
Az összeadás után a C bit és az A tartalma:
1 |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
0 |
A C bit mindig a legutoljára elvégzett művelet eredményét mutatja (ugyanúgy, mint a többi). Ha a C bitet a byte mellé "ragasztjuk", és a kilenc bitnyi értéket együttesen meghatározzuk, akkor megkapjuk a 310-et (a C bit helyiértéke 256, az akkumulátorban 54 van, 256+54=310). Az akkumulátor tartalmához másik regiszter értékét adhatjuk az
ADD A,A |
ADD A,B |
ADD A,C |
ADD A,D |
ADD A,E |
ADD A,H |
ADD A,L |
utasításokkal.
Ha az akkumulátorhoz egy tárrekesz tartalmát akarjuk hozzáadni, akkor az
ADD A,(HL) |
ADD A,(IX+d) |
ADD A,(IY+d) |
utasításokat használhatjuk.
A művelet során képződő átvitel bitet az akkumulátorhoz adhatjuk az ADC (ADd with Carry, összeadás átvitellel) utasításokkal, melyek a C és a Z biteket ugyanúgy állítják, mint ahogyan azt fentebb láttuk. Az "n" 8 bites közvetlen operandus értékén kívül a C-t is az akkumulátorhoz adja az
ADC A,n |
utasítás. Ha például a művelet elvégzése előtt a C bit értéke 1 (azaz az előző műveletnél átvitel keletkezett), és az akkumulátor tartalma 250-el egyenlő, akkor az
ADC A,15
végrehajtását követően a C bit értéke egy lesz (hiszen újabb átvitel keletkezett), az A regiszterben pedig 10 lesz (250+15+1=266, ebből C bit=1, az akkumulátorban 266256=10).
Az átvitel és egy regiszter értékét az
ADC A,A |
ADC A,B |
ADC A,C |
ADC A,D |
ADC A,E |
ADC A,H |
ADC A,L |
utasításokkal adhatjuk az akkumulátor tartalmához. Ha az operandus a tárban van, akkor az összeadást az
ADC A,(HL) |
ADC A,(IX+d) |
ADC A,(IY+d) |
utasításokkal hajthatjuk végre.
A SUB (SUBtract, kivon) és az SBC (SuBtract with Carry, kivonás átvitellel) utasításokkal tudunk az akkumulátor tartalmából kivonni. Közvetlen értéket a
SUB n |
utasítással vonhatunk ki az akkumulátorból. Ha az A regiszter tartalmából vele egyenlő adatot vonunk ki a Z bit egy lesz (hiszen az eredmény zérus lesz). Amikor a kivonandó nagyobb, mint az akkumulátor tartalma, akkor a C bit 1 lesz, jelezve ezzel az alulcsordulást. Legyen az A regiszterben 5, ekkor a
SUB 10
után a C bit értéke 1, a regiszterben pedig 251 lesz. Eljött az idő arra, hogy tisztázzuk a számok kettes komplemens ábrázolását. Ugyanis a művelet után eredményül kapott átvitel és 251-es érték a -5-öt ábrázolja, a következők szerint: az indexelt címzésnél már láthattuk, hogy a "d" eltolási értékkel az indexregiszter által mutatott címtől lefelé (mínuszban) 128, felfelé (pluszban) 127 byte-tal tudunk eltérni. Ebből talán már látható a 7. bit kitüntetett előjelet helyettesítő szerepe. Ha a 7.bit értéke 1, akkor negatív számról van szó, a többi 7 (6-0) biten a szám lesz ábrázolva. Az előjeles összeadások és kivonások korrekt elvégezhetőségéhez a negatív számokat kettes komplemensükkel kell ábrázolni: a szám abszolút értékében minden bitet ellenkezőjére változtatunk, és ehhez még egyet hozzáadunk. Írjuk fel a -5-öt! Az abszolút érték:
0 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
negálva a biteket:
1 |
1 |
1 |
1 |
1 |
0 |
1 |
0 |
egyet hozzáadva:
1 |
1 |
1 |
1 |
1 |
0 |
1 |
1 |
aminek értéke e példánkban kapott 251. Kettes komplemens ábrázolásban tehát a -128 és a + 127 közötti számokat tudunk ábrázolni, és a Z80 ilyen formában megadott számokkal műveleteket tud végezni. A
SUB A |
SUB B |
SUB C |
SUB D |
SUB E |
SUB H |
SUB L |
utasításokkal regiszter tartalmát vonhatjuk ki az akkumulátorból. (Érdekes, hogy ezeknél az utasításoknál nem kell külön (és feleslegesen) jelölni az akkumulátort, úgy mint például az ADD A,H-nál.) Tárban lévő adatot a
SUB,(HL) |
SUB (IX+d) |
SUB (IY+d) |
utasításokkal tudunk az akkumulátorból kivonni. Az alulcsordult bitet is kivonhatjuk az
SUB A,n |
utasítással. Tehát ha az A regiszterben 5 van, a C bit 1, akkor az
SBC A,10
után az akkumulátorban -6 (250) lesz. Sejthető, hogy regisztertartalmat az
SUB A,A |
SUB A,B |
SUB A,C |
SUB A,D |
SUB A,E |
SUB A,H |
SUB A,L |
tárbeli adatot pedig az
SBC A,(HL) |
SBC A,(IX+d) |
SBC A,(IY+d) |
utasításokkal tudunk kivonni. A jelzőbitek itt ugyanúgy állítódnak, mint ahogyan azokat a példáknál láttuk.
Összeadás és kivonás 16 biten
Ezekről az utasításokról fontos tudnunk, hogy a C (valamint a számunkra érdektelen N és H) biten kívül más jelzőbitet nem állítanak, így az eredmények feldolgozásához a későbbiekben különféle trükköket kell majd alkalmaznunk. Itt a HL regiszterpárnak van kitüntetett szerepe, demonstrálják ezt az
ADD HL,BC |
ADD HL,DE |
ADD HL,HL |
ADD HL,SP |
utasítások, melyek a HL regiszterhez adják a feltüntetett regiszter értékét. Túlcsordulásnál a 15. bitről van átvitel, így ha HL-ben például 57269, BC-ben pedig 8267 van, akkor az
ADD HL,BC
végrehajtása után HL-ben 0 lesz, és a C bit 1-be billen. Az indexregiszterekhez adhatjuk más regiszterek értékét az
ADD IX,BC |
ADD IX,DE |
ADD IX,IX |
ADD IX,SP |
ADD IY,BC |
ADD IY,DE |
ADD IY,IY |
ADD IY,SP |
utasításokkal. Gondolom a működésüket nem kell külön példával illusztrálni, helyette inkább meg kell említeni, hogy a Z80-nak nincsenek olyan utasításai, amelyekkel regiszter tartalmat vonhatnánk ki az indexregiszterek valamelyikéből. Az indexregiszterekhez nem adható hozzá a C bit értéke sem, hiszen ezek a regiszterek (mint a nevük is mutatja) valamilyen adat indexelt elérésére szolgálnak, nem pedig aritmetikai műveletek operandusaiként.
A regisztertartalmakon kívül a C bit értékét is hozzáadhatjuk HLhez az
ADC HL,BC |
ADC HL,DE |
ADC HL,HL |
ADC HL,SP |
utasításokkal. Ha előző példánál maradunk azzal a különbséggel, hogy kiindulásnál a C bit értéke 1, akkor az
ADC HL, BC
végrehajtását követően HL-ben 1 lesz, és C bit továbbra is túlcsordulást jelez az 1-be billenésével.
Nincsen 16 bites SUB utasítás, csak elsődlegesen a HL regiszterből kivonó
SBC HL,BC |
SBC HL,DE |
SBC HL,HL |
SBC HL,SP |
utasítások léteznek. Legyen HL-ben 57269, BC-ben 57268, és C bit 1. Az
SBC HL,BC
után HL-ben 0 lesz, hiszen HL-ből BC-t és C bitet (azaz egyet) is kivont az utasítás. Ha induláskor BC-ben is 57269 lett volna, akkor a műveletet követően HL-ben 65535 és C bit értéke ismét 1 (az alulcsordulást jelezve) lenne.
Logikai műveletek
A Z80 négyféle logikai műveletet tud elvégezni: AND (ÉS), OR (VAGY), XOR (KIZÁRÓ VAGY), NOT (NEM). Először nézzük meg a műveletek jelentését az ún. igazságtáblázatokon!
|
|
||||||||||||||||||
|
|
A processzor ezeket a műveleteket bitről bitre elvégzi a teljes regiszteren, közben a C bit mindig 0 lesz, Z pedig a művelet eredményről ad felvilágosítást. Mint ahogy azt már megszoktuk, a művelet befejezésekor az eredmény az akkumulátorban lesz.
Az AND műveletek
Használhatjuk az
AND n |
AND A |
AND B |
AND C |
AND D |
AND E |
AND H |
AND L |
AND (HL) |
AND (IX+d) |
AND (IY+d) |
utasításokat. Tegyük fel, hogy A-ban 147 van, E-ben pedig 117. Az
AND E
végrehajtása után A = 17 lesz, mert
A | 1 |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
E | 0 |
1 |
1 |
1 |
0 |
1 |
0 |
1 |
A | 0 |
0 |
0 |
1 |
0 |
0 |
0 |
1 |
(a legalsó sor a művelet utáni akkumulátortartalmat jelképezi). Az AND művelettel a gyakorlatban ún. maszkolásokat végezhetünk: egyes biteket, bitcsoportokat egyszerűen nullába állíthatunk.
Az OR műveletek
A lehetséges utasítások szervezése az AND-hez idomul.
OR n |
OR A |
OR B |
OR C |
OR D |
OR E |
OR H |
OR L |
OR (HL) |
OR (IX+d) |
OR (IY+d) |
Az A és az E regiszterek tartalma legyen ugyanaz, mint az előző példában, így az
OR E
eredményeként A-ba 247 került, mert
A | 1 |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
E | 0 |
1 |
1 |
1 |
0 |
1 |
0 |
1 |
A | 1 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
Az OR művelettel biteket, bitcsoportokat határozottan 1-be állíthatunk.
A XOR műveletek
Már sejthetjük az utasításformákat:
XOR n |
XOR A |
XOR B |
XOR C |
XOR D |
XOR E |
XOR H |
XOR L |
XOR (HL) |
XOR (IX+d) |
XOR (IY+d) |
Maradjunk továbbra is az előző példa kiinduló állapotánál, így a
XOR E
Művelet után az akkumulátor 230-al lesz egyenlő. A magyarázat:
A | 1 |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
E | 0 |
1 |
1 |
1 |
0 |
1 |
0 |
1 |
A | 1 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
Ha ismét végrehajtjuk az utasítást, akkor az A regiszter tartalma az eredeti 147 lesz, mert
A | 1 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
E | 0 |
1 |
1 |
1 |
0 |
1 |
0 |
1 |
A | 1 |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
A XOR művelet ismételt végrehajtásával ezek szerint biteket, bitcsoportokat tudunk billegtetni. A műveletnek ezt a kellemes tulajdonságát szokás többnyire a sprite-ok kezelésénél is kihasználni, mert egyszerű a sprite kirakása, visszavétele, az elrontott háttér pedig automatikusan helyreáll.
A NOT művelet
A műveletet a CPL (ComPLement, komplementál, kiegészít) utasítás hajija végre. Legyen az A regiszter tartalma 147, így a
CPL
végrehajtását követően az akkumulátorban 108 lesz, hiszen az összes bit az ellenkezőjére változik:
A | 1 |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
A | 0 |
1 |
1 |
0 |
1 |
1 |
0 |
0 |
Összehasonlító utasítások
Mint az utasításcsoport elnevezése mutatja, ezekkel a műveletekkel két adatot hasonlíthatunk össze. A "központi illetékes" szerepét itt is az akkumulátor tölti be, mindig ez szolgáltatja az egyik operandust. A végrehajtást követően a jelzőbitek az eredménynek megfelelően beállnak, az összehasonlított regiszterek tartalma változatlan marad. A CP (ComPare, összehasonlítás) műveletek lehetséges formái:
CP n |
CP A |
CP B |
CP C |
CP D |
CP E |
CP H |
CP L |
CP (HL) |
CP (IX+d) |
CP (IY+d) |
Nézzük meg a C és a Z bitek viselkedését három alapvető esetben! Legyen az akkumulátor tartalma végig 147, a másik operandust E tartalmazza, így a
CP E
műveletet hajtjuk majd végre az összes példában.
A megértésben sokat segíthet, ha tudjuk, hogy a CP utasítások tulajdonképpen kivonást végeznek az eredmény visszaírása nélkül.
Ugró utasítások
Az ugró utasításokkal tulajdonképpen közvetett módon a PC regiszterbe töltünk új adatot, így a processzor a tár más helyéről olvassa be az utasításokat. PC-relativ és abszolút ugrásokat hajthatunk végre, akár feltételesen is, a feltételbitek megváltozása nélkül. A JP (JumP, ugrás) utasítások feltétel nélküli, abszolút formái a következők:
JP addr |
JP (HL) |
JP (IX) |
JP (IY) |
Tegyük fel, hogy addr egyenlő 57269-el, a
JP 57269
utasítás pedig a 34567-os címen van. A végrehajtás pillanatában PC már a JP utasítás után elhelyezkedő következő utasításra mutat, azaz PC=34570 (a JP addr utasítás 3 bájt hosszú). A processzor ebben a pillanatban azonban felismeri az ugró utasítást, és betölti a PC-be az 57269-es értéket, Így a következő utasítást már innen olvassa be, nem pedig a 34570-es címről. Az utasítások feltétel nélküliek, hiszen a végrehajtásuk mindig bekövetkezik. Abszolútak, mert egy konkrét címre hivatkoznak. Feltételes abszolút ugrásokat hajthatunk végre a
JP Z,addr |
JP NZ,addr |
JP C,addr |
JP NC,addr |
és a számunkra egyelőre lényegtelen
JP P,addr |
JP PE,addr |
JP PO,addr |
JP M,addr |
utasításokkal. Szeretném hangsúlyozni, hogy az utasításokban szereplő Z, NZ, C, NC feltételek nem az adott feltételbit értékére, hanem az előzőleg elvégzett művelet eredményére vonatkoznak! A
JP Z,addr
utasítás például akkor hajtódik végre, ha mondjuk az előző kivonás eredménye nulla volt (ekkor Z bit 1), nem pedig olyankor, amikor a Z bit értéke 0.
Feltétel nélküli relatív ugrást (Jump Relative, relatív ugrás) hajthatunk végre a
JR d
utasítással. A "d" egy kettes komplemensben ábrázolt számot jelent, aminek báziscímét a JR utasítás utáni cím szolgáltatja. Ez annyit jelent, hogy magától a 2 bájt hosszú JR utasítástól számított -126 ... +129 tartományon belül tudunk ugrani. Az ugrást tehát a JR utasításhoz viszonyítva hajthatjuk végre. Feltételekhez köthetjük a vezérlésátadást a
JR Z,d |
JR NZ,d |
JR C,d |
JR NC,d |
utasításokkal. A "d" értékek kiszámítása természetesen nem a mi feladatunk, ezt majd az assembler megteszi.
Az Z80-nak van egy különleges ciklusszervező utasítása is. A
DJNZ d |
utasítás (Decrement Jump if Non Zero, csökkentés és ugrás, ha nem nulla) a B regiszter tartalmát csökkenti, és amíg az a nullát el nem éri, addig a vezérlés a "d" által meghatározott relatív címen folytatódik. Ha a B regiszter értéke nulla lesz, a DJNZ utasítás után következő utasítást hajtja végre a processzor.
Szubrutinok
A programokban több helyen gyakran ugyanazt a feladatot kell e lvégeztetnünk a processzorral (pl. képernyőtörlés, billentyűleütésre várakozás stb.). Ilyen esetekben nem fordítjuk be újra és újra a programrészt a főprogramba, hanem csak egyszer írjuk meg, és a szükséges helyekről ezt az egy rutint hívjuk meg, a rutin lefutása után ott folytatódik a végrehajtás, ahol az abbamaradt. Ezeket a "magányos", de sok helyről hívott programrutinokat nevezzük szubrutinoknak. A sorozat második részében már leírtam az SP regiszter szerepét a szubrutinhívásnál. Ezért itt most csak annyit említenék meg, hogy a regiszterek pillanatnyi állapotának megőrzése a mi feladatunk. Az előző mondat pontos jelentését, és a probléma megoldását később részletezem. A szubrutinhívó utasítások a feltételbiteket nem állítják.
Az egyetlen feltétel nélküli szubrutinhívó CALL (CALL, hívás) utasítás a
CALL addr |
Feltételhez köthetjük a szubrutin meghívását a
CALL Z,addr |
CALL NZ,addr |
CALL C,addr |
CALL NC,addr |
és a
CALL P,addr |
CALL PE,addr |
CALL PO,addr |
CALL M,addr |
utasításokkal.
A szubrutin lefutása után a végrehajtásnak valahogy vissza kell kerülnie a hívó programhoz. Erre szolgálnak a RET (RETurn, vissza) utasítások. Feltétel nélküli formája a
RET |
míg a feltételhez kötöttek a
RET Z |
RET NZ |
RET C |
RET NC |
RET P |
RET PE |
RET PO |
RET M |
utasítások, melyek az JP, JR utasításokhoz hasonlóan nem módosítják az F regiszter bitjeit.
Nézzünk egy példát szubrutinhívásra konkrét számokkal! Kiindulási állapotban SP=1000, a CALL 3000 utasítás pedig a 2000-es címen legyen (tehát a szubrutin a 3000-es címen kezdődik). A CALL parancs hatására a processzor az SP által címzett verem tetejére teszi a CALL utasítás után következő utasítás címét, a veremre 2003 kerül, hiszen a CALL utasítás 3 bájt hosszú. Ezután SP értéke kettővel csökken (998), így az már készen áll az esetleges további hívások címeinek elrakásához. A PC-be betöltődik a 3000-es érték, elkezdődik a szubrutin végrehajtása. A rutin végén álló RET hatására az SP értéke kettővel növekszik (ismét 1000), és a verem tetején lévő érték betöltődik a PC-be, így a program végrehajtása 2003-as címen folytatódik.
Az egybájtos RST (ReSTart, újrakezd) utasítások is szubrutinhívást valósítanak meg, de az
RST 0h |
RST 8h |
RST 10h |
RST 18h |
RST 20h |
RST 28h |
RST 30h |
RST 38h |
utasítások sokkal rugalmasabbak a CALL-oknál. Az utasítások végén álló számok memóriahelyekre hivatkoznak. Például az
RST 0h
hatására a vezérlés a 0h címen kezdődő rutinra adódik át, ahol egy újabb szubrutinhívás, ugróutasítás stb. állhat. A dolog rugalmassága abban nyilvánul meg, hogy a főprogramnak mindig csak RST utasítást kell generálnia, de az RST utasítással meghívott rutinban az újabb hívások a főprogram tudta nélkül módosíthatók. Ha például a 0-ás címre egy CALL 57269 utasítást teszünk, majd később ezt mondjuk kicseréljük egy CALL 43923-al (mert mondjuk elkészült a meghívandó rutin újabb verziója), akkor a főprogramból továbbra is az RST 0h utasítást használhatjuk anélkül, hogy tudnánk a végrehajtandó rutin kezdőcímének megváltozásáról. Az RST utasítással hívott rutinból is RET-tel lehet visszatérni, az utasítás nem állítja a jelzőbiteket. Az ENTERPRISE-ban az RST utasításoknak rendkívül fontos szerepük van, többek között az EXOS-t is ezek segítségével hívjuk, lévén az EXOS maga is egy nagy szubrutingyűjtemény.
Veremkezelő utasítások
Nincs sok dolgunk, hiszen már eddig is jónéhány veremkezelő utasítást ismerünk. Az összes olyan ide sorolható, amelyben szerepel az SP regiszter. A veremmel kapcsolatos, a feltételbiteket nem állító utasítások két csoportját kell még megismernünk. Az egyik a veremre teszi az illető regiszter tartalmát, ezek a
PUSH AF |
PUSH BC |
PUSH DE |
PUSH HL |
PUSH IX |
PUSH IY |
utasítások. A másik csoport az adott regiszterbe tölti a verem tetején lévő szót:
POP AF |
POP BC |
POP DE |
POP HL |
POP IX |
POP IY |
A veremre tehát ezekkel az utasításokkal tehetünk, és vehetünk le ideiglenesen ott tárolt adatot. Ezt a módszert használjuk akkor, amikor például a szubrutinok elején elmentjük az összes, a rutinban használt regiszter tartalmát azért, hogy a főprogram a szubrutin hívás előtti helyes adatokkal tudjon majd tovább futni a szubrutinból való visszatérés után (erre utaltam a "regiszterek pillanatnyi állapotának megőrzése"-ként). Ha a szubrutinnak a vermen keresztül adunk át paramétereket, akkor ne feledkezzünk meg arról, hogy a verem tetején PC értéke van, a paraméterek ez alatt vannak! Látható, hogy csak olyan veremkezelő utasítások vannak, amelyekben regiszterpár szerepel.
Bitműveletek
A bitműveletekkel regiszterek, tárrekeszek bitjeit tudjuk 1-be vagy 0-ba állítani, illetve meg tudjuk vizsgálni a bitek állását. Ezekből az utasításokból olyan sok van (összesen 240), hogy értelmetlen felsorolni az összeset, helyette inkább egy kissé általánosított formába öntve nézzük meg őket. Vezessük be a "b" jelölést, ahol "b" a bájton belüli bitsorszámot jelöli (b7...b0), így 7>=b>=0.
Bitállítást végeznek a SET (SET, beállítás) utasítások:
SET b,A |
SET b,B |
SET b,C |
SET b,D |
SET b,E |
SET b,H |
SET b,L |
SET b,(HL) |
SET b,(IX+d) |
SET b,(IY+d) |
Példaképpen emeljük ki a
SET 3,E
utasítást, melynek végrehajtása után az E regiszter 3. bitje 1-be billen. A biteket törölhetjük a RES (RESet, törlés) csoport utasításaival:
RES b,A |
RES b,B |
RES b,C |
RES b,D |
RES b,E |
RES b,H |
RES b,L |
RES b,(HL) |
RES b,(IX+d) |
RES b,(IY+d) |
Ha az előző példán felbuzdulva megvizsgáljuk a
RES 3,E
utasítást, akkor a végrehajtása után az E regiszter 3. bitje törlődik. A BIT és a RES utasítások a feltételbiteket nem állítják, nem így a bit vizsgáló BIT (BIt Test, bitvizsgálat) utasítások:
BET b,A |
BIT b,B |
BIT b,C |
BIT b,D |
BIT b,E |
BET b,H |
BIT b,L |
BIT b,(HL) |
BIT b,(IX+d) |
BIT b,(IY+d) |
Ha a vizsgált bit értéke 1, akkor Z bit 0, egyébként Z bit 1 lesz; C bit egyik esetben sem változik. Legyen az E regiszterben 8, ekkor a
BIT 3,E
utasítás végrehajtását követlen a Z bit 0 lesz, jelezve a 3. bit 1-es értékét.
Portműveletek
A Z80 portjain keresztül kommunikál más áramkörökkel, perifériákkal (a port kaput jelent), nincs ez másképpen az ENTERPRISEban sem: portműveletekkel lapozhatjuk a memóriát; az EXOS rutinjai is ilyen műveletekkel végzik a kooprocesszorok, a nyomtató, a magnó stb. vezérlését. A portok is saját címmel azonosíthatók, ezeket portcímeknek hívjuk. A Z80-nak 256 portcíme van, 1 bájtos adatokat olvashatunk be róluk, illetve írhatunk ki rájuk.
A beolvasást az IN (INput, bemenő) utasításokkal végezhetjük. Az akkumulátorba az
IN A,(p) |
utasítással tudunk közvetlenül adatot beolvasni a "p" portcímről (0<=p<=255). Ez a közvetlen címzésű forma nem állítja a jelzőbiteket. Legyen a portcím B3h, ekkor az
IN A,(B3h)
után az akkumulátorban lesz a B3h port tartalma. Ha a port címét indirekt módon akarjuk megadni, és más regiszterben szeretnénk megkapni a port értékét, akkor az
IN A,(C) |
IN B,(C) |
IN C,(C) |
IN D,(C) |
IN E,(C) |
IN H,(C) |
IN L,(C) |
utasítások közül választhatunk, ahol C a C regisztert jelenti, amely a port címét tartalmazza. A beolvasott értéknek megfelelően megváltozik a Z bit, a C bit változatlan marad. Portra írni az OUT (OUTput, kimenő) utasításokkal tudunk, közvetlen címmegadással az akkumulátorból az
OUT (p),A |
utasítással küldhetünk adatot valamelyik eszköznek. Indirekt módúak az
OUT (C),A |
OUT (C),B |
OUT (C),C |
OUT (C),D |
OUT (C),E |
OUT (C),H |
OUT (C),L |
utasítások. Az OUT utasítások nem módosítják a jelzőbiteket.
Eltoló utasítások
Ha regiszterben vagy memóriában elhelyezkedő biteket szeretnénk balra jobbra mozgatni, akkor ezt az eltoló utasításokkal tehetjük meg. Nyilván a léptetés során egy-egy bit mindig kiesik, de nem vész el, hiszen ez a bit bekerül a C bitbe. Ha a C bit tartalma a következő eltoló műveletnél visszalép a túloldalon a bájtba, akkor ciklikus eltoló művelettel van dolgunk. Az eltoló műveletekkel egyszerűen elvégezhető a kettővel való szorzás és az osztás. Ezt könnyen beláthatjuk, mert ha például a bájtban 00101001b (41) van, és ezt a tartalmat balra egy hellyel eltoljuk, akkor a 01010010b (82) kétszeres érték fog megjelenni. Ilyen utasításokat használunk jobbra-balra scrollozó rutinokban is.
Regiszter vagy tárrekesz tartalmát tudjuk jobbra léptetni az SRL (Shift Right Logical, logikai eltolás jobbra) utasításokkal, a már ismerős formákkal:
SRL A |
SRL B |
SRL C |
SRL D |
SRL E |
SRL H |
SRL L |
SRL (HL) |
SRL (IX+d) |
SRL (IY+d) |
Az SRL utasítás tehát egy hellyel jobbra lépteti a biteket, a jobboldalon kipottyanó bit a C bitbe kerül, a bájt 7. bitje pedig 0 lesz.
Másfajta biteltolást végez az SRA (Shift Right Aritmetical, aritmetikai eltolás jobbra) utasítás.
SRA A |
SRA B |
SRA C |
SRA D |
SRA E |
SRA H |
SRA L |
SRA (HL) |
SRA (IX+d) |
SRA (IY+d) |
Az SRA utasításnál a jobboldalon kieső bit szintén a C bitbe kerül , a b7 értéke viszont végig az eredeti marad.
Balra végez eltolást az SLA (Shift Left Aritmetical, aritmetikai eltolás balra) utasítás.
SLA A |
SLA B |
SLA C |
SLA D |
SLA E |
SLA H |
SLA L |
SLA (HL) |
SLA (IX+d) |
SLA (IY+d) |
Az utasítás hatására a bájt bitjei egy hellyel balra mozdulnak, a baloldalon kieső bit C bitbe kerül, b0-ba pedig 0 kerül.
Eddig bölcsen hallgattam arról, hogy mit is jelent az utasítások elnevezésében a logikai, illetve az aritmetikai eltolás kifejezés. Az aritmetikai eltolásoknál fontos szerepe van az előjelbitnek is. Az SRA utasítás is ezért hagyja változatlanul a 7. bit (előjelbit) értékét. Osszuk el példaképpen a -20-at (11101100b) 2-vel! Az SRA utasítás végrehajtása után a megkapjuk a -10-et (11110110b). Ha balra mozgatunk (azaz kettővel szorzunk), akkor az előjelbit értelemszerűen ki fog esni, de a C bitben rendelkezésünkre áll, ennek kezelése a mi feladatunk. Legyen a szám -30 (11100010b), melynek kétszerese -60 (11000100b) előáll az SLA után. A logikai eltolások - az ábrán látható módon - egyszerűen csak a biteket mozgatják. Mivel az SLA utasítás mindkét célnak tökéletesen megfelel, így nincs külön valamiféle SLL nevezetű utasítás. A C bit (ezek szerint) a kilépő bitet tárolja, a Z bit pedig mindig a kapott eredménynek megfelelően áll be.
A ciklikus eltolásoknál a kipotyogó bitek azonnal, vagy a C biten keresztül visszalépnek a túloldalon. Mindenféle magyarázat helyett lássuk az utasításokat és a működésüket illusztráló ábrákat!
Az RRC (Rotate Right Circular, ciklikus eltolás jobbra):
RRC A |
RRC B |
RRC C |
RRC D |
RRC E |
RRC H |
RRC L |
RRC (HL) |
RRC (IX+d) |
RRC (IY+d) |
Az RR (Rotate Right, eltolás jobbra) utasítás:
RR A |
RR B |
RR C |
RR D |
RR E |
RR H |
RR L |
RR (HL) |
RR (IX+d) |
RR (IY+d) |
Balra mozgatja a biteket az RLC (Rotate Left Circular, ciklikus eltolás balra) utasítás:
RLC A |
RLC B |
RLC C |
RLC D |
RLC E |
RLC H |
RLC L |
RLC (HL) |
RLC (IX+d) |
RLC (IY+d) |
Utolsó a sorban az RL (Rotate Left, eltolás balra) utasítás:
RL A |
RL B |
RL C |
RL D |
RL E |
RL H |
RL L |
RL (HL) |
RL (IX+d) |
RL (IY+d) |
A műveletek az ábrákon látható módon állítják be a C bitet, a Z bit pedig az eltolás után kapott eredménynek megfelelő értéket kap.
Ide tartozik még négy utasítás:
RRCA |
RRA |
RLCA |
RLA |
Ezek az utasítások az akkumulátoron keresztül végzik az eltolást, és nem állítják a Z bitet. Előnyük viszont, hogy kétszer gyorsabbak RRC A, RR A, RLC A, RL A társaiknál.
Ebből az utasításfajtából már csak az RRD (Rotate Right Decimal, decimális eltolás jobbra), és az RLD (Rotate Left Decimal, decimális eltolás balra) utasítások hiányoznak. Ezek tulajdonképpen az ún. binárisan kódolt decimális (BCD) számokkal végeznek műveletet (a BCD számokról néhány mondattal később esik majd szó). Működésük az ábrák alapján világossá válik.
Az utasítások a C bitet nem bántják, a Z pedig az akkumulátorban kapott eredménynek megfelelően áll be.
Blokkműveletek
A blokkműveletekkel megadott számú bájtot mozgathatunk, összehasonlíthatunk a tárban; beolvashatunk, kiírhatunk a portokra.
Blokkmozgatást az LDI (block LoaD Increment address, blokkmásolás címnöveléssel), LDIR (block LoaD Increment address auto Repeat, blokkmásolás címnöveléssel, automatikus ismétléssel), LDD (block LoaD Decrement address, blokkmásolás címcsökkentéssel), LDDR (block LoaD Decrement auto Repeat, blokkmásolással címcsökkentéssel, automatikus ismétléssel) utasításokkal végezhetjük.
Az
LDI |
utasításnál a HL tartalmazza azt a címet ahonnan, a DE pedig azt, ahová akarunk másolni; BC-ben a másolandó bájtok számát adjuk meg. Az utasítás végrehajtása után HL és DE értéke eggyel növekszik, BC eggyel csökken. Lehetőségünk van a helyzet analizálására, ennek megfelelően eldönthetjük, hogy visszaugrunk az LDI-re, vagy sem. Folyamatosan hajtja végre a blokkmásolást az
LDIR |
utasítás. A regisztereket a fentieknek megfelelően kell beállítani. Itt a másolás addig tart, amíg BC nem egyenlő nullával.
Az
LDD |
és az
LDDR |
utasítások ugyanígy működnek, azzal az óriási különbséggel, hogy ezeknél HL és DE csökken.
Az utasítások nem módosítják a C és a Z biteket.
Blokkösszehasonlítást végezhetünk a CPI (block ComPare Increment address, blokkösszehasonlítás címnövel éssel), CPIR (block Compare Increment address, auto-Repeat), CPD (block ComPare Decrement address, blokkösszehasonlítás címcsökkentéssel) és a CPDR (block ComPare Decrement address, auto-Repeat, blokkösszehasonlítás címcsökkentéssel, automatikus ismétléssel) utasításokkal.
A
CPI |
utasításnál az A regiszterbe kell töltenünk azt az értéket, amellyel a HL által címzett memóriarekesz tartalmát akarjuk összehasonlítani. Az utasítás a BC regiszter értékét is csökkenti, szükség esetén ezt is felhasználhatjuk.
A
CPIR |
utasítással egyszerűen kereshetünk meg bizonyos értékeket a memóriában. A-ba a keresendő értéket, HL-be az indulási címet, BC-be pedig az összehasonlítandó bájtok számát töltjük. A művelet során HL egyesével növekszik, BC csökken. Ha a CPIR utasítás az A regiszter értékével megegyezőnek találja a HL által címzett rekesz tartalmát, akkor abbahagyja a keresést. Ekkor HL már a következő bájtra mutat! Ha a keresés során a bájtszámlálóként működő BC tartalma eléri a nullát, akkor szintén befejeződik a végrehajtás.
Ezek után a
CPD |
CPDR |
utasításokról csak annyit kell elárulni, hogy itt értelemszerűen HL csökken.
A blokkösszehasonlító utasítások a Z bitet állítják, a C bithez nem nyúlnak.
Blokkba tudjuk letárolni egy port tartalmát az INI (block INput Increment address, blokkinput címnöveléssel), INIR (block INput Increment address, auto-Repeat, blokkinput címnöveléssel, automatikus ismétléssel), IND (block INput Decrement address, blokkinput címcsökkentéssel) és az INDR (block INput Decrement address, auto-Repeat, blokkinput címcsökkentéssel, automatikus ismétléssel) utasításokkal.
Az
INI |
végrehajtása előtt C regiszterbe a port címét, HL-be a letárolás kezdőcímét kell töltenünk, a B regisztert ciklusszámlálóként használhatjuk. A Z bit egybe billen, ha a B regiszter tartalma nulla lesz.
Az
INIR |
utasításnál a regiszterek szerepe ugyanaz, mint az INI-nél, de itt a CM egyhúzásra végrehajtja a portról olvasást, és a letárolást. Az utasítás végrehajtása addig tart, amíg B el nem éri a nullát. A Z bit itt végig 1.
HL csökkentésén kívül ugyanezt teszik az
IND |
INDR |
utasítások is.
Ez az utasításcsoport a C bitet változatlanul hagyja.
A blokkokkal kapcsolatos kimeneti OUTI (block OUTput Increment address, blokk kivitel címnöveléssel), OTIR (block OuTput Increment address, auto Repeat, blokk kivitel címnöveléssel, automatikus ismétléssel), OUTD (block OUTput Decrement address, blokk kivitel címcsökkentéssel), OTDR (block OuTput Decrement address, auto Repeat, blokk kivitel címcsökkentéssel, automatikus ismétléssel) utasítások már önmagukért beszélnek.
Az
OUTI |
utasításnál C tartalmazza a portcímet, HL a forrás helyét, B a kiírandó bájtok számát. Az utasítás hatására tehát B "darab", a HL által címzett blokkban elhelyezkedő bájt tartalma íródik ki a C regiszterben megadott portra. A Z bit 1 értéket vesz fel, ha B értéke eléri a nullát.
Ugyanezt végzi el egyetlen ciklusban az
OTIR |
utasítás, amelynél a Z bit végig 1.
HL tartalmának csökkentésén kívül ugyanígy működnek az
OUTD |
OTDR |
utasítások.
A blokkos kivitel nem módosítja a C bitet.
És ami kimaradt
Maradt még néhány érdekes, különleges utasítás, melyek nagyrésze nélkülözhetetlen a programozás során. Ezeket most mindenféle külön kategóriába sorolás nélkül nézzük végig. Az
SCF |
(Set Carry Flag, az átvitelbit 1-be állítása) és a
CCF |
(Complement Carry Fiag, az átvitelbit komplemetálása) utasítás. A komplemetálás azt jelenti, hogy a C bitet ellenkezőjére változtatjuk. A C bitet szokás különféle jelzések (pl. hiba történt) adására használni.
A
DAA |
(Decimal Adjust Accumulator, az akkumulátor decimális igazítása) utasítás a BCD formátumban végzett műveletek eredményeinek korrigálásánál tesz jó szolgálatot. A BCD ábrázolásnál a bájt alsó négy bitjén az egyesek, felső négy bitjén a tízesek számát tároljuk. Nézzük meg a 29 BCD reprezentációját!
29 = 0010 1001 (BCD)
Nos, ha két ilyen számot összeadunk, akkor hamis eredmény is adódhat. Legyen az akkumulátorban a BCD-ben ábrázolt 29, a B regiszterben pedig 19 (0001 1001). Ha most a CPU-val végrehajtatjuk az ADD A,B utasítást, akkor 42-t fogunk kapni, amely rossz eredmény. A DAA utasítás azonban korrigálja az eredményt, az akkumulátorban 48 (0100 1000) lesz. A C bit értéke itt több mindentől függ, alkalmas helyen erre visszatérünk. A Z bit természetszerűleg az akkumulátor tartalmának megfelelően áll be.
Ha már az aritmetikánál tartunk, említsük meg a
NEG |
(NEGate accumulator) utasítást, amely előállítja az akkumulátorban lévő érték kettes komplemensét. Ha az utasítás végrehajtása előtt az akkuban 0 volt, akkor C bit is nulla lesz, egyébként egy. A Z bit feladatának megfelelően áll be.
A processzornak néha a futó főprogram végrehajtásán kívül, más fontosabb rutinokat kell végrehajtania. Erre különböző perifériák kérhetik a CPU-t. Ilyenkor a processzor megszakítja a program végrehajtását, és kiszolgálja a megszakítást kérő eszközt. A kiszolgálást követően a CPU folytatja félbehagyott munkáját. Néha azonban nem lenne jó, ha programunkat megszakítaná egy ilyen igény, ezért ezt szoftverből letilthatjuk a
DI |
(Disable Interrupt, megszakítás tiltás), engedélyezhetjük az
EI |
(Enable Interrupt, megszakítás engedélyezés) utasításokkal. (Minden autostartos, 5-ös fejlécű program DI utasítással kezdődik.)
A Z80 háromfajta megszakítási módban képes dolgozni, ezeket az
IM 0 |
IM 1 |
IM 2 |
(Interrupt Mode, megszakítási mód) utasításokkal választhatjuk ki. Számunkra az IM 1 érdekes, mert az EXOS ebben a beállításban fut. Ennek lényege, hogy ha CPU egy megszakítási kérelmet érzékel a megfelelő lábán, akkor egy RST 38h utasítást hajt végre.
A megszakítást kiszolgáló rutinból a
RETI |
(RETurn from Interrupt, visszatérés megszakításból) és a
RETN |
(RETurn from Non maskable interrupt, visszatérés nem maszkolható megszakításból) tudunk kilépni. A két utasítás pontos jelentése számunkra (egyelőre) teljesen közömbös.
A kettes interrupt móddal (IM 2) kapcsolatosak az
LD A,I |
LD I,A |
utasítások, melyeknél I-nek (Interrupt vektor regiszter) a címképzésben van szerepe.
Az akkumulátorba olvashatjuk a memóriafrissítő regiszter tartalmát az
LD A,R |
utasítással. Az R regiszter tartalma olyan gyorsan változik, hogy egyfajta "véletlenszám-generátorként" lehet használni.
Nem csinál semmit a
NOP |
(No OPeration, nincs működés) utasítás. A processzor a semmit 1 mikroszekundum alatt végzi el.
NOP-okat hajt végre a processzor a
HALT |
(HALT, megállás) utasítás hatására. A processzor megszakítás kérésig vagy törlésig várakozik ebben az állapotban.
Ezzel - sokak megelégedésére - kivégeztük az összes utasítást!
Röviden meg kell ismernünk azt az eszközt, amellyel forrásszintű programjainkat megírjuk, lefordítjuk, belőjük. Az ENTERPRISE-ra eddig két ilyen rendszer készült, az egyik az ASMON (készítője Henrik Bendtsen), a másik a DEVPAC (Hisoft termék). Mindkét programnak vannak előnyei és hátrányai. A DEVPAC például nagyon gyenge az editora.
A sorozat leginkább az ASMON-ra támaszkodik. Egyes felhasználóknak az ASMON SIMON néven van meg a két program lényege ugyanaz. A programokkal kapcsolatos verziószámokra egyébként nem (ehet hagyatkozni, mert állítólag Henrik Bendtsen kiadta az ASMON forrásnyelvi listáját is, így aztán különféle verziók születtek ugyanazon a változatszámon. A következő részben ismertetjük az ASMON összes lehetőségét. Munkánkhoz addig is kell valami támpont, így most röviden ismerkedjünk meg az ASMON-nal.
Az ASMON tulajdonképpen három főmodulból áll, ezek a következők:
A gépi kódú program készítésekor elsőként be kell gépelnünk azt az editorba. Ezt az assembly szöveget nevezzük "forrásnyelvinek". Ha valamilyen szupertitkos programot írunk, akkor ügyelnünk kell arra, hogy ne lopja el senki a forráslistát. Tulajdonképpen ez a legértékesebb része munkánknak, erre kell a legjobban vigyáznunk.
Az forrásnyelvi listába ún. assembler direktívákat is kell tennünk. Ezek nem Z80, hanem a fordítást vezérlő utasítások. Sok egyéb mellett megadhatjuk a program fordításának kezdőcímét, feltételes fordítást végezhetünk stb. Munkánkat jelentősen megkönnyítik a címkék, szimbólumok használata, ezekkel sokkal kezelhetőbbé válik a forráslista. A JP utasításokba például nem memóriacímeket kell írnunk, hanem címkeneveket.
Az így megírt assembly nyelvű programot lefordítjuk az assemblerrel. A fordítás során hibaüzeneteket kaphatunk, ezeket a hibákat korrigálnunk kell, mert a fordító egy csökönyös állat, de mindig neki van igaza... A kijavított, hibátlanul fordítható forráslistát gyakran mentsük el!
Sikeres fordítás után előáll a kód, amely remélhetőleg azt csinálja, amit mi akarunk. A tesztelés történhet úgy, hogy egyszerűen ráugrunk a program elejére, és várjuk az eredményt. Az eredmény legtöbbször ugrabugráló képernyő, hörgő hang, érzéketlen billentyűzet stb., majd rendszerindítás formájában jelentkezik. Ettől nem kell megijedni, a gép a hibás programtól soha nem romlik el! Célszerűbb tehát, ha a programot kis rutinonként lépésenként lefuttatjuk, és figyeljük a viselkedését. Ha egy helyen nem azt csinálja, mint amit mi szeretnénk, akkor visszalépünk az editorba. Kijavítjuk, lefordítjuk, elmentjük a forráslistát, ezt követi az újabb nyomkövetés. Így előbb-utóbb elkészül, használhatóvá válik programunk.
Az ASMON kétféle változatban terjedt el: az egyikben autostartos alkalmazói programként, a másikban rendszerbővítőként inicializálódik. Jelen leírás az előbbi 1.3-as változatára vonatkozik, igazán lényeges eltérés nincs a különböző verziók között. A betöltést követően megjelenik az ASMON képernyője, amely három főrészre tagolódik:
A felső, legnagyobb ablakban 16 sorban jeleníthető meg a kód listája, a memóriatartalom (vagy népszerűbb nevén a memóriadump), itt végezhetjük el a memóriatartalom módosítását, ide íródnak a rendszer üzenetei stb.
A két csík által bezárt középső sort parancssornak nevezhetjük. A "Command> " promptnál nyomjuk le az egyes parancsokhoz tartozó billentyűket, és a szükséges további paramétereket is ebben a sorban adjuk meg. A sor vége feletti "PRINTER:" felirat tájékoztat a nyomtató státuszáról, a sor alatti "INPUT:" pedig azt árulja el, hogy hexadecimális, decimális vagy éppen karakteres formában kell megadnunk a paramétereket az illető parancsnak.
A legalsó ablak bal szélén a regiszterek tartalmát látjuk. A legfelső sorban az F regiszter bitjeinek állását vizsgálhatjuk. Ha valamelyik bit értéke 1, akkor jelenik meg a hozzátartozó betű.
A BC, DE, HL, IX, IY, SP regisztertartalmak mellett tíz értékből álló dumpot látunk. Ez a dump úgy képződik, hogy a rendszer az adott regiszter által címzett memóriarekesszel szomszédos négy alsóbb és öt felsőbb című rekesz tartalmát is kijelzi. Ez a lehetőség főleg az indexregiszterekkel, veremkezeléssel kapcsolatos vizsgálatoknál nagyon hasznos.
A legalsó sorban a PC értéke mellett az éppen megcímzett utasítást láthatjuk.
A debugger
A debugger viszonylag sokfajta lehetőséggel rendelkezik: itt végezhetjük a program fordítását, lépésenkénti végrehajtását, futtatását: kezelhetjük a portokat, vájkálhatunk a tárban stb. A fő feladata tehát, hogy segítsen programunkat futtatható formába önteni, a hibákat kibogarászni (nem véletlenül hívják debugger-nek, a "bug" ugyanis bogarat jelent).
Az ASMON összes parancsa a megfelelő billentyű leütésével aktivizálható. A parancsok listáját a [H] (Help, segítség) billentyű lenyomásával kapjuk meg. A parancsok paraméterként számokat és szövegeket is várhatnak. A számokat hexadecimális vagy decimális formában adhatjuk meg, a számrendszerek között a [CTRL]+[F8] billentyűkkel válthatunk. Ez a billentyűkombináció egyébként a szükséges helyeken mindig működik, ugyanúgy, mint az [ESC], amellyel bármilyen helyzetből, bármelyik helyről visszaléphetünk a parancssorba.
Sok opció alapértelmezéssel rendelkezik, így ha az ott szereplő érték számunkra megfelelő, akkor nincs más dolgunk, mint hogy az [ENTER]-t leüssük.
A következőkben az angol gépeknél megjelenő help szerinti sorrendben kerülnek ismertetésre a funkciók. A német gépeken csak annyi az eltérés, hogy néhány parancs más billentyűre indul el. Ezen különbségektől a [H] lenyomásával szerezhetnek tudomást a német gépek tulajdonosai. (Sajnos, szerkesztőségünknek egyetlen német gépe sincsen. A szerk.)
[A] Assemble: a fordítási opcióknak megfelelően elindul az editorban lévő forráslista fordítása.
[B] Breakpoints: két töréspont ("Breakpoint 1:" és "Breakpoint 2:") beállítása. Ha a program futás közben a törésponthoz jut, akkor megáll a végrehajtása, így ismét az ASMON-hoz kerül a főszerep.
[C] Copy memory: memóriatartalom másolása a "Start:"-tól az "End:"-ig a "Destination:" célcímre.
[D] Dump memory: memóriatartalom megjelenítése a "Start:" címtől.
[E] Edit source: belépés az ASMON editorba.
[F] Fill memory: a memória feltöltése a "Start:"-tól az "End:" címig a "Value:" értékkel.
[G] Go: a lefordított program indítása a "Start:" címtől.
[H] Help: a helpüzenetek megjelenítése.
[I] Set funtion key: a "Key number:" sorszámú funkcióbillentyűhöz a "Key string:" idézőjelek között megadott sztringet rendelhetjük.
[J] Swap register: a regiszterkészletek cseréje, tartalmuk kijelzése. Az alsó ablak jobb oldalán az "-ALT-REGS-" felirat jelenik meg.
[K] Read source: a "File name:"-nél megadott nevű forráslista beolvasása az editorba.
[L] Disaasemble: a lefordított kód listázása a "Start:" címtől.
[M] Modify memory: a memóriatartalom módosítása a "Start:" címtől. A számmező és az ASCII mező között az [ALT]+[F8] billentyűkkel ugrálhatunk.
[N] Number cruncher: kiszámolja a "Value:"-nál megadott szám 16-os, 10-es, 2-es számrendszerbeli és ASCII megfelelőjét.
[O] Output port: a "Port:" portra a "Value:" értéket írja.
[P] Printer ON/OFF: bekapcsolt (ON) állapotban a megjelenítést a nyomtatóra irányítja.
[Q] Query port: megjeleníti a "Port:" port tartalmát.
[R] Read BIN fiile: a "Start:"-tól az "End:" címig a "File name:" nevű tárgykód betöltése.
[S] Save BIN file: a memóriában a "Start:"-tól az "End:" címig elhelyezkedő tárgykód elmentése "File name:" néven.
[T] Trace memory: a "Start:" címtől "Steps:" számú utasítás végrehajtásának lépésenkénti végrehajtása, nyomkövetése.
[U] Untrace: ez a furcsára sikeredett funkció elvileg ugyanaz, mint az előző, csak itt nincs kijelzés.
[V] View status: megjelenik az EXOS verziószáma; a CPU-ra lapozott szegmensek száma (Page0...Page3); a szabad szegmensek (Free segment(s)), az ASMON munkaszegmenseinek (Working segment(s)), az üzemképtelen szegmensek (Defective segment(s)) és az összes szegmens (Total RAM segment(s)) száma; a copyright szöveg.
[W] Write source: a forráskód mentése az editorból a "File name:"-nél megadott néven.
[X] eXamine register: a "Registerpair:"-nál megadott regiszterpár (AF, BC stb.) értéke a Value:" lesz.
[Y] Options disarembller: a listázáskor megjelenő memóriacímekhez hozzáadhatjuk a "Memory offset"-nél megadott értéket, a címek kiírását az "Addresses" opcióval tilthatjuk / engedélyezhetjük (a "NO/YES" válaszokat bármelyik billentyű leütésével váltogathatjuk).
[Z] Options assemble :
"Assembly listing" - fordítás közben listázza-e forráslistát, vagy sem;
"List conditions" - fordítás közben listázza-e a feltételeket, vagy sem (a feltételekről később);
"Force Pass 2" - az első menet esetleges hibáitól függetlenül végrehajtódjék-e a második fordítási menet, vagy ne;
"Memory assembly" - memóriába fordítson, vagy ne (ha a "Memory assembly"-re "YES"-t válaszolunk, akkor a "Memory offset"-tel a forráslistában megadott fordítási címtől eltérhetünk);
"Object file name:" - a tárgykód neve. Ha nem adjuk meg a tárgykód nevét, akkor nem tesz fel több kérdést a rendszer, ellenkező esetben még a közvetkezőkre kell válaszolnunk: "EXOS module header" - készítsen-e az ASMON a tárgykód elé EXOS fejlécet, vagy sem; ha "YES"-t válaszolunk, akkor meg kell adnunk a típusát az "EXOS module type:"-nál.
[ [ ] Options printer:
"Output to" - a listázást az editorba (EDITOR), vagy a nyomtata (PRINTER:) irányítja (ha PRINTER:-t választunk, akkor a "Use:"-zal akár fájlba is listázhatunk);
"Return sends" - a listázott sorok végére CR/LF (Carriage Return, kocsivissza; Line Feed, soremelés) karakterek, vagy csak CR kerüljön;
"Lines:" - egy oldalra kerülő sorok száma;
"Chars:" - karakterek száma egy sorban;
"Left margin" - a bal margó mérete karakterben;
"Bottom margin" - az alsó margó mérete karakterben;
"Header:" - a lap tetejére kerülő fejléc szövege.
[\] Options editor: az editorban tárolt forráslista az "Editor buffer start:"-tól az "Editor buffer end:"-ig tart.
[ ] ] Load Module: a "File name:"-nél magadott nevű EXOS rendszerbővítő betöltése.
[@] Symbol table: a szimbólumtábla tartalmának megjelenítése.
[=] Find string: a "Start:" címtől a "Search:"-nál idézőjelek között megadott sztring keresése. Ha idézőjelek nélkül számokat sorolunk fel, akkor a számsorozatot keresi.
[:] EXOS string: parancs küldése az EXOS-nat.
Az editor
Az editorba az [E] leütésével léphetünk. Az editor gyakorlatilag minden olyan dolgot tud, ami fontos. Az editorba lépve az alsó két sorban a funkcióbillentyűkhöz rendelt lehetőségeket láthatjuk.
Az assembler
Az assembler feladata, hogy az editorban megirt forráslistát lefordítsa, futtatható kódot generáljon. A minél kényelmesebb programozói munka érdekében a fordító sokfajta lehetőséget kínál: négyféle számrendszert használhatunk, a szimbólumokkal a fordítási időben műveleteket végezhetünk, támogatja a makrók használatát stb. Vegyük sorba ezeket az adottságokat!
Számrendszerek
Az assembler ugyanazokat a jelöléseket használja, mint amelyekkel a sorozat előző részeiben mi is alkalmaztunk a számok végén egy-egy betűvel jelöljük a számrendszert. Ha nem adunk meg a szám után betát, akkor az assembler az alapértelmezésnek megfelelő számrendszerben (l. .RADIX) értelmezi.
Értékadás másképpen is történhet:
Ha idézőjelek közé teszünk egy karaktert, akkor annak ASCII megfelelőjét fogjuk megkapni, pl. LD B,"A", amely az LD B,041h-val megegyező.
Szimbolikus jelölés is használható. A szimbólum egy, a fordítás idején létező értéket jelképez a fordítónak. Legyen például a "CHAR" szimbólum értéke 041h. Ekkor az LD B,CHAR hatására a fordító az LD B,041h kódot fogja előállítani.
Az ún. címszámláló értékét is megkaphatjuk, ha a "$" jelölést használjuk. A fordítás indításakor meg kell adnunk a fordítás kezdőcímét, erre fog kerülni az első utasítás. A címszámláló értéke a fordítás során az utasítások hosszának megfelelően állandóan növekszik, mindig annak a tárrekesznek a címét tartalmazza, amelyre az illető utasítás kerülni fog. Ez a lehetőség az EXOS-nál különösen hasznos, mert pl. az ún. escape szekvenciák elküldésekor tudnunk kell azok hosszát.
A szimbólumokkal végezhető műveletek
Legyen a két szimbólum A és B, ekkor a következő műveletek végezhetők el:
A+B | összeadás |
A-B | kivonás |
A*B | szorzás |
A/B | osztás |
A MOD B | maradék |
A AND B | ÉS művelet |
A OR B | VAGY művelet |
A XOR B | KIZÁRÓ VAGY művelet |
A SHL B | A bitjei B hellyel balra mozognak |
A SHR B | A bitjei B hellyel jobbra mozognak |
NOT A | a biteket negálja (egyes komplemens) |
-A | kettes komplemens képzése |
LOW A | az alacsony bájt képzése |
HIGH A | a magas bájt képzése |
Használhatók még az
A<B, A>B, A<=B, A=>B, A=B, A< >B
relációk.
Ezek után lássuk a fordítást vezérlő utasításokat, az ún. assembler direktívákat. A direktívákat a sor elejénél legalább egy szóköznyi (célszerűen egy tabulátornyi) hellyel beljebb kell írni; a szimbólum- és a címkeneveket viszont mindig a sor elejére kell tennünk, máskülönben a fordító utasításként próbálja értelmezni.
ORG n - A fordítás az "n" címtől fog kezdődni, minden programot ezzel kell kezdeni, pl. ORG 0C00Ah.
.PHASE n - Ez a direktíva tulajdonképpen egy második címszámlálót indít el az "n" címtől, a fordítás természetesen erre az új címre történik. Párhuzamosan az elsődleges címszámláló értéke is megfelelően növekszik.
.DEPHASE - Ismét az elsődleges címszámlálóé lesz a főszerep.
DEFB n - Az "n" bájtot a címszámláló által meghatározott tárrekeszbe teszi (DEFine Byte, bájt definiálás).
DB n - L. DEFB n.
DEFW n - Az "n" szót a címszámláló által megcímzett helyre teszi (DEFine Word, szó definiálás).
DW n - L. DEFW n.
DEFS n - kódban "n" bájtnyi helyet üresen hagy (DEFine Storage, "tárolóhely" definiálás)
DS n - L. DEFS n.
DEFM "text" - Az idézőjelek között álló szöveget letárolja (DEFine Message, "üzenet" definiálás).
symb EQU value - Egy adott szimbólumot egyenlővé tehetünk egy értékkel, pl. az esc EQU 01Bh után az "esc" szimbólum 27-el lesz egyenértékű (EQUaI, egyenlő).
INCLUDE filename - Az "filename" nevű forráslistát lefordítja, a kódhoz illeszti. Az "inkludolást" nagyobb programok készítésekor szokás használni. Ilyenkor a már jól működő részeket külön fájlokba mentjük, és a szükséges helyeken betöltjük őket. Így az editorban mindig csak az aktuális, fejlesztés alatt álló rutin van. Ez a direktíva akkor is nagyon hasznos, ha a forráslistánk teljes mérete meghaladja az editorba tölthető 46KB-ot. Nagy jelentősége van a makrókönyvtárak aktivizálásánál is, l. alább.
END - A forráslista és ezzel együtt a fordítás végét jelzi.
mname MACRO parameter(s) - A makródefiníció kezdetét jelöli. A makró egy olyan paraméterezhető, csak a fordítás idején létező utasításegyüttes, amely csak akkor kerül be a kódba, ha a nevével hivatkozunk rá. A makró definiálása tehát önmagában még nem eredményez semmiféle kódot. A makró és a szubrutin között az a különbség, hogy a szubrutin a kódban csak egyszer fordul elő, és ezt az egy rutint hívjuk meg távolról; a makró viszont minden esetben lefordításra kerül, ha hivatkozunk rá, így a makróban szereplő utasítások több helyen is szerepelnek majd a kódban. Egy példa a makró definiálásra és használatra:
; először a definíció wrchr |
MACRO @char ld a,070h ld b,@char rst 30h defb 2 endm |
; majd a használat | org 01000h wrchr "A" end |
A példában egy olyan makrót készítünk, amely bemeneti paraméterként a kiírandó karakter ASCII kódját várja, majd ezt a megfelelő EXOS hívással kiteszi az ASMON debugger képernyőre. A tulajdonképpeni programban a makró nevét ("wrchr") a paraméter, azaz egy "A" betű követi, ez fog megjelenni. Ha megvizsgálnánk a generált kódot, akkor azt látnánk, hogy az ugyanaz, mint a makró (mi más is lehetne...?), csupán az "ld b,@char" helyére "ld b,041h" került.
Az ASMON egyik súlyos hiányossága, hogy nem támogatja a feltételes paraméterátadást.
ENDM - A makró végét jelzi, l. az előző példát.
EXOS n - Egy előredefiniált, az operációs rendszer hívását támogató makró. "n" a hívni kívánt funkció számát jelenti. Az előbbi példában az "rst 030h" és a "defb 7" sorok helyett "exos 7"-t is írhattunk volna.
EXITM - A direktívát az alább ismertetendő feltételes fordítást vezérlő utasításokkal együtt arra használhatjuk, hogy fordítás alatt kiugorjunk a makró belsejéből (EXIT Macro, kilépés a makróból).
IF kifejezés ELSE |
utasítások 1 utasítások 2 |
ENDIF - Az IF..ELSE..ENDIF szerkezettel feltételes fordítást végezhetünk. Ha a "kifejezés" igaz, azaz értéke egy, akkor az "utasítások1", egyébként az "utasítások2" lesz lefordítva. Az ELSE ág elhagyható.
IFT - Ua., mint az IF (IF True, ha igaz).
IFF - Az IF-től annyiban különbözik, hogy az "utasítások1" a feltétel hamissága esetén kerül lefordításra (IF False, ha hamis).
IF1 - A fordítás első menetében igaz.
IF2 - A fordítás második menetében igaz.
.SFCOND - A fordítási feltételek listázását kapcsolja be.
.LFCOND - A fordítási feltételek listázását kapcsolja ki.
.TFCOND - A fordítási feltételek listázását kapcsolja be/ki. E három direkt í va a debugger "Options Assemble / List conditions" részét utánozza.
.LIST - E direktíva már a listázás vezérlésére vonatkozik. A direktíva hatására fordításkor bekapcsolódik a listázás.
.XLIST - Kikapcsolja a listázást.
.PRINTX text - A "text" szöve g et beleírja a fordítási listába. Ha a szöveg első karaktere a "%" jel, akkor a jelet követő kifejezés értékét jeleníti meg.
TITLE str - Nyomtatásnál a lap fejlécébe kerül az "str" sztring.
.RADIX n - Az alapértelmezésű számrendszer "n" lesz.
Az ASMON saját kódjában szerepelnek a relokálható állományok fordításához szükséges INIOFS, SETRTP, RESRTP, ASEG, CSEG stb. direktívák is.
Az utasítások és a programok sikeres elkészítéséhez nélkülözhetetlen ASMON megismerése után nekiláthatunk elkészíteni első rutinjainkat. Ezeknek egyenlőre még semmi közük nincs az EXOS-hoz, céljuk inkább az, hogy az utasítások működését és az ASMON kezelését bemutassák.
Az első rutin megváltoztatja a képernyő keretszínét.
; |
1. program org 01000h equ 0bfe0h equ 255 ld a,white ld (border),a ret end |
; fordítási cím ; keretszín címe ; a fehér kódja ; fehér lesz ; fehér lett ; visszatérés ; fordítás vége |
1. lista |
Az ASMON betöltéz után nyomjuk meg az [E]-t, így az editorba jutunk. Gépeljük be a programot, ügyelve a következőkre:
Látható, hogy az assembly program egy-egy sora az alábbi részekre tagolódik:
[szimbólum-/címkenév] utasítás (az operandusokkal) [;megjegyzés]
A két szélső rész azért áll szögletes zárójelek között, mert alkalmazásuk csak esetleges, nincs rájuk mindig szükség.
Nos, a program első sora egy megjegyzést tartalmaz, a sokat mondó "1. program" szöveget. A megjegyzést jelző pontosvessző a sor legelejére került, ilyet is szabad csinálni.
Célszerű, ha minden rutint - legyen az akármilyen parányi - ellátunk a szükséges megjegyzésekkel Az assembly programok önmagukban nehezen olvashatók, ezért fontos a röviden, szabatosan megfogalmazott megjegyzések használata. Könnyen megtörténhet, hogy egy nem dokumentált, hetekkel (esetleg napokkal) ezelőtt saját magunk által készített rutinról csak hosszas fejtörés után tudjuk kideríteni működését. Ne sajnáljuk tehát az időt a megjegyzések írásától, ezzel önmagunkon segítünk!
A második sorban szereplő "ORG 01000h" jelzi az ASMON-nak, hogy a hexadecimális 1000-es címtől kezdve kell majd a kódot elhelyeznie, minden programot ORG-gal kell kezdeni. Az ASMON-ról tudni kell, hogy maga a rendszer a 3. lapon, azaz a 49152-es címtől helyezkedik el, a mi forrásszintű programunk pedig - alapértelmezésben - a 800h címnél kezdődik. Az ORG érték megadásánál tehát előre meg kell becsülnünk a forráslista méretét azért, nehogy a tárgykód felülírja azt. Assembly-s körökben egyébként mindenki az ORG 01000h használja, merthogy az ASMON alapban ezt kínálja.
A harmadik sor elején a "border" szimbólumnév áll, melynek értéke 0BFE0h lesz Erről a címről most csak annyit kell tudni, hagy ha a rendszerszegmens a 2 lapon van, akkor ennek a címnek tartalma határozza meg a keret színét (itt helyezkednek el az EXOS változói).
A fehér szín kódja a 255, ezzel tesszük egyenlővé a "white" szimbólumot. Érdemes ennél a pontnál néhány szót ejteni a programok nyelvezetéről: Sokan és sokat vitatkoznak arról, hogy angol vagy magyar kifejezéseket használjanak-e a programírók munkáikban. Nincs szándékomben eldönteni a vitát, én mindenképpen az angol kifejezéseihez ragaszkodom. A főindok: a számítástechnika nyelve (is) az angol. Igen zavarónak tartom a "magyarul írt" programokat, hiányoznak az ékezetek (így az magyarosnak már aligha nevezhető). Ráadásul a magyar címke- és szimbólumnevek legtöbbször - elnézést a kifejezésért - idétlennek tűnnek, nehéz rájönni hogy jelentésük mit is takar, az angol sokkal kifejezőbb. A sorozat kedvéért kissé eltértem elveimtől, a megjegyzéseket magyarul (élezetek nélkül...) fogom írni, így talán a többség számára jobban érthető. Egyébként döntse el mindenki, hogy melyik nyelvet használja.
Igazából maga a program az ötödik sornál kezdődik: az akkumulátorba töltjük a fehér szín kódját. Ebben a sorban látszik már, hogy milyen jó dolog a szimbólumhasználat. Az "LD A,white" sokkal többen mond, mint az itt vele teljesen ekvivalens "LD A,255". Azonnal kiderül, hogy a fehér színnel fog valami csalafintaság történni. Persze, azért a szimbólumok használatát sem kell túlzásba vinni, olyan helyen kell csak őket használni, ahol ez az áttekinthetőséget, a program olvashatóságát, a hordozhatóságot stb. szolgálja.
A forráslista hatodik sorában töltjük le a fehér szín kódját tartalmazó akkumulátortartalmat a keret színét meghatározó címre. Ennek hatására a keret színe ténylegesen felveszi majd az új színt, tehát ez a sor a rutin lényege.
A hetedik sorban a "RET" hatására (a tényleges kód lefutását követően) az ASMON-hoz kerül majd vissza a főszerep. A fordítás végét az "END" direktíva zárja.
A lista begypelése után az [F8] leütésével jutunk vissza az ASMON parancssorába . Következő feladatunk, hogy lefordítsuk az editorban lévő forrásszintű programot. Most csak a memóriába akarunk fordítani, üssük le a [Z]-t (német gépeknél [ Y]), és az ASMON által feltett kérdések közül csak a "Memory assembly"-re válaszoljunk "YES"-t, a többi opciót hagyjuk változatiónul, ezeknél csak az [Enter]-t nyomjuk le. A fordítási feltételek megadása után az [A] billentyű leütésével indítható a fordítás. Sikeres fordítás után a képernyőn a
Pass 1.
Pass 2.
No error.
sorokat olvashatjuk. A "Pass" sorok az egyes fordítási menetek futását jelzi, a "No error" a fordítás hibamentességét jelzi. Előfordulhat, hogy egyeseknél mindenféle misztikus üzenetek jelentek meg a képernyőn. Ők valamit félregépeltek, ellenőrizzék a forráslistát!
Vizsgáljuk meg hogy valójában mi is történt! A fordítást a memóriába kértük, a 01000h címtőt kezdve. Ennek tehát ott is kell lennie, meggyőződhetünk erről, ha megnézzük a tárgykódot. Nyomjuk meg az [L]-t, írjuk be az 1000-t, és üssük le az [Enter]-t, majd néhány sornyi listázás után az [Esc]-et! Ekkor a következő sorokat látjuk:
Ez a tárgykód visszafordított formában. Itt már nincsenek szimbólumok címkék, megjegyzések csak maga a jó kemény gépi kódú program. Az egyes sorok elején az a címérték látható, ahonnan az adott utasítás kódja letárolásra kerül. A következő, bájtokat jelentő értékek (maximálisan négy) az illető utasítás gépi kódú megfelelőjét jelenti(k). Így az "LD A,FF" gépi kódja "3E FF", a teljes utasítás itt két bájt hosszú. Ezután következik a kódhoz tartozó assembly kód, és végül a tárgykód "fizikai megjelenését" bemutató pontosvessző utáni karakterek.
Az első utasítás tehát az 1000h, címtől kezdve két bájtnyi helyet foglal a tárban, így a kővetkező utasítás az 1002h címre kerül A letöltő utasítás három bájt hosszú, érdemes megfigyelni, hogy a memóriacím (BFE0) ténylegesen alacsonyrész (Low), magasrész (High ), azaz "E0 BF ' formában tárolódik. Az egyetlen bájtnyi helyet elfoglaló "RET" az 1002h+3=1005h címre kerül.
Bár a visszafordított listában már értelemszerűen nem szerepelnek a szimbólumnevek, megjeleníthetjük őket, ha leütjük az [@]-t (népszerű nevén a "kukac"-oz, német gépeken az [Ü]-t.) Így megjelenik a következő sor:
BORDER BFE0 | WHITE 00FF |
Miután kigyönyörködtük magunkat, próbáljuk meg lefuttatni a programot! Ha a rendszer pillanatnyi állapotában indítanánk el, semmit sem látnánk, mert nincs a kettes lapra lapozva a rendszerszegmens (erről a [V]-vel győződhetünk meg). Először tegyük ezt a helyére az [O]leütésével, a B2 [Enter] és az FF [Enter] megadásával! Jöhet a program indítása: [G] 1000 [Enter] megadásával! S lőn: a keret színe fehér lett.
Második programunk (2. lista) már összetettebb. Nem elégszünk meg azzal, hogy megváltozik a keret színe, az összes szín felhasználásával villogtatni akarjuk a keretet.
|
; 2. program |
|
2. lista |
A fordítást az 1000h címtől kezdjük. Értéket adunk a "border", "seg", és a "page2" szimbólumoknak. Ez utóbbi kettőt azért építjük bele a programba, mert így a futtatás előtt nem kell a parancssorból beállítanunk a kettes lap helyes tartalmát; a program magától elvégzi a feladatot. A "NOP" utasításnak a lépésenkénti végrehajtásnál lesz egy kis szerepe (az ASMON egy kisebbfajta hibáját küszöböli ki), a program lényegéhez semmi köze.
A tulajdonképpeni program a forráslista hetedik soránál kezdődik. Az ENTERPRISE memóriaszervezéséről a sorozat legelső részében esett szó. Az ott elmondottakhoz most már csak annyit kell hozzátenni, hogy az egyes lapok tartalmát négy port állapota határozza meg. A nullás laphoz (P0) a 0B0b, az egyeshez (P1) a 0B1h, a ketteshez (P2) a 0B2h, a hármashoz (P3) a 0B3h portám tartozik. Ha tehát a kettes lapon a 255-ös sorszámú rendszerszegmenst akarjuk használni, akkor a 0B2h portra 255-öt kell írnunk. Pontosan ezt teszi a hetedik és a nyolcadik sor.
A kilencedik sorban a B regiszterbe nullát töltünk. A B regisztert - mint tudjuk - a DJNZ utasítás használja. Ha a regisz terbe induláskor a nulla érték kerül, akkor a ciklus 256-szor fog lefutni.
A következő sor elején álló "cycl1" címkét jelent, tehát azt a memóriacímet jelképezi, amelyre a "PUSH BC" utasítás fog kerülni. Az előbbi veremkezelő utasításra azért van szükség, mert egy belső DJNZ ciklust is használunk, és az elrontaná a külső ciklushoz tartozó regisztertartalmat. (A belső ciklust természetesen másképpen - ezerféle módon - is kialakíthattuk vol na, de így legalább látunk egy kis veremkezelést.)
A keret színét mindig a külső ciklushoz tartozó B regiszter tartalma határozza meg. Az "LD A,B" utasítással betöltjük azt az akkumulátorba, majd az "LD (border),A"-val letöltjük a színt.
A következő sorban nyugodt lelkiismerettel elronthatjuk a B regiszter tartalmát az LD B,0"-val, hiszen a külső ciklus számlálóértéke a stack-en van.
A belső ciklusra az "időhúzás" miatt van szükségünk. Ha ezt nem tennénk a programba, akkor az olyan gyorsan lefutna, hogy a villogásnak nem lenne "élvezeti értéke . A "cycl2" címen lévő DJNZ önmagára ugrik vissza, így éri el a lassítást.
Amint véget ért a belső ciklus, azonnal helyreállítjuk" a külső ciklushoz tartozó B regiszter tartalmát a "POP BC"-vel. A külső ciklus végét a "DJNZ cycl1 " jelzi, a programot a "RET" utasítás zárja.
Ha végeztünk a begépeléssel, lépjünk vissza a parancssorba! Nyomjuk le az [A]-t, így - hibátlan lista esetén - végre hajtódik a fordítás. A programot most lépésenkénti végrehajtásnál foguk lefuttatni, nyomjuk le ehhez a [T]-t, majd háromszor az [Enter]-t. A harmadik lenyomásra azért van szükség, hogy elkaphassuk a végrehajtás legelejét is. Itt derül ki a program elejére becsempészett "NOP szerepe: az ASMON a trace funkciónál nem tud azonnal megállni, csak a második utasításnál. Ha tehát ügyesek voltunk (ezt jelzi a képernyő bal alsó sarkában a "PC=1001"), akkor éppen a program elején állunk. Nyomogassuk az [Enter]-t, és figyeljük meg az A, B és az SP regiszterek és a Z flagbit változásait. Ha a belső ciklushoz értünk, .nyomjuk le a [SPACE]-t, így az gyorsabban lefut. A lépésenkénti végrehajtásból az [Esc] leütésével juthatunk ki.
Fontos megemlékezni itt az ASMON egy igen bosszantó jellemzőjéről Ha trace üzemmódban a végrehajtás a program végén álló "RET" utasításra fut, akkor nagy esélyünk van arra, hogy a verem tettjén lévő szó tartalmától függően a teljes rendszer elszáll. Az utasítás hatására ugyanis (mint rendesen) PC-be töltődik ez a szó, és a lépésenkénti végrehajtás innen folytatódik, tehát nem ér véget...
A program lefuttatásához nyomjuk meg a [G]-t, és írjuk be az 1000h-t, végül üssük az [Enter]-t! A belső lassító ciklusnak köszönhetően a keretvillogás szemünk által érzékelhető "lassúsággal" megtörtént A tisztelt Olvasóra bízom annak eldöntését, hogy kilépéskor miért lesz vörös színű (kódja 1) a keret.
Harmadik programunk (3. lista) is a keretvillogtatáshoz kötődik, de itt a keret színét meghatározó értékeket a teljes memória, felülről lefelé történő olvasásával kapjuk meg. A lista első hét sorát már ismerjük.
|
; 3. program |
|
3. lista |
A színek kódját tehát úgy akarjuk megkapni, hogy először kiolvassuk a tár tetején (a 65535-as címen) lévő értéket, majd az ezalattit és így tovább, egészen a tár aljáig (a 0-ás címig) haladva. A program elején álló "LD HL,0" ezért ne tévesszen meg senkit: Mivel a következő utasítás a "DEC HL", így a HL tartalma a legelső felhasználáskor tényleg 65535 lesz Látható, hogy a HL a programban mutatóként szolgál, mindig arra a tárrekeszre mutat, amelyik értékét keretszínnek akarjuk felhasználni. A színkód az "LD A,(HL)" hatására kerül majd az akkumulátorba, a letöltés a szokásos módon történik.
A következő feladat annak eldöntése, hogy HL elérte-e a nullát, mert ebben az esetben abba kell hagyni a program (illetve a ciklus) folytatását. Mivel nincs 16 bites CMP utasítás, ezért valamilyen cselt kell kieszelnünk, ez a következő: Az akkumulátorba nullát töltünk (ezt teszi a "XOR A". művelet, amely az LD A,0-val teljesen azonos, csak rövidebb annál), és "belevagyoljuk" H és L tartalmát. Így ha HL tartalma nullával egyenlő, akkor A tartalma is nulla lesz, egyébként nem. Ha még nem értük el a memória alját, akkor az akkumulátorban az eredmény nem zéró (NZ igaz), így visszaugrunk a ciklus elejére.
A parancssorba lépve fordítsuk le a programot a memóriába! Azt javaslom, hogy lépésenkénti végrehajtásnál mindenki maga vizsgálja meg a program tényleges működését, az akkumulátor, a HL regiszter és a Z flagbit viselkedését Még annyi tanácsod adok, hogy mindenki nyugodtan írja át a nyolcadik sorban álló "LD HL,0"-t mondjuk "LD HL,3"-ra, így senki sem fog egyenesen a gépe mellől nyugdíjba menni.
Megismertük az utasításokat, az ASMON-t, és elkészítettük első, "EXOS-független" rutinjainkat. Itt a Idő arra, hogy igazi Enterpriseos programokat írjunk.
A sorozat ezentúl egy bizonyos, nagyalakú, fehér-fekete, csíkos borítójú könyvre fog épülni. A könyv címét, beszerzési helyét nem adhatom meg, mert reklámnak számítana. Aki azonban az Enterprise gépi kódú programozásával foglapozik, pontosan tudja, melyikre gondolok. A továbbiakban erre a silány fordítású, széteső kötésű műremekre a "könyv" elnevezéssel utalok. A sorozat nagyban támaszkodik a könyvben leírtakra, az érdeklődők a sorozaton keresztül rájönnek az ott leírtak használatára, értelmezésére. (Itt és most azért eláruljuk: az ENTERPRISE EXOS 2.1 Műszaki Leírás a könyv címe...)
Figyelműnket az EXOS-ra irányítjuk. Az EXOS az az operációs rendszer, amely az Enterprise-t magasan kiemeli a tucat házi számítógépek közül. Az EXOS-nak ugyanis megvannak azok a jellemzői, amelyek egy igazi egyfelhasználós - egyfeladatos (single user - single task) operációs rendszert jellemeznek. Csak néhány ezek közül: paraméterezett funkcióhívások, dinamikus memóriahasználat, memóriakezelés, bővíthető funkciók stb. Az olyat gépek, mint a C64, a ZX Spectrum ezeket nem, vagy igen primitív módon tudják csak, lévén, hogy azokban - mindenféle hiedelemmel ellentétben - nincs igazi operációs rendszer, legfeljebb a ROM-ot közvetlenül hívogathatja a programozó. Ehhez pontosan ismernie kell a ROM felépítését, az egyes rutinok kezdőcímét stb. Egy EXOS programozó ezzel szemben ismer néhány funkcióhívást, tisztában van az eszközök és a csatornák fogalmával, így tulajdonképpen mindent (fájlkezelés, hangkeltés, rajzolás, képernyőkezelés stb.) el tud végezni. Nem kell tisztában lennie a ROM szegmenseken lévő, a funkció által hívott rutinok elhelyezkedésével, paraméterezésével stb. (Ez a jól átgondolt operációs rendszer nem a véletlen, hanem a sakkszámítógépeiről híres Intelligent Software remekműve. Köszönjük Mr. David Levy!) Ha valakit mindezek nem vonzanak, akkor Frédi-Béni (értsd: kőkorszaki) módon közvetlenül hívogathatja a EXOS-ROM-ot, az sincs megtiltva.
Azért az EXOS sem fenékig tejfel! Tudnunk kell ugyanis, hogy egy funkció meghívása, és a tényleges ROM kód végrehajtásának megkezdése előtt a gépnek sok mindent el kell még végeznie (állapotmentés, memórialapozás, különféle vizsgálatok stb.), ami rabolja az időt. A ROM-ban futó kód (ezt hívják EXOS-KERNEL-nek, vagy felügyelőprogramnak) ráadásul univerzálisra van megírva, mindenre fel van készítve, így a gép sok olyan dolgot is ellenőriz, amit az adott feladatnál nem mindig kellene, és ez is csak időt vesz igénybe. Persze, ennek inkább örülnünk kell, hiszen így programjaink mindig biztosan futnak, mi több, az EXOS 3.0-n is használhatóak lesznek. (Rajtunk nem múlik.) Az azonban biztos, hogy például igazán gyors képernyőkezelést kizárólag a közvetlen video-RAM-ba írással lehet végezni. (Az EXOS ezt is támogatja.)
Az EXOS ezenkívül rendelkezik néhány speciális, nagyon ügyes adottsággal is. Ilyen például az, hogy az egyes eszközök közötti kommunikáció ún. csatornákon keresztül folyik. Ez rendkívüli rugalmasságot tesz lehetővé. További unikum az ún. rendszerbővítők és a perifériakezelők használata, továbbá az új rendszerváltozók létrehozása. Ezeket még komolyabb operációs rendszerek is megirigyelhetnék.
Előreszaladva még azt említeném meg, hogy az EXOS alapelvéhez igazították a ROM Basic-et is. Ott szintén funkcióhívások sorozatának végrehajtásával jár egy-egy Basic parancs vagy utasítás végrehajtása. Aki tehát megérti az EXOS programozását, a ROM Basic-ét is érti.
Perifériakezelők, eszközök, csatornák
Amikor bekapcsolás után a rendszer éledni kezd, az EXOS nyilvántartásba veszi a ROM-ban tárolt összes perifériakezelőt. Egy perifériakezelő a valóságban egy rutinhalmazt jelent, olyan rutinokkal, amelyek alkalmasak a perifériakezelőhöz tartozó eszköz (pl. lemezmeghajtó, nyomtató stb.) egységes kezeléséhez. Az egységes kezelés azt jelenti, hogy például minden eszközre lehet csatornát megnyitni, lezárni; az eszközhöz lehet karakter küldeni stb. A perifériakezelők tehát egységes felületet mutatnak a különböző eszközök felé. Fontos hangsúlyozni , hogy a perifériakezelők nem végeznek fizikai vezérlést. Ezért van szükség például az EXDOS kártyára: hiába írnánk a lemezegységhez perifériakezelőt, az a fizikai vezérlést végző EXDOS (kontroller) kártya nélkül nem tudná a "pucér" lemezegységet vezérelni.
A csatorna nem más, mint az az út (kanális?), amelyen az egyik eszköztől a másik felé haladnak az adatok. Az alkalmazói program szintén a csatornába teszi, vagy onnan pecázza ki az adatokat. Az EXOS-t ezért szokás csatorna-alapú operációs rendszemek hívni. Később majd belátjuk, hogy a "csatorna-szemlélet" milyen rugalmasságot biztosít.
A beépített eszközök
Az eszközök mindegyike névvel rendelkezik. A "bedrótozott" eszközök a következők:
Az eszközök saját változókkal is rendelkeznek. Ezeket az eszköz használata előtt (pl. csatornamegnyitás) a kívánt értékre kell állítani. (A könyv az egyes eszközöket, használatukat külön részekben ismerteti.)
Példa a csatornahasználatra
Tudjuk, hogy az adott eszközzel a csatornán keresztül kommunikálunk. Ehhez először meg kell nyitnunk a csatornát. A csatorna megnyitása az EXOS 1 és az EXOS 2 funkció feladata. A két funkció annyiban különbőzik egymástói, hogy az EXOS 1-nél fájlnév is megadható. (A könyv a funkciókat az I/11-nél írja le.) A csatorna megnyitásához a funkciónak paraméterként át kell adnunk azt a csatornaszámot, amelyen a csatornára hivatkozunk majd; továbbá kell egy mutató az eszköz nevére. Lássunk példát egy 10*10-es, hardver-szöveges képernyő megnyitására!
;4. program |
||
v_mode v_color v_xsize v_ysize mode color xsize ysize v_chm |
equ 22 |
;képtípus ;képszín ;X méret ;Y méret ;szöveges ;kétszínű ;szélesség ;magasság ;csatornaszám |
;Basic: SET VIDEO MODE 0 | ||
ld b,1 ld c,v_mode ld d,mode exos 16 |
;beállítás ;változó ;érték |
|
;Basic: SET VIDEO COLOR 0 | ||
ld b,1 ld c,v_color ld d,color exos 16 |
||
;Basic: SET VIDEO X 10 | ||
ld b,1 ld c,v_xsize ld d,xsize exos 16 |
||
;Basic: SET VIDEO Y 10 | ||
ld b,1 ld c,v_ysize ld d,ysize exos 16 |
||
;Basic: OPEN £5:"video:" | ||
ld a,v_chn ld de,devnam exos 1 ret |
;csatornaszám A-ba ;mutató az eszközre ;funkció hívása |
|
devnam | defb 6 defm "VIDEO:" end |
;a "VIDEO:" hossza "VIDEO:" |
4. lista
A lista hosszabb, mint amire egyesek számítottak. A képernyőcsatorna megnyitása előtt ugyanis inicializálnunk kell négy paramétert: a képernyő típusát (V_MODE), a színét (V_COLOR), szélességét (V_XSIZE), magasságát (V_YSIZE). Ezeket az EXOS 16 funkcióval kell beállítani. A funkciónál B=1 jelzi, hogy rendszerváltozót akarunk beállítani (B=0 olvasás - ASK; B=1 beállatás - SET; B=2 billentés - TOGGLE). C regiszterbe töltjük a megcélzott rendszerváltozó sorszámát, D-be pedig az írandó értéket. Bizonyára feltűnt, hogy minden funkcióhívás előtt újra és újra feltöltjük a regisztereket. Erre azért van szükség, mert a funkcióhívások kényszerűen elrontják az AF, BC, DE regiszterek tartalmát, merthogy az EXOS ezekben adja vissza az eredményeket. Az összes többi regiszter, a teljes háttérregiszter-készlettel együtt, valamint a belapozott szegmensek kiosztása változatlan marad.
A program elején tehát a négy változót állítjuk be. Ezt követi a csatorna megnyitása: A-ba a csatorna számát, DE-be az eszköznév hosszbájtjának címét töltjük, amelyet a név követ. Ebből kiderül, hogy nem csak az eszköznevet, hanem a név elé "írt" névhosszat is meg kell adni. További követelmény, hogy az eszköz neve után kettőspontot kell tenni. (Így lesz a "VIDEO:" hossza 6 bájt.) Abban az esetben, ha fájlnevet adunk meg, természetesen a teljes hosszat kell megadni (pl. a "TAPE:PRG_NAME.EXT" hossza 17 bájt). Az EXOS keményen meghatározza, hogy egy ilyen névben milyen karakterek adhatók meg, ezeket a könyv az I/11.1-nél ismerteti.
Fordítsuk le a memóriába a programot ([Z] ... Memory assembly YES), majd a [T] 1000-el kövessük végig a működését! Figyeljük meg az egyes EXOS n hívások végrehajtása után az AF, BC, DE regiszter tartalmakat, azokat az EXOS tényleg elrontja. Egyelőre az A regiszter tartalma érdekes számunkra. Az EXOS ugyanis olyan jól nevelt operációs rendszer, amelyik minden hívás után megmondja, hogy a hívás sikeres volt-e vagy sem. Ha minden rendben, akkor 0-t ad vissza az akkumulátorban. Ha az akkumulátor tartalma nullától eltérő, akkor valami hiba történt, az érték egy ún. hibakódot jelent. (A hibakódokat a könyv a II/3-nál sorolja fel.) Az EXOS azonban még magán is túltesz: képes ugyanis arra, hogy egy újabb funkcióhívással (EXOS 28) angolul szöveges magyarázatot adjon a hibakód alapján! Egy komolyabb alkalmazói programnak tehát nincs különösebb dolga, csupán a szükséges helyeken meghívni a hibakód értelmező funkciót, és a funkció által a memóriába helyezett szöveget kiírja a képernyője.
Térjünk vissza programunkhoz! A RET utasításnál nyomjuk le az [ESC]- et (ha nem akarunk elszállni...). Semmi látványos nem történt, viszont van egy 10*10-es szöveges képernyőnk, amivel az 5-ös számot viselő csatornán kommunikálhatunk. Az érdekesség kedvéért indítsuk el újra a programot a [G] 1000-rel! A végrehajtás után az A regiszter tartalma F9h. Mi történhetett? Tudjuk, hogy az utolsó utasítás a RET volt, ami biztosan nem módosítja az akkumulátor tartalmát. Ebből egyenesen következik, hogy a nullától eltérő értéket az utolsó EXOS hívás adta viasza.
Ha fellapozzuk a könyvet, láthatjuk, hogy az F9h hibakód a ".CHANX" hibát jelenti, amely tanúsága szerint a csatorna már létezik Egy csatornát tehát nem lehet kétszer ugyanazon a számon megnyitni. A megnyithatóság attól is függ, hogy milyen eszközről van szó. A két véglet: a billentyűzethez csak egy csatornát lehet megnyitni (hiába adunk meg másik csatornaszámot), a képernyőhöz pedig annyit (eltérő számokon), amennyi a szűk 64KB-as video-memóriában elfér.
A következőkben jelenítsük meg a képernyőt, és írjunk rá valamilyen szöveget. Ezek nem jelentenek semmi egyebet, mint két újabb funkcióhívást. A 5 lista felső részét a RET utasítás elé, a msg..., maglen... sorokat pedig a RET után kell begépelni.
;megjelenítés | ||
ld a,v_chn ld b,1 ld e,1 ld c,1 ld d,10 exos 11 |
;DISP alfunkció ;AT ;FROM TO |
|
;kiírás | ||
msg msglen |
ld a,v_chn ld bc,msglen ld de,msg exos 8 defm "This is a message." equ &-msg |
5. lista
A videolap megjelenítés speciális funkciónak számít, az EXOS 11-el lehet elvégezni. A-ba (szokás szerint!) a csatornaszámot, B-be a funkcióra vonatkozó alfunkciószámot (DISP-nél 1-et), C-be, DEbe egyéb paramétereket kell betölteni. Videolap megjelenítésnél E-be kell azt a képernyősor értéket, ahol a lap kijelzése kezdődjön; C-be az első, D-be az utolsó kijelezni kívánt sor számát kell megadni. (Ha Basic-kel szeretném illusztrálni, így írnám fel:
DISPLAY £A:AT E FROM C TO D
ahol az A, C, D, E betűk a megfelelő regisztereket jelentik.)
A megjelenítendő szöveget blokként kell felfogni, az EXOS 8 képes blokkot kiírni. A-ba a csatornaszámot (a videolap azonosítót), BC-be a blokk hosszát, DE-be a blokk kezdetének címét kell megadni.
Még mielőtt lefordítanánk a programot, közvetlenül az írandó értékek definiálása után (;Basic: SET VIDEO MODE 0 elé) gépeljük be a 6. listán látható néhány sort. Mivel a programot előzőleg már futtattuk, az 5-ős csatorna létezik Hogy a program szép legyen, rögtön a kezdetén zárjuk le az 5-ös csatornát. Erre az EXOS 3 ad lehetőséget: A-ba a lezárandó csatorna számát kell tölteni.
;lezárás | |
ld a,v_chn exos 3 |
6. lista
Programunk most így néz ki: (4_prg.asm)
Ennél a pontnál lehet egy kicsit filozofálni: Semmi baj sem történt volna, ha a 6. listán szereplő 3 sort nem szúrjuk be a programba. Igaz ugyan, hogy az EXOS 1 F9h hibakódot adott volna vissza, de mi ezzel nem törődünk. Ettől függetlenül a megjelenítési és kiírási rész normálisan lefutott volna. A mi helyzetünkben a beszúrt 3 sor helyesen működött, az EXOS 3 nem adott viasza hibát, hiszen a csatorna már nyitva volt, le tudta zárni.
Ha azonban rögtön a legelején (amikor még egyáltalán nem volt nyitva az 5-ös csatorna, mindjárt a 4. listánál) ezt a három sort is begépeltük volna, akkor az EXOS 3 FBh hibát (.ICHAN) válaszolt volna, mert olyan csatornát akartunk lezárni, amelyik még nem is volt megnyitva. A példa szándékosas erőltetett, feje tetejére állított. A tanulság: ha megnyitunk egy csatornát a használat befejeztével zárjuk le. (Sok program betöltője nem végzi el ezt. Erről árulkodik a lemezegység előlapján világító LED.)
Fordítsuk le a programot, és futtassuk! Csak egy fél másodpercre látjuk felvillanni a videolapot, mert az ASMON visszatéréskor ráteszi a saját főképernyőjét. Megoldást jelentene, ha programból várakoznánk egy billentyű leütéséig, és csak azután hajtódna végre a RET. Azoknak, akik egy kicsit sejtik már az EXOS-t, és birtokukban van a könyv, figyelmükbe ajánlom a KEYBOARD: eszközt, és a karakterolvasás funkciót...
A blokkírás és a szekvenciák
A múltkori példában felhasználtuk az EXOS 8 funkciót, de nem esett szó ennek teljesebb használatáról. Ismétlésképpen annyit kell felidézni a funkcióról, hogy segítségével egy csatornára (A) adott számú bájtot (BC) tudunk a tár adott helyéről (DE) küldeni. Ez egy meglehetősen általános ismertetése az EXOS 8-nak, nézzünk három példát, mire használható még:
Nos, az escape-szekvencia egyfajta különleges vezérlőkódok sorozatát jelenti. Nem csak a VIDEO: eszköznél értelmezett, de jelentőségük talán itt a legnagyobb. Az ASCII 27 (ez az escape kódja) karakterrel kezdődő szekvenciák különféle parancsokat jelentenek az eszköznek. Szekvenciákkal tudjuk például a kurzort ki- és bekapcsolni, a képernyő scrollozását tilthatjuk, engedélyezhetjük; olvasni tudjuk a kurzor pozíciót, a színeket tudjuk beállítani stb. (A képkezeléssel kapcsolatos szekvenciákat a könyv a III. 4.2-nél sorolja fel.)
A példaprogram
A blokkarás testvére az EXOS 7 - karakter írása funkció. Ennél is A-ba annak a csatornának a számát kell töltenünk, amelyikbe "pottyantani" akarunk, a B-be pedig az elküldendő karakter ASCII kódját tesszük. A képernyőkezeléssel kapcsolatos első példa ezt és a szekvenciák használatát is bemutatja.
(A könnyebb magyarázhatóság kedvéért a felsorszámoztam a sorokat, termé szetesen a sorszámokat nem kell bepötyögni.)
A program a következőt teszi: megnyit egy 40*27-es hardver-szöveges képernyőt, majd ennek egészén ciklikusan megjeleníti az ASCII 33-127 karaktertartományt. Az ilyenfajta program a kezdők első munkái közé tartozik. Milyen feladatokat kell megoldanunk a rutinban?
Az 1. és 3. pont sima ügy. A megjelenítő rutint sokféleképpen ki lehet alakítani, én a következőket teszem:
Ez az első olyan program, amelyben makródefiníciót (3-8) használunk. Ügyeljünk arra, hogy a " @val" paraméter előtt ne álljon szókőz, mert így az ASMON nem hajlandó átvenni!
A 10-11 sorokban a képernyőméreteket adjuk meg, majd a 14-22ben beállatjuk a szükséges változókat, és megnyitjuk 10-es számon a videocsatornát. A 25-28-as sorokban a 76-78 részen deklarált 3 escape-szekvenciát küldjük el a csatornába: 76-kikapcsolja a kurzort, 77-kikapcsolja a scrollozást (set scroll off), végül 78-megadja a képernyő színeit (set palette c0,c1,c2,c3,c4,c5,c6,c7). A 31-36 részben a már ismerős "DISP"-speciális funkciót hívjuk.
És most lássuk a lényeget! 39-66 végzi el a ciklikusan a karaktermegjelenítést. 39-nél az akkumulátorba töltjük a legelsőnek kiírandó karakter kódját (ASCII 33, ami maga a felkiáltójel!). A karakterkódot egészen 128-ig növeljük majd. Ha még nem érte ezt el, akkor mindig visszaugrunk cycl_ 0-ra (40), ellenkező esetben pedig lezárjuk a csatornát, és jöhet a RET.
40-nél a veremre tesszük a kódot azért, mert A-t az EXOS hívásoknál kell majd használnunk. A 41-43-ban kiírjuk a csatornába az ASCII 30-at; ennek hatására a képernyő letörlése nélkül a bal felső sarokba (HOME) ugrik a kurzor.
A 44-es sorban a D regiszterbe töltjük a képernyő függőleges méretét (az oszlopok magassága), így D a külső ciklus (cycl_1) számlálója lesz.
Ezután (48) E-be a vízszintes méretet (a sorok hossza) töltjük, E a belső ciklus lefutásainak számát (cycl_2) fogja vezérelni. A karakterek kirakását úgy végezzük, hogy E darabot írunk ki D darab sorban.
A cycl_2 (52) kezdeténél a verem tetején van a megjelenítendő karakter kódja. Ebben a megoldásban kihasználjuk, hogy A az AF-ben és B a BC-ben a magasabb (high) bájtok. Az 52 után tehát B-ben lesz a karakterkód. Ezután gyorsan újra elmentjük a karakterkódot (53), B tartalma természetesen nem romlik el a PUSH után! Mivel EXOS hívás következik, így (54) elmentjük DE tartalmát. 55-56-ban megjelenik a karakter. Levesszük a veremről DE-t (57), majd a belső ciklus számlálóját csökkentjük (58), és ha az nem érte el még a nullát (azaz még nem futott le annyiszor, ahány karakter hosszt a sor), akkor visszaugrik (59) cycl_2-re. Ezután lezajlik ugyanaz, amiről ebben a bekezdésben írtam.
Ha az E tartalma nullára csökkent, az azt jelenti, hogy egy sort végigírt a rutin. Egy (újabb) sor készen van, tehát csökkenteni kell a külső ciklus (sorok) számlálóját eggyel (60). Ha D értéke nem nulla (62), akkor még nincs teleírva a képernyő, folytatni kell cycl_1-et (l. a megelőző két bekezdést). Tehát ez a ciklus gondoskodik a szükséges számú (y_size) sor megjelenítéséről.
Amikor a képernyő megtelik az adott kódú karakterrel, akkor le vesszük a veremről a kódot (63), majd a következő kitételhez növeljük értékét (64). Megvizsgáljuk, hogy elérte-e már a 128-at (65). Ha nem, akkor a cycl_0-ra ugrunk vissza (66), és megjelenítjük az újabb karaktereket.
A 127-es kódú karakter megjelenítése után A tartalma 128 lesz, véget érnek a ciklusok. Nincs más hátra, mint lezárni a videocsatornát (69-70), majd visszatérni.
Fordítsuk le és indítsuk el a programot, de az indítás után ne ijed jen meg senki! Nem a gép órajele esett vissza 5 Hz-re, a program az EXOS hívások miatt ilyen lassú. Íme, egy ékes bizonyíték arra, hogy gyors képernyőkezelést csak a közvetlen videomemória-írással érhetünk el.
Az első grafika
A 6. program jelenti első grafikus próbálkozásunkat. Az előző példára hivatkozva szeretném felhívni a figyelmet arra, hogy ott igazi, hardver-szöveges üzemmódban ("TEXT 40"), karakteres képernyőn jelentek meg a karakterek. Ennek kezelése rendkívül egyszerű, hiszen a videomemória egy bájtja egyértelműen meghatároz egy karakter. Sajnos nincs ez így a szoftver-szöveges képernyőn (TEXT 80). Ennek az a lényege, hogy ebben a módban egy EXOS rutinocska jeleníti meg grafikus képernyőn a karaktereket (egy karakter egy 8*9-es pontmátrixban jelenik meg). Szöveges képernyőnek azért nevezhetjük, mert editorcsatornát rendelhetünk hozzá. Bizonyára mindenki megfigyelte már, hogy 80 karakteres képernyőre szinte nincs is alkalmazás (kivéve az IS-DOS-t). Ennek pontosan a képernyőtípus sajátossága az oka: ha szöveges képernyőnek szeretnénk használni, akkor nem tudjuk gyorsan meghajtani, mert úgy kellene "megprogramozni", mintha grafikus képernyő lenne. Ezért aztán a programozók vagy a hardver-szöveges, vagy pedig igazi grafikus képernyőt választanak.
A listán olyan programot láthatunk, amely megnyit egy nagyfelbontású, kétszínű grafikus videolapot, amelyre felrajzol egy kifestett kört, majd ezt a kört 200 ponttal balra scrollozza.
Néhány dolgot el kell árulnom, az Asmon hibájából: a programot csak úgy lehet használni, ha 5-ös fejlécű programmá fordítjuk, és új alkalmazói programként futtatjuk.
A scroll program működése
A program 13-15-ös sora amiatt kell, mert NAP (New Application Program) fájlt készítünk. Az EXOS ezt a programtípust csak akkor tudja elindítani, ha a megszakítástiltás és engedélyezés között a P0-n kijelöljük a stack-et. Ez egy hevenyészett eljárás, a specifikáció egyebeket is előír. (Kitérő: Miért is kell valójában 5 fejlécű programmá fordítani? Nos, az Asmon olyan mohó, hogy induláskor az összes szabad szegmensre ráteszi a kezét. Így hiába próbálunk videolapot nyitni, memóriahiány miatt nem lehetséges.)
A 17-es sorban a 22. Exos változóba írt értékkel határozzuk meg a grafikus lapot. A 26-29-nél álló rész küldi el a 117-122 tartomány szekvenciáit. (A videoszekvenciákról a könyv 148-159-es oldalain olvashatunk.) Ezek rajzolják meg, és festik ki a kört. A program első 63 sorához már nem kell további magyarázat.
A 64-es sorban egy viszonylag összetett kifejezés áll, amely a videolap bájtban mért nagyságát adja meg. Ennek felhasználásával akarjuk a lap Z80-as végcímét kiszámolni (Kezdőcím + lapméret - 1 = végcím. Ki gondolta volna?)
Az x_size változót azért kell kettővel szorozni, mert a magasfelbontású grafikus lapok szélessége kétszer annyi bájt, mint amennyit a 24. változóba írunk. Gondoljunk csak arra, amikor szoftver-szöveges (Text 80) típusú lapot definiálunk: Basic-ben a 80 karakteres szélesség eléréséhez SET VIDEO X 40-et írunk, a valóságban pedig a nyolcvan karakter miatt nyolcvan bájt széles lesz az.
Az Exos csak úgy tudja használni az LPT-t (a demo gyártók örömére), hogy abban egy sor 9 bit magas. Mivel grafikus üzemmódról van szó - azaz a 9 bit magasságot effektíven kihasználjuk - 9-cel kell szorozni a video_y-t. A -1 azért kell, mert a lapról egy bájtot már tudunk (a kezdőcímet), ennek "nagyságát" (1 bájt) már nem kell hozzáadnunk. Összeadás után HL-ben előáll a lap végcíme (65).
A lap mérete 20412 bájt, azaz legrosszabb esetben 3 szegmensen helyezkedik el. A P1-P3-as tartományba helyezzük el a szegmenseket. Esőszőr (67-78) elmentjük, majd beállítjuk a lapkiosztást. (Végül is különösebb értelme nincsen, hiszen új alkalmazói programról van szó, és úgyis úgy kell "kirobbantani" magunkat a végén. De azért szokjuk meg a rendet!)
B-ben adjuk meg azt (80), hogy 200 pixellel akarjuk a kört balra mozgatni. De hogyan is kell ezt megoldanunk?
A sokfajta lehetséges megoldás közül ez a program a lap végénél kezdi a ténykedését. Sorban - jobbról balra - végigmegy egy pixelsoron. A pixelsorban éppen megcímzett bájt bitjeit balra mozgatja, az üressé váló 0. bit-be (a legszélső a bájt jobboldalán) pedig beteszi az előző bájt kiléptetett 7. bitjét. Ha egy pixelsor legelejéhez érkezett, akkor a legutolsó bájt kilépő 7. bitjét a pixelsor végén álló bájt 0. bitjébe írja.
A legkülső ciklus (cycl_0) a kétszáz lefutást irányítja. Mivel B a ciklusszámlálónk, HL-be pedig a nagyon fontos lapvégcím van, ezért ezeket az értékeket elmentjük (81-82), így nyugodtan "elhasználhatjuk" őket.
A középső ciklus (cycl_1) számlálója szintén a B regiszter lesz. Ez a ciklus megy végig az összes pixelsoron, ezért B kezdeti értéke az összes sorok száma (84), a középső ciklus ezután kezdődik.
Töröljük a C bitet (85), ne kerüljön szemét az adott pixelsor végén álló bájt 0. bitjébe. (Nem történne nagy katasztrófa nélküle, hiszen a bitet a végén úgyis felülírjuk.)
C lesz a belső ciklus (cycl_2) számlálója, kezdeti értéke az adott pixelsor hossza bájtokban mérve (86). A ciklus elején (87) a HL által címzett bájt bitjeit balra mozgatjuk. A mozgatás után C bitben a kilépő 7. bit lesz, a bájt 0. bitjébe pedig C bit elázd értéke lesz. Csökken HL értéke (88), az az előbbi bájt előtti bájtra mutat. Csökken a ciklusszámláló is (89). Ha még nem ért a pixelsor elejéig, akkor folytatódik a belső ciklus (90).
Ha a pixelsor elejére értünk, megnézzük, hogy a kilépett bit értéke egyenlő-e 1-gyel? Ha igen, be kell még állítanunk a pixelsor végén álló bájt 0. bitjét 1-be (91).
Mindezt egy külön kis rutin végzi, de nézzük csak, hogy ekkor hova mutat HL? Nem nehéz kitalálni, hogy az már az előző pixelsor feletti másik sor legvégére mutat - így lép ki a belső ciklusból -, nekünk viszont az ez alattira lenne szükségünk. Mi sem egyszerűbb! DE-be megadjuk a sor hosszát bájtokban (95), ezt HL-hez adjuk (96), és rendben vagyunk. Beállítjuk a 0. bitet 1-be (97), a következő kivonáshoz C bitet nullázzuk (98), és HL értékének visszanyeréséhez kivonjuk belőle az előbb hozzáadott értéket (99), és visszalépünk a középső ciklusba (100). Ezután a program megnézi, hogy végigmentünk-e az összes pixelsoron (92), azaz a teljes képernyőn. Ha nem, újra lefut a pixelsorokat kezelő rész.
Ha a képernyő összes bitjét megmozgattuk, akkor jöhet a következd mozgatás, ismét le kell futtatni a scroll rutint. Visszaállítjuk HL (102) és a ciklusszámláló B értékét (103), kezdődhet az egész elölről.
Ebben a részben nem mutatok be újabb programot, viszont megpróbálom tisztázni a különféle grafikus képernyőket felépítő bájtok szervezését, használatukat.
A "grafikus" bájtokról
A kétszínű üzemmód
Az előző példában magasfelbontású, kétszínű grafikát használtunk. Ennek a típusnak a kezelése a legegyszerűbb, hiszen a képernyőt felépítő összes bájt bitjei egyértelműen meghatároznak egy-egy képernyőpontot. (Két "szint", két értéket különböztetünk meg. Ehhez egyetlen bit elegendő, így tehát egy bájt 8 pixelt tartalmaz.) Az első pixel a b7, a második a b6 stb.
A színek miatt más a helyzet a többi képernyőtípusnál. Elöljáróban szeretném felhívni a figyelmet arra, hogy a színkezelésben nincs különbség a magas és alacsonyfelbontást lapok között!
A négyszínű üzemmód
Négyféle értéket két biten tudunk ábrázolni. Ez a négyszínű üzemmódnál annyit jelent, hogy egy képernyőbájton 4 pontot tudunk ábrázolni (4 * 2 bit = 8 bit). Az adott pixel színét a hozzátartozó két bit határozza meg, nézzük ennek a szervezését (1. ábra) egy teljes bájtban:
A grafikán ténylegesen megjelend pontokat az 1,2,3,4 számok jelölik. Az egyes pixelek színeinek vezérléséhez a következő bitek vannak rendelve
pixel bit bit 1. b7 b3 2. b6 b2 3. b5 b1 4. b4 b0Mit mutat a két táblázat? Az elsőn azt a négy pontot láthatjuk, amelyek grafikusan fognak megjelenni. Eszerint a legelső pixel a 7. és a 6. bitek helyén jelenik meg. Azt viszont, hogy az itt megjelenő pixelnek milyen színe lesz, a 7. és 3. bitek kombinációja dönti el. Elképzelhetjük úgy a dolgot, mintha a pixel mindig "bekapcsolt" lenne, és a színe b7-től és b3-tól függene.
Már csak az a kérdés, hogy miképpen határozza meg a két bit kombinációja a tintaszínt:
ink bit bit 1. 0 0 2. 1 0 3. 0 1 4. 1 1Ez természetesen az összes "színmeghatározó" bitre együttesen érvényes.
Biztos ami biztos, nézzünk egy példát! Azt akarjuk elérni, hogy a harmadik pixel (b3, b2 helye) vegye fel az INK 2 színt, a negyedik pedig (b1, b0 helye) az INK 1-et. A harmadik pixel színét b5 és b1 írja le, az INK 2-höz b5=0, b1=1 kell.A negyedik pixel színét b4, b0 adja, ezekhez az INK 1: b4=1, b0=0. A négy pixel tartalmazó bájt ezek után így néz ki:
Ha tehát egy "képernyőbájtba" írjuk ezt az értéket (16), akkor a példának megfelelő eredményt kapunk.
Ha a kétszínű üzemmódhoz készült scroll-rutint ráeresztjük egy négyszínűre, igencsak csalódottak leszünk az eredmény láttán.
A tizenhatszínű üzemmód
Egy pixel 16 féle színű lehet. A 16 bináris ábrázolásához 4 bitre van szükségünk, így egy bájton 2 pixel jelenhet meg. Az első pont a b7-b4, a második a b3-b0 bitek helyén jelenik meg. A bájthoz tartozó két pixel szervezése tehát:
A két pixel színvezérlését a következő bitek végzik:
pixel bit bit bit bit 1. b7 b5 b3 b1 2. b6 b4 b2 b0A színleíró bitek viselkedése első pillantásra zavaros, de a logikájukra a táblázat alapján mindenki rájön (7. ábra):
INK bit bit bit bit 0. 0 0 0 0 1. 1 0 0 0 2. 0 0 1 0 3. 1 0 1 0 4. 0 1 0 0 5. 1 1 0 0 6. 0 1 1 0 7. 1 1 1 0 8. 0 0 0 1 9. 1 0 0 1 10. 0 0 1 1 11. 1 0 1 1 12. 0 1 0 1 13. 1 1 0 1 14. 0 1 1 1 15. 1 1 1 1Nézzünk ismét egy példát! Legyen a 2. pixel INK 13 színű. A pixel tehát a b3-b0 bitek "felett" jelenne meg. Az INK 13-at b6=1, b4=1, b2=0, b0=1 bitek adják, összegük 81. A bájt képe:
A kétszázötvenhatszínű üzemmód
Ez a típus olyan színes, mint amennyire rossz a felbontása. A színvezérlés roppant egyszerű, hiszen 256 szín megkülönböztetése 8 bitet, azaz teljes bájtot igényel. A bájtba írt érték maga a megjelenő szín kódja. Egy bájton tehát egy pixel ábrázolása lehetséges. További magyarázatra nincs szükség.
Az attribútum üzemmód
Attribútum képernyőnél gyakorlatilag két területet használunk. Az egyik megfelel egy alacsonyfelbontású kétszínű videolapnak. Tudjuk erről, hogy itt a képernyőt felépítő bájtok minden egyes bitje meghatároz egy-egy pixelt. A másik terület ugyanakkora méretű, mint a bittérkép, de a feladata egészen más. Minden egyes - a bittérképet tartalmazó - képernyőbájthoz az attribútum módban hozzárendelődik egy másik bájt, ami a színek vezérlését végzi. Az attribútum bájttal a hozzátartozó bájt pixeleinek színét határozhatjuk meg.
Egy bájthoz itt is csak két színt rendelhetünk, de ezeket 16 szín közül választhatjuk. S mivel ezt az egész képernyőn megtehetjük, az összhatás végül is nagyon jó, hiszen viszonylag magas felbontás mellett 16 szín használható. A bittérképen lévő bájthoz (ezt színezzük) ún. papír és tintaszíneket rendelhetünk. Papírszínűek a bájt 0, tintaszínűek a bájt 1 értékű bitjei lesznek.
Az attribútum képernyő egyik hátránya a dupla memóriaigény. A másik az, hogy a képernyő színei "rücskösek" lehetnek, mert nem egy-egy pixelhez rendelhetünk 16 színt, hanem 8-hoz egyszerre (ezek 1 bájtban vannak). A színeknek ez a fajta keveredése jut gyakran érvényre pl. a játékprogramokban: két eltérő színű figura szaladgál a képernyőn, és amikor egymás mellé érnek, akkor kölcsönösen átszínezik egymást.
Az attribútum bájt értelmezése nagyon könnyű:P a papír-, I a tintaszínt meghatározó rész. A 16 érték megadása a megszokott módon történik a 4 biten (0000b=0, 0001b=1 stb.).
Példa: az attribútum bájthoz tartozó, bittérképet alkotó bájt l-es bitjei INK 5, 0-ás bitjei PAPER 12 színűek legyenek. A 12=1100b, ez a négy bit kerül a bájt felső négy bitjének helyére; 5=0101b kerül az alsó négyre, az érték tehát:Az attribútum terület rögtön a bittérkép után helyezkedik el. Ha tehát a kezdőcímére vagyunk kíváncsiak, lekérdezzük a lap kezdőcímét (ez a bittérkép kezdetét jelenti), majd ehhez az értékhez hozzáadjuk a bittérkép bájtokban mért hosszát.
Haladjunk az "anyaggal"!
Van még egy nagy problémánk: nem tudunk kommunikálni a géppel. Erre szolgál a billentyűzet. (Nocsak, nocsak! A szerk.)
A billentyűzet tipikusan beviteli eszköz (csak olvasásra lehet megnyitni), az EXOS-ban KEYBOARD: a periféria elnevezése. Mivel csak egy billentyűzet jár a géphez, így csak egy csatorna lehet a billentyűzethez rendelve. Az előző mondatból már kiderült, hogy a billentyűzetet is a "csatornaszemlélettel" alapján kell használni: a megnyitás, olvasás, lezárás teljesen ugyanúgy megy, mint pl. a fájloknál.
A billentyűzetről karaktereket és blokkokat is tudunk olvasni. A blokkok itt a funkcióbillentyűkhöz rendelt szövegeket jelentik, ezt is a KEYBOARD: kezeli. Nagyon hasznos az ún. csatornaolvasási-állapot funkció. Ennek segítségével programunk futása közben megnézhetjük, hogy van-e olvasható karakter a billentyűzet saját pufferében. Hogy hol lehet ezt használni? Pl. írunk egy programot, amely - amíg nem nyom le egy billentyűt a felhasználó - demózik.
Másik igen hasznos szolgáltatása ennek az eszköznek a STOP billentyű figyelése. Egyik rendszerváltozó (STOP_IRQ) igénybevételével gyönyörű szoftvermegszakításokat lehet készíteni, amellyel ügyes hibakezelést lehet végezni pl. fájlműveletek közben. (Erre jó példa a SPRED program, amely ilyen szoftvermegszakítások segítségével védi magát mindenféle hibáktól.) A billentyűzetnél be lehet állítani a késleltetést és az ismétlés sebességét.
Másik fontos tudnivaló az eszközről, hogy a belső és a két külső botkormányt is kezeli. Ezeket a speciális funkció (EXOS 11) hívásával pöcögtethetjük. A funkció hívása előtt A-ba kell tölteni a billentyűzet csatornaszámát, B-be 9-et (a JOY funkció), végül C-be az illető botkormány azonosítóját: 0-beépített, 1-külső1, 2-külső2. A hívás után C-ben lesz az adott botkormány állása. A bebillent bitek jelentése: bit0 - jobb, bit1 - bal, bit2 - le, bit3 - fel, bit4 - tűzgomb. Természetesen ezek kombinációját is kaphatjuk.
Gépi kódú programozás kezdőknek
Sokan várták már, hogy végre elinduljon ez a cikksorozat, amely segítségével elindulhatnak a gépi kódú programozásban. Rögtön az elején elmondom: azért én írom ezt a sorozatot, mert kb. két évvel ezelőtt vettem rá Zozosoftot, hogy tanítson meg a gépi kódú programozásra az ENTERPRISE-on. Ha én megértettem (!) amit eddig tanított, akkor úgy érzem a Tisztelt Olvasó is meg fogja érteni sorozatunkat. Természetesen még én sem tudok mindent, de amíg sorozatunkból kb. 3-4 részt közlünk, addig nekem is újabb "fogásokat" kell majd megtanulnom.
Szükségünk lesz a kővetkezőkre:
Az említett könyvekből át kell néznünk az utasításokat. Aki erre lusta, vagy nincsenek meg neki ezek a könyvek, az a későbbiek folyamán a példaprogramokból is meg tudja majd érteni azt, hogy melyik utasítás mit jelent. A példaprogramok sorait a pontosvessző után magyarázzuk. Az ASMON program leírását egy régebbi ENTERPRESS (91/3.) újságban megtalálhatják, de cikkeinkben mindig leírjuk, hogy mikor melyik billentyűt kell lenyomni és mit kell beírni.
A gépi kódú példaprogramok mellett mindig lesz egy olyan BASIC program, ami ugyanazt hajtja végre. Így könnyen összehasonlíthatjuk, hogy a BASIC és a gépi kód hogyan hajtja végre ugyanazt. Vágjunk bele! Kezdésnek nézzük azt, hogyan lehet gépi kódban megnyitni egy videólapot amire valami szöveg is van írva. Mivel mi az ASMON-nal dolgozunk ezért itt lehet MAKRÓ-t használni. Lényege az, hogy a programunk elején definiálunk egy általunk megnevezett makró nevet és a későbbiekben amikor ezt a hívást szeretnénk használni csak erre hivatkozunk. Ez megkönnyíti munkánkat, mivel később nem kell ismét több sorban leírnunk ezt a hívást, hanem elég egy sorban.
Tehát: ha nem használnánk makrót, akkor az első beállításunkat (0-ás videómód) a következőképpen adhatnánk meg:
LD B,1 LD C,22 LD D,0 EXOS 16 |
; Írás |
Ha makrót használunk, akkor ez csak egy sort igényel: .SET 22,0. Ezért klassz az ASMON makró funkciója. (A FENAS sokmindenben jobb mint az ASMON, de nem definiálhatunk benne makrót. Mi ezért tanulunk inkább az ASMON-nal).
A videólap megnyitása hasonlít a BASIC videólap megnyitásához. Először lássuk, hogy példaprogramunk hogyan néz ki BASIC-ben:
100 SET VIDEO MODE 0
110 SET VIDEO COLOR 0
120 SET VIDEO X 30
130 SET VIDEO Y 4
140 OPEN £1:"VIDEO:"
150 SET £1:PALETTE 255,0,255,180,0,0,0,0
160 DISPLAY £1:AT 22 FROM 1 TO 4
170 PRINT £1:CHR$(27)&"o"
180 PRINT £1,AT 2,4:"EZ A VIDEOLAP GEPI KOD"
190 SET £1:INK 2
200 PRINT £1,AT 3,1:"SEGITSEGEVEL LETT MEGNYITVA!"
Ezután próbáljuk meg ugyanezt gépi kódban!
Először be kell hívnunk az ASMON programot. Ha lemezről töltjük, akkor :LOAD ASMON13.EXT vagy BASIC-ból LOAD "ASMON13.EXT", ha EPROM-ba égetve használjuk, akkor :ASMON a behívás módja. Bejelentkezik az ASMON. Nyomjuk meg az E betűt (belépünk az ASMON editorába). Kezdjük el begépelni a listán látható programot. Vigyázzunk arra, hogy a címkenevek legyenek elkülönítve az utasításoktól (ez általában 1 tabulátor pozíció). Az utasítások után lévő pontosvesszővel kezdődő szövegeket nem muszáj begépelni, ezek csak magyarázó szövegek. Legfeljebb rövidített változatban érdemes ezeket begépelni, hogy követni tudjuk, melyik utasítás mit csinál.
Most pedig nézzük végig kis programunkat!
5-ős fejléccel fogjuk fordítani (ez azt jelenti, hogy bárhonnan nyugodtan betölthetjük, tehát forrásszövegünkből egy gépi kódú programot készítünk). Az ötös fejléccel lefordított programokat 100H-ra tölti az ASMON (ORG 100H).
Makró definiálása (ezt már tárgyaltuk).
Stack beállítása: STACK (verem). A verem egy olyan tárterület, amelyet felülről lefelé vesz igénybe a processzor, miközben az SP veremmutató mindig a szabad terület fölé mutat.
EXOS-változók beállítása (a videólap üzemmódjának illetve méreteinek meghatározása).
Csatorna megnyitása: megnyitjuk az 1-es csatornát videólapunknak (LD A,1), majd a NEV címkére hivatkozunk (LD DE,NEV), (NEV DB 6,"VIDEO:"). A 6-os szám a hosszbájt (a VIDEO: hat karakterből áll).
Videólap megjelenítése: ugyanaz, mint BASIC-ben a DISPLAY utasítás (lásd a pontosvessző utáni megjegyzéseket!).
ESCAPE sorozatok kiírása: az ESC1 címkenév hívása (LD DE,ESC1). Itt határozzuk meg videólapunk színeit (lásd az ESC1 címkenevet: először kikapcsoljuk a kurzort (DB 27,"o"), utána meghatározzuk a színeket (DB 27,"C" ...), majd beállítjuk a tintaszínt (DB 27,"I" ...)). A 27-es szám az ESC kódja. Itt is lényeges a hosszbájt (HOSSZ DW 15).
Szöveg kiírása a videólapra: a SZOV címkenév hívása (LD DE,SZOV). Itt tároljuk a videólapra kiírt szöveget. Itt is figyeljünk a hosszbájtra! A hosszbájtot egyébként egy egyszerűbb módszerrel is meghatározhatjuk, de erről majd a következő számban írunk.
Várakozási ciklus: 5 másodpercig várakozik a program. A HALT felfüggeszti a processzor működését egy megszakítás érkezéséig.
Visszatérés BASIC-be: először lezárjuk az 1-es csatornát, majd visszatérünk BASIC-be. Az EXOS 26 végigvizsgálja a bővítőláncot.
Ezután már csak az információkat tartalmazó címkeneveket találjuk és a programot záró END utasítást.
Ha végeztünk a lista begépelésével, nyomjuk meg az F8-as billentyűt, amellyel kilépünk az ASMON editorából.
Ha kiléptünk, be kell állítanunk a fordításhoz szükséges opciókat, ehhez a Z billentyűt kell lenyomnunk és válaszolnunk kell a kérdésekre (a SPACE billentyűvel lehet váltogatni az ON/OFF vagy NO/YES között!):
Assembly listing ON [utána ENTER-t kell ütni]
List conditions NO [ENTER]
Force Pass 2 NO [ENTER]
Memory assembly NO [ENTER]
Object file name: TAN1.COM [ez tetszés szerint be írható, utána ENTER]
EXOS module header YES [ENTER]
EXOS module type: 5 [ENTER]
Ezzel beállítottuk a fordítás paramétereit. Hogy mit is állítottunk be? Igen, úgy érzem ezt is el kell magyaráznom.
Assemly listing ON / OFF: A fordítás közbeni lista készítését kapcsolhatjuk KI / BE.
List conditions NO / YES: Feltételes elágazások listázásának KI / BE kapcsolása.
Force Pass 2 NO / YES: YES esetén akkor is megkísérli a 2. menetet a fordító, ha az elsőben hibát talált.
Memory assembly NO / YES: YES esetén az ORG címnek (és az offset címnek) megfelelően a memóriába töltődnek a lefordított gépi kódok (így készíthető futtatható rutin). Ebben az esetben a következő kérdés:
Memory offset: megadható a címszámláláshoz hozzáadandó eltolási érték. Pl. egy rendszerbővítőnek majd a 3. lapon kell futnia, de az assembler nem töltheti ide, hiszen saját maga is itt működik. Ilyenkor:
ORG C000H
offset = 8000H.
Object file name: ha nevet adunk meg, az ASMON a lefordított programot háttértárolóra menti. Ehhez adhatunk meg további információkat:
EXOS module header NO/YES: a fájl elé kell-e EXOS fejléc IGEN / NEM.
EXOS module type: megadható a kimentett fájl típusa:
00 - ASCII, vagy adatfile
01 - Fenntartva (jelenleg nem használt)
02 - Felhasználói relokálható modul
03 - Basic-programlánc
04 - Basic program
05 - Nap file
06 - Abszolút rendszerbővítő
07 - Relokálható rendszerbővítő
08 - Editor dokumentum file (tokenizált)
09 - LISP program
10 - File vége
11-31 - Fenntartva (jelenleg nem használt)
Az opciók beállítása után nyomjuk meg az A betűt. Az ASMON lefordítja és lemezre vagy kazettára menti első gépi kódú programunkat, amit behívhatunk BASIC-ből: LOAD "TAN1.COM" paranccsal, lemezes rendszernél EXDOS-ból vagy akárhonnan :LOAD TAN1.COM parancsokkal.
A programban használt utasítások magyarázata:
EXOS 16 | (EXOS-változó olvasása, írása, vagy átbillentése, B-ben lesz a funkció kódja) |
LD A,1 | (az A-ba [ akkumulátor] 1-et tölt. A=1) |
LD DE,NEV | (A NEV címke tartalmát tölti DE-be) |
EXOS 2 | (csatorna megnyitása) |
EXOS 11 | (speciális funkció, funkció kódja B-ben) |
LD BC,(HOSSZ) | (a HOSSZ címen lévő értéket tölti BC-be) |
EXOS 8 | (blokk kiírása) |
LD B,50 | (B-be 50 kerül, azaz 5 másodpercet vár majd, lásd a példaprogramban) |
HALT | (felfüggeszti a processzor működését) |
DJNZ CIKLUS | (addig csökkenti a ciklusváltozót, 1-el, amíg B = 0 lesz) |
EXOS 3 | (csatorna lezárása) |
LD DE,BASIC | (kilépés BASIC-be, hozzá tartozó sor): |
EXOS 26 | (bővítések vizsgálata. Megnézi, hogy van-e a rendszerben BASIC, ha van meghívja) |
JP VISSZA | (feltétel nélküli ugrás. Ugrik a VISSZA címkenévre) |
Egy kis előzetes a JP utasításhoz:
pl. | JP NZ,VISSZA JP Z,VISSZA JP NC,VISSZA JP C,VISSZA |
(ugrás, ha Z=0) (ugrás, ha Z=1) (ugrás, ha C=0) (ugrás, ha C=1) |
Sorozatunk második részében - mint ahogy előző számunkban ígértük - először megtanuljuk, hogyan lehet a hosszbájtot egyszerűbben meghatározni. Az előző számban lévő példaprogramban így határoztuk meg a hosszbájtot:
HOSSZ ESC1 |
LD A,1 |
A hosszbájtot egyszerűbben meghatározhatjuk a következőképpen:
ESC1 HOSSZ |
LD A,1 |
Mint láthatjuk a HOSSZ címkenévnél történt a változás. Az EQU $-ESC1 utasítás megnézi, hogy az ESC1 címke hány karakterből áll és automatikusan meghatározza a hosszbájtot, megkönnyítve munkánkat. Azért tanultuk meg az előző variációt is, hogy könnyebben megértsük a hosszbájt meghatározását. (Vö.: Ha valakinek az IBM DOS parancsait akarjuk megtanítani, ne a NORTON Commander-t tanítsuk meg neki először, mert soha nem tanulja meg a lemezkezelést!)
Most pedig folytassuk. Előző programunkban a kiírás után egy HALT ciklussal várakoztattuk a programot, azért, hogy lássuk: mit írunk ki a képernyőre. Most egy praktikusabb módszert alkalmazunk. Megtanuljuk a billentyűzet figyelését, azaz a képernyőn való megjelenítés után a program egy billentyű leütésére vár. Hogy ne legyen unalmas sorozatunk, most nem írni fogunk a képernyőre hanem rajzolni, mondjuk egy kört. Utána várakoztatjuk a programot addig, míg egy billentyűt nem ütünk le. Először szokás szerint lássuk BASIC nyelven a programot:
100 SET VIDEO MODE 1
110 SET VIDEO COLOR 0
120 SET VIDEO X 30
130 SET VIDEO Y 20
140 OPEN £1:"video:"
150 SET £1:PALETTE 203,0,0,203,0,0,0,0
160 DISPLAY £1: AT 1 FROM 1 TO 20
170 PLOT £1: 600,300,
180 PLOT £1: ELLIPSE 120,120
190 DO
200 LOOP UNTIL INKEY$<>""
Ugyanezt a programot láthatjuk listánkon gépi kódban. Lépjünk be az ASMON programba és az E billentyűvel lépjünk be az EDITOR-ba. A lista begépelése után az F8-as billentyűvel lépjünk ki. A Z billentyű leütése után végezzük el a szokásos beállításokat:
Assembly Listing ON
List conditions NO
Force Pass 2 NO
Memory assembly NO
Object file name: TAN2.COM
EXOS module header YES
EXOS module type: 5
Az opciók beállítása után nyomjuk meg az A betűt. Elkészült második gépi kódú programunk, amit természetesen - ahogy múltkor is leírtuk - bárhonnan betölthetünk.
Az új utasítások magyarázata:
LD DE,BILL
LD A,3
EXOS 1
LD A,3
EXOS 5
Először a BILL címkenévre hivatkozunk (BILL DB 9,"KEYBOARD:") A KEYBOARD 9 karakterből áll, tehát a DB után hosszbájtnak 9-et kell írnunk. Ezután megnyitjuk billentyűzetcsatornának a 3-as csatornát (LD A,3 és EXOS 1), majd az EXOS 5 funkcióhívás következik, amely a billentyűzetcsatornáról (3-as csatorna) egy karakter leütésére vár. Az EXOS 5 funkcióhívás jelentése: karakterolvasás.
DB 27,"A"
Grafikus sugár pozicionálása.DW 600,300
Itt lesz a kör középpontja.DB 27,"E"
Kör rajzolása, majd megadjuk a kör méretét aDW 120,120
utasítással.
Természetesen programunkban már a hosszbájtot a cikk elején ismertetett módon használjuk. (HOSSZ EQU $-ESC1).
A veremkezelés
Miután már profi módon tudunk írni és rajzolni a képernyőre ideje, hogy megtanuljunk egy nagyon fontos funkciót: a veremkezelést. A verem funkciója egyértelmű: tárolunk benne valamit. Számítógépes körökben általában adatokat szoktak benne tárolni. A veremkezelésnél két új utasítást tanulunk meg: a PUSH elhelyezi a veremben adatainkat, a POP pedig visszahívja onnan. Hogy jobban haladjunk, ezért megtanulunk még egy utasítást, melynek neve ADD. Ennek a jelentése: két regiszter tartalmát összeadja.
Most pedig nézzük részletesen, hogy mit is csinál mintaprogramunk, amelyet most kivételesen az ASMON égiszei alatt futtatunk .ORG 1000h - mivel most csak az ASMON-nal dolgozunk, ezért 1000h-ra fordítjuk programunkat.
LD SP,3FFFh - veremmutató beállítása a 3FFFh címre.
A veremmutató mindig az SP feletti szabad helyre mutat, tehát példánkban a 3FFF címre. Folyamatábráinkon a veremmutató állását mindig a nyíl () mutatja majd.
3FFE 3FFF |
LD HL,10 ; HL regiszterbe 10-et töltünk,
LD BC,20 ; BC regiszterbe 20-at töltünk,
LD DE,30 ; DE regiszterbe 30-at töltünk.
A PUSH HL utasítás elhelyezi a verembe a HL értékét, azaz a 10-et. A PUSH HL utasítás után a verem és a veremmutató:
3FF9 3FFA 3FFB 3FFC 3FFD 3FFE: 10 3FFF: 00 4000 |
A PUSH BC utasítás után a verem és a veremmutató:
3FF9 3FFA 3FFB 3FFC: 20 3FFD: 00 3FFE: 10 3FFF: 00 4000 |
Ezután HL-be nullát helyezünk (LD HL,0), majd meghívjuk az IZE címkét. A processzor elmenti a visszatérési címet, ez 14.
3FF9 3FFA: 14 3FFB: 00 3FFC: 20 3FFD: 00 3FFE: 10 3FFF: 00 4000 |
Az INC SP utasítás eggyel növeli a veremmutatót. Az első INC SP utasítás után a veremmutató:
3FF9 3FFA: 14 3FFB: 00 3FFC: 20 3FFD: 00 3FFE: 10 3FFF: 00 4000 |
A második INC SP utasítás után:
3FF9 3FFA: 14 3FFB: 00 3FFC: 20 3FFD: 00 3FFE: 10 3FFF: 00 4000 |
Ezután a POP BC utasítással visszahívjuk a 20-as értéket.
3FFD |
Lásd a programsorok melletti komment sorokat!!!
Ugyanez a POP HL utasítással, itt a 10-es értéket hívjuk vissza. Szintén lásd a komment sort!
3FFE |
Ezután az ADD HL,DE utasítás következik: HL értéke 10, ehhez adjuk DE értékét ami 30. Tehát: HL=HL+DE (10=10+30 = 40).
Ugyanígy az ADD HL,BC utasítás. HL értéke most 40. Ehhez adjuk BC értékét, ami 20. Tehát: HL=HL+BC (40=40+20 = 60).
Most pedig letesszük a verembe HL értékét a PUSH HL utasítással. A PUSH HL utasítás után a verem és a veremmutató.
3FF9 3FFA: 14 3FFB: 00 3FFC: 20 3FFD: 00 3FFE: 60 3FFF: 00 4000 |
Ezután vissza kell állítanunk a veremmutatót.
A DEC SP utasítás csökkenti a veremmutatót: SP=SP-1.
DEC SP 3FFC
DEC SP 3FFB
DEC SP 3FFA
DEC SP 3FF9
Ezt négyszer kell végrehajtanunk, hogy a veremmutató a helyére kerüljön. Ezután a RET kiveszi a visszatérési címet (14) - ezt azonban még látjuk 3FFA-n.
3FF9 3FFA: 14 3FFB: 00 3FFC: 20 3FFD: 00 3FFE: 60 3FFF: 00 4000 |
Programunkat most a memóriába fordítjuk. Szokásos beállításaink a Z billentyű lenyomása után:
Assembly listing ON,
List conditions NO,
Force Pass 2 NO,
Memory Assembly YES,
Memory offset 0,
Object file name: (ide ne írjunk semmit!).
Az opciók beállítása után nyomjuk meg az A betűt. Könnyen megnézhetjük programunk eredményét, ha az M leütése után begépeljük: 3FF9, de lépésenként is nyomon követhetjük, ha a T betű leütése után beírjuk: 1000. (Az alsó sorban SP= .... után láthatjuk, hogy hol áll a veremmutató.
ORG 100H LD SP,3FFFH LD HL,10 LD BC,20 LD DE,30 PUSH HL PUSH BC LD HL,0 CALL IZE . . . . . . . . . |
; Fordítási cím beállítása ; veremmutató beállíása 3FFFH címre ; HL-be 10 (HL = 10) ; BC-be 20 (BC = 20) ; DE-be 30 (DE = 30) ; Letesszük HL értékét (10) a verembe ; H értéke az (SP-1) címre ->3 FFFH ; L értéke az (SP-2) címre -> 3FFEH ; Letesszük BC értékét (20) a verembe ; B értéke az (SP-3) címre -> 3FFDH ; C értéke az (SP-4) címre -> 3FFCH ; A veremmutató most 3FF9H-ra mutat! ; HL-be 0 (HL=0) ; IZE címke meghívása |
IZEINC SP INC SP POP BC POP HL ADD HL,DE ADD HL,BC PUSH HL DEC SP DEC SP DEC SP DEC SP RET |
; SP = SP+1 (SP = 3FFAH) ; SP = SP+1 (SP = 3FFBH) ; BC regiszter visszatöltése a veremből ; C-be az (SP) cím tartalmát tölti ; B-be az (SP+1) cím tartalmát tölti ; HL regiszter visszatöltése a veremből ; L-be az (SP+2) cím tartalmát tölti ; H-ba az (SP+3) cím tartalmát tölti ; HL = HL+DE azaz 10=10+30 (HL=40) ; HL = HL+BC azaz 40=40+20 (HL=60) ; letesszük HL értékét (60) a verembe ; H értéke az (SP-1) címre -> 3FFFH ; L értéke az (SP-2) címre -> 3FFEH ; SP = SP-1 -> 3FFCH ; SP = SP-1 -> 3FFBH ; SP = SP-1 -> 3FFAH ; SP = SP-1 -> 3FF9H ; visszatérés a CALL után -> SP=3FFCH |
A veremkezelés elég nyers téma (ráadásul szerintem elég nehéz megérteni). Ezért most gépi kód kezdőknek c. sorozatunkat folytassuk egy érdekes rutinnal.
Programunk a szokásos módon indul: fordítási cím (ORG 100H) és a makró beállítása. Ezután definiálunk egy videolapot, melyre kiírunk három sornyi szöveget (ENTERPRESS 3-szor) és a "Billentyű leütésére folytatom." szöveget az alsó sorba. Megnyitunk egy billentyűzet csatornát, majd meghívjuk a VILLOG címkét. A "VILLOG" rutin elkezdi a paletta színeivel (1-255) villogtatni az alsó sorba írt szöveget. A rutin jól használható olyan szöveges képernyőknél, ahol szeretnénk elolvastatni mondjuk egy teljes képernyős szöveget a felhasználóval, ha elolvasta leüt egy billentyűt és folytatódhat a program futása. (Programunkban a billentyű leütése után kilépünk BASIC-be).
Részletesebben a "VILLOG" rutin: LD B,1 - B-ben a palettaszám (1-től indulunk). Belapozzuk a rendszerszegmenst a második lapra (LD A,255 - OUT (0B2H),A). Erre azért van szükség, mert az LPT itt található. Ezután B értékét A-ba töltjük (1), majd átadjuk ezt az értéket 0BA89H címre (az LPT-ben ezen a címen található ennek a sornak - ide írtuk a villogtatni kívánt szöveget - a tintaszín állítása). BC értékét eltesszük a verembe (PUSH BC). Olvasunk egy csatorna készenlétet a billentyűzet csatornáról (LD A,105 - EXOS 9). EXOS 9-nél a kimenő adat C regiszterben található, ez tartalmazza a készenlétjelző bájtot. Olyan csatorna esetén praktikus használni, amely olvasásnál várakozna a készenlétig (pl. KEYBOARD). Így várakozás nélkül is információt kérhetünk arról, van-e olvasható karakter a perifériakezelő pufferben. Visszahívjuk BC-t a veremből. OR A és RET Z vizsgálja: ha van leütött billentyű, akkor visszatér. Következő lépésként várakozunk (HALT-ok), hogy lássuk a villogó sort, majd növeljük B értékét eggyel (INC B). Ha leütöttünk egy billentyűt, akkor visszatérünk meghívni a BILLENTYU címkét, amely figyeli, hogy van-e leütött billentyű. Ha van visszatér BASIC-be. Ha nincs leütött billentyű, akkor a ciklus folytatódik (JP VISSZA).
Programunkat a begépelés után lefordíthatjuk. Szokásos beállításaink a Z billentyű lenyomása után:
Assembly listing ON,
List conditions NO,
Force Pass 2 NO,
Memory Assembly NO,
Object file name: TAN3.COM,
EXOS module header YES,
EXOS module type: 5.
Az opciók beállítása után nyomjuk meg az A betűt. Ezzel harmadik gépi kódú programunkat készítettük el, amit természetesen bárhonnan behívhatunk.
Most a menükezeléssel fogunk megismerkedni, a példaprogram a menükezelést valósítja meg gépi kódban.
A szokásos beállításokat végezzük el: fordítási cím beállítása (ORG 100H), makró funkciók beállítása, valamint a video módok beállítása. Ezután nyitunk egy szöveges videolapot (LD A,50) és beállítjuk a megjelenítési funkciókat (ezt már nem írom le részletesen, lásd előző részek!)
A szokásos ESCAPE szekvenciák után kiíratjuk a kívánt szövegeket. És itt jön a lényeg, a menükezelés. A CALL BILL sor figyeli a billentyűzetet (billentyű leütésre vár). A CP "1" a billentyűleütést vizsgálja, tehát ha az 1-es billentyű lett leütve, akkor a CP "1" utáni sor, JP Z,MENU1 fog aktivizálódni. Jelentése: JP (Jump - ugrás), Z (ha Z=1), MENU1 (a MENU1 nevű címkére ugrik). A többi menüpontnál ugyanígy működik, kivéve a CP "3"-nál. A menüpontok vizsgálatánál szükség van egy olyan rutinra, amelyik azt vizsgálja, ha más billentyűt ütöttünk le. Ez a JP NZ,NEMJO. Ha Z=0 (ha más billentyűt ütöttünk le, pl. a 4-est), akkor visszaugrik a CALL BILL sorra és kezdődik mindet elölről. Ha az 1-es billentyű lett leütve, akkor a BASIC-be térünk vissza, ha a 2-es, akkor a WP-be. A 3-as billentyű leütése után kiíratunk egy szöveget a képernyőre és ezután várunk egy billentyű leütésre. A program a billentyű leütése után visszatér a menübe (CALL BILL és RET).
A közölt menü sokat segíthet azoknak, akik programjukba szeretnének menükezelést beépíteni. Természetesen ez tovább fokozható, készíthetünk almenüket is. Csak arra vigyázzunk, hogy mindent helyesen gépeljünk be az ASMON-ba, mert egy hiba is végzetes lehet (a program elszáll és nem tudjuk mitől). Ez különösen akkor érdekes, ha forrásszövegünk már jó hosszú. Ilyenkor aztán lehet bogarászni!
Lépjünk be az ASMON-ba, nyomjuk meg az E billentyűt és kezdjük el begépelni a közölt programot. Miután begépeltük programlistánkat az ASMON szövegszerkesztőjébe, az F8-al lépjünk ki, majd fordítsuk le forrásszövegünket! A beállítások a szokásosak: a Z billentyű leütése után a válaszok:
Assembly listing ON,
List conditions NO,
Force Pass 2 NO,
Memory assembly NO,
Object file name: TAN4.COM,
EXOS module header YES,
EXOS module type: 5.
Ha válaszoltunk minden kérdésre, nyomjuk meg az A billentyűt. Ha a Force pass 1 és Force pass 2 szöveg után a NO ERROR üzenet jelenik meg, akkor hibátlanul begépeltük a forrásszöveget. Ezután akár BASIC-ből, akár EPDOS-ból vagy EXDOS-ból elindíthatjuk a TAN4.COM nevű programot.
Elérkeztünk sorozatunk ötödik részéhez, amelyben a grafikával kezdünk el foglalkozni. Bár sorozatunk egyik részében már rajzoltunk kört is, de szerintem az sokkal egyszerűbb mint például egy négyzet rajzolása és kiszínezése. Példaprogramunk elég egyszerű(nek tűnik). Mint láthatjuk, a makrót nem csak az EXOS változók beállítására használhatjuk, hanem az ESCAPE szekvenciák írásánál is kiválóan alkalmazható ez a kényelmes funkció. A szokásos videolap megnyitás után megadjuk a koordinátákat. BC regiszterben az X koordináta, DE-ben az Y koordináta, HL-ben az oldalhosszat határozzuk meg, A-ban pedig a keretszínt és a négyzet színét (FILL) adjuk meg. Ezután meghívjuk a RAJZOL rutint, ahol egyszerű érték átadások következnek, azaz az előre meghatározott címkéknek (pl. X1 DW 0...) átadjuk a BC, DE, HL regiszterek tartalmát (a koordinátákat). Két négyzetet rajzolunk és ki is színezzük őket. Miután ez megtörtént, egy billentyű leütésére várunk, majd kilépünk BASIC-be.
A programban szereplő új utasítások magyarázata:
LD A,1*16+5
A regiszterben tároljuk a keretszínt és a négyzet színét (FILL). A *16 értelmezése: ez az érték a felső 4 biten lesz. PUSH AF
A regisztert a verembe helyezzük. Erre azért van szükség, hogy elkülönítsük a négyzet keretszínét és a FILL színét. Ez a legjobb példa arra, hogy miért kell a veremtárolót használni. POP AF
Ugyanaz, csak visszahívjuk A értékét a veremből (azt az értéket, amit a PUSH AF-el eltettünk). AND 15
Logikai és. Törli azokat a pontokat, ahol A bitjei 0 állapotúak. Maximum 15 maradhat. SRL A
Képzeljünk el az asztalon 8 poharat (A). Vannak tele poharak (1) és üres poharak (0). Az SRL A utasítás eggyel jobbra mozgatja a poharakat, azaz a jobb szélső pohár leesik az asztalról és marad 7. Programunkban ez az utasítás 4-szer szerepel, tehát 4 marad. OR A
Logikai vagy. Kigyújtja azokat a pontokat, ahol A bitjei 1 állapotúak. Programunkban: megvizsgálja, hogy A regiszter értéke 0-e. Ennek azért van jelentősége, hogy a keretszín ne legyen fekete. DB 27,"s"
Surár kikapcsolása. DB 27,"A"
Grafikus sugár pozicionálása. DB 27,"S"
Sugár bekapcsolása. DB 27,"R"
Relatív sugármozgás. DB 27,"F"
Grafikus kitöltés. DW 0
A kétbájtos értéket tárolja a programban. Ide kerülnek azok az értékek, amelyeket a RAJZOL rutin ad át. (LD (X1),BC, LD (Y1),DE és LD (X2),HL valamint LD (Y2),HL utasítások).DW 16,16
A rajzolósugarat elmozgatjuk, hogy ki tudjuk színezni a négyzetet. Ha ezt nem tennénk meg, akkor a keret színe változna meg.
Programunkat az ASMON editorába gépeljük be, majd a Z billentyű lenyomása után állítsuk be a fordításhoz szükséges paramétereket:
Assembly listing ON,
List conditions NO,
Force Pass 2 NO,
Memory assembly NO,
Object file name: TAN5.COM,
EXOS module header YES,
EXOS module type: 5.
A válaszok után nyomjuk meg az A billentyűt. Elkészült ötödik gépi kódú programunk.
Előző számunkban egy négyzetet rajzoltunk ki a képernyőre. Most - ahogy ígértük - ezt a programot kiegészítjük egy RND-rutinnal. Ezzel a rutinnal elérhetjük, hogy a képernyőre több négyzete is kirajzoljunk, de úgy, hogy a program véletlenszerűen rajzolja a négyzeteket. Most nem tanulunk új utasításokat (kell egy kis pihenés is. Ez a téli szünet). Az RND rutint HSOFT alkotta meg még régebben, ezt közöltük is egyik Enterpressben. Mint a programban látható minden rajzolás előtt meghívjuk ezt a rutint (CALL RND). Az RND-rutint úgy érzem fölösleges megmagyarázni, mert olyan utasítások szerepelnek benne, amelyeket már tartalmaztak előző programjaink.
VI. rész - Rendszerbővítő készítése
A nagyobb, valamilyen rendszert alkotó programok rendszerbővítőként kapcsolódnak az ENTERPRISE operációs rendszeréhez. Ilyen a gépbe csatlakoztatott BASIC, valamint minden EPROM-ba égetett program, de ilyen a kazettáról vagy lemezről betöltött PASCAL vagy ASMON program is.
A rendszerbővítők két csoportba sorolhatók:
A ROM-bővítőket és az abszolút rendszerbővítőket a 3. lapon a C00AH belépési címen hívja az EXOS.
Az ENTERPRISE egyik nagyon jó tulajdonsága a rendszerbővítő. Különösen az EPROM-ba égetett programokra gondolok. Sok programot tehetünk így rezidenssé, és nem kell ezeket mindig újra és újra betölteni. Ez természetesen azoknak nagy előny, akiknek még nincs floppy-meghajtójuk, de akinek van, annak is kényelmi szempont. Ha az ezzel kapcsolatos hardver választékot nézzük, akkor nagyon hasznos Mészáros Gyula 6 EPROM-helyes (egészen helyes) kártyája. Ennek a legújabb változata az SRAM kártya (ez is 6 férőhelyes). Ide akár statikus RAM-ot, vagy EPROM-ot is elhelyezhetünk. Ebbe a kártyába akár 6 db 27512-es EPROM-ot (64 kb) is tehetünk. Pillanatnyilag nincs ENTERPRISE-ra annyi rendszerbővítő, hogy ezt maximálisan kihasználhatnánk. A baloldali cartridge-be összesen 64 Kb (2x32) programot tehetünk (ebből 16 Kb a BASIC. Ezt úgy is megoldhatjuk, hogy beszerzünk egy 3 foglalatos kártyát és egy kapcsoló segítségével két 32 K-s EPROM között kapcsolgatunk (természetesen a számítógép kikapcsolt állapotában).
Rendszerbővítőnk elég egyszerű, nem akartuk túlbonyolítani. Bővítőnket a 3. lapon a C00AH címen kezdjük. A C az úgynevezett akciókód. Ez a vezérlés különböző értékeinél különböző programpontokra kerül.
A CIM címkénél található a HELP szövegünk. Ha kiadjuk majd a :HELP parancsot, akkor megjelenik a képernyőn az "EPS version 1.0" szöveg a többi rendszerbővítővel együtt.
Mögötte található az "Első rendszerbővítőnk" szöveg, amelyet úgy írathatunk ki, hogy kiadjuk a :HELP EPS parancsot, részletes információt kérve a rendszerbővítőnkről.
A programban van egy vizsgáló rutin is (VIZSGAL címke). Ennek a funkciója: ha a :EPS parancs lett kiadva, akkor végrehajtja az SZ2 címkenevet, ahol a szövegünk van. Kiírja a képernyőre egysoros üzenetünket: "Ide kerül a program". Ha más parancs érkezett, pl. egy hibás, akkor hibaüzenetet küld.
Természetesen ezt a részt behelyettesíthetjük saját programjainkkal is. Programunkat, mivel ez egy rendszerbővítő, most másképp kell lefordítanunk az ASMON-nal. A program fordításához szükséges beállítások: Miután begépeltük a programlistát, lépjünk ki F8-al az ASMON editorából. Nyomjuk meg a Z billentyűt és állítsuk be a fordításhoz szükséges paramétereket:
Assembly Listing: ON
List Conditions: NO
Force Pass 2: NO
Memory Assembly: NO
Object file name: EPS.EXT
EXOS module header: YES
EXOS module type: 6
Az opciók beállítása után nyomjuk meg az A billentyűt. Elkészítettük első rendszerbővítőnket, amelyet betölthetünk akár BASIC-ből is a LOAD "EPS.EXT" paranccsal. Miután betöltöttük, győződjünk meg arról, hogy sikerült-e beláncolnia az EXOS-nak, adjuk ki a :HELP parancsot. Ha a lista elején megjelenik az "EPS version 1.0" üzenet, akkor sikerült minden. Utána adjuk ki a :HELP EPS parancsot, amellyel bővebb információhoz juthatunk a rendszerbővítőről. Ha a :EPS parancsot adjuk ki, akkor végrehajtódik egy kis program, amely jelen esetben letörli a képernyőt és utána kiír egy sort. Ez, mint említettem, lehet a saját programunk is. Ha saját programot szeretnénk ennek helyére, akkor az LD A,255 sornál kezdhetjük programunkat. Ne felejtsük el a program végére írni a XOR A és az LD C,A sorokat és persze utána a RET-et. Jó kísérletezést kívánok, remélem mindenki örül majd első rendszerbővítőjének, és bízom benne, hogy saját rendszerbővítőkkel gazdagítják majd programjaikat.
VII. rész - LPT-kezelés és scroll
Mostani számunkban az LPT kezelése és egy szöveg scrollozása a téma. Ezt a két témát egy mintaprogramban mutatjuk be. Az LPT kezeléséről már indult az ENTERPRESS-ben egy sorozat.
Programunkban az első teendő, hogy belapozzuk a 0-ás VIDEO szegmenst. 25 karakteres sort készítünk (LD A,25). Az ezt követő ciklusban kap helyet a szöveg melyet scrollozunk, valamint a szinkronizáció (SYNC). Az OUT (82H),A és OUT (83H),A utasításokkal tudatjuk a NICK chippel az elkészített LPT-nket. Ezután következik egy utasítás az EI. Ennek jelentése: megszakítások engedélyezése. (Lásd. Haluska Laci kezdőknek szóló cikkét lapunkban a megszakításkezelésről!) Ezután elhelyezzük a szöveget, és meghatározzuk a VIDEO címet (LD IX,4000, LD HL,1000). Az SCIK1 és SCIK2 címkenevek alatt a scrollozás rutinjait találjuk. A program legvégén végtelenítjük a ciklust (JR SCIK1).
A programban további új utasítás az IX regiszter. Lássuk ennek a magyarázatát:
Az IX és IY 16 bites indexregiszterek elsősorban a memória (pontosabban egy 256 bájtnyi memóriaintervallum) címzésére szolgál. Az indexregiszterek alsó és felső 8 bitje - az L, ill. H regiszterekre vonatkozó adatmozgató, logikai és aritmetikai utasítások kiterjesztéseként - külön is használható. Az IX és IY regiszterek - HL kiterjesztésként - 16 bites akkumulátorként is használhatók.
Példa:
LD A,(IX+2)
A-t az IX+2 memóriacímtol tölti fel.
Fordított irányú adatmozgatás esetén:
LD (IX+2),A
A regiszter tartalma a célregiszterbe kerül.
Néhány szó az LPT-ről:
Az LPT (más néven sorparaméter tábla) a 2. lapon lévő FFH rendszerszegmensen a B900H címen kezdődik. A LPT 16 bájtos blokkokban tárolja egy-egy sor (1-255 pixelsor magas ablak) megjelenítéséhez szükséges (mód, tárolási cím, szín stb.) információkat. Részletesebben lásd a fentebb említett cikksorozatot.
Programunkat a szokásos módon fordítjuk az ASMON programmal, ezt nem írom le, mert szerintem már mindenki kívülről fújja.
Külön öröm, hogy Ferenci Jóska is küldött egy egyszerű kis programot, a billentyűzet kiolvasási mátrix táblázattal (ami még soha nem jelent meg az ENTERPRESS-ben). Ezt egészíti ki Hsoft kis programja (szintén egy táblázattal, ami más mint az előző) amely a joystick olvasását mutatja be.
A szorzás és osztás gyakran használt művelet Sajnos a Z80 csak az összeadás-kivonást támogatja, ezért a szorzást összeadásokra, az osztást pedig kivonásokra vezetjük vissza. A 7*4-et leírhatjuk 7+7+7+7 alakban a végeredmény ugyanaz. Ez a módszer nem célszerű a nagyobb számok esetében, pl. 30000*25000 kiszámolása hosszú időbe tellene.
Kettes számrendszerben a következő algoritmus használatos. Jobbra shifteljük a szorzót (vagyis kettővel osztjuk) A kicsorduló legkisebb bittel megszorozzuk a szorzandót és ezt hozzáadjuk az eredményszámlálóhoz. Két eset lehetséges, 0 vagy 1. A nullával való szorzás nulla, eggyel pedig a szorzandó értékével azonos eredményt ad. Ebből következik, hogy ha a bit értéke 1, az eredményszámlálóhoz hozzáadjuk a szorzandót. A szorzandót ezután balra shifteljük. (vagyis 2-vel megszorozzuk) Az eljárást addig ismételjük, amíg a szorzó értéke nullára csökken.
Osztás esetén az egészet megfordítjuk. Mielőtt elkezdenénk, az osztót balra shifteljük annyiszor, amennyi a hányados bitjeinek száma, mínusz 1. Összehasonlítjuk az osztót az osztandóval. Ha az osztandó kisebb, az eredménybit nulla. Ellenkező esetben kivonjuk az osztandóból az osztót, és az eredménybit egy lesz. Balra shifteljük a hányadost úgy, hogy a nulladik bit helyére az eredménybit kerüljön. Jobbra shifteljük az osztót. Az eljárást annyiszor kell elvégezni amennyi a hányados bitjeinek száma.
Vannak olyan esetek amikor felesleges a MULDIV rutinokat használni:
Szorzés kettővel: | Osztás kettővel: | ||
HL=HL*2 |
ADD HL,HL | HL=HL/2 |
SRL H RR L |
A=A*2 |
ADD A,A | ||
DE=DE*2 |
SLA E RL D |
A=A/2 |
SRL A |
A szorzó értékét törzstényezőkre bontva, más értékek esetében is lehetséges a közvetlen szorzás elvégzése. Pl.:
HL=HL+16(HL=HL*2*2*2*2) ADD HL,HL
ADD HL,HL
ADD HL,HL
ADD HL,HL; *2
; *4
; *8
; * 16 Pl. A=A*10(A=A*2*2*2+A*2) ADD A,A
LD B,A
ADD A,A
ADD A,A
ADD A,B; *2
; A kétszeres érték elmentése
; *4
; *8
; + a kétszeres érték
A MULDIV.ASM lista rutinjai:
Szorzás: Szorzat: MULBCDE:
MULBCDE1:
MULBCDE2:
MULAE:
MULADE:
MULADE1:HL'HL=BC*DE
HL'HL=BC*DE+HL
HL'HL=BC'*DE'+HLHL'
HL=A*E
HL=A*DE
HL=A*DE+HLOsztás: Hányados: Maradék: DIVHLDE:
DIVHLA:
DIVLB:BC=INT(HLHL'/DE)
DE=INT(HL/A)
A=INT(L/B)HLHL'=MOD(HLHL',DE)
HL=MOD(HL,A)
L=MOD(L,B)
A szorzórutinnal a hatványozás is megoldható. Most a négyzetgyökvonásra adunk megoldást. Az assembly gyökvonás az osztáshoz hasonlóan kivonásokra épül. A kiindulási azonosság:
SQR(X) = (A+B)^2 = A^2+2AB+B^2 = A^2+(2A+B)*B
Ciklusonként a maradékot megszorozzuk néggyel, az eredményt pedig kettővel. Az X felső két bitjét begörgetjük a MARADÉK alsó bitjeibe. Tudjuk, hogy a maradékból az A^2 már ki van vonva (ez persze a kezdetben nulla) tehát ki kell vonni még a (2A+B)*B tagot. (A = pillanatnyi eredmény, B = az eredmény következő bitje.) Mivel a B csak 0 vagy 1 lehet, előbb feltételezzük hogy B=1. Kivonjuk a maradékból a 2A+1-et. Túlcsordulás esetén a maradékot visszaállítjuk az előző értékre. A CARRY komplemense megadja a B értékét, melyet begörgetünk az eredmény alsó bitjébe. Az SQR.ASM listában két féle, 16 és 32 bites rutin áll a rendelkezésünkre.
SQRHL: A=SQR(HL)
SQRDEHL: HL=SQR(DEHL)