str.38 Podmíněné absolutní skoky Výzvy podprogramů a povely k návratu ----------------------------------------------------------------| |Mnemonic | Působení | Flagy | Hexkód | Analog BASIC | |---------------------------------------------------------------| | Absolutní skoky podmíněné | |---------------------------------------------------------------| | JP Z,nm | if Z=1 PC=nm | ----- | CA,m,n | IF Z=1 GOTO nm | | JP NZ,nm | if Z=0 PC=nm | ----- | C2,m,n | IF Z=0 GOTO nm | | JP C,nm | if C=1 PC=nm | ----- | DA,m,n | IF C=1 GOTO nm | | JP NC,nm | if C=0 PC=nm | ----- | D2,m,n | IF C=0 GOTO nm | | JP M,nm | if S=1 PC=nm | ----- | FA,m,n | IF S=1 GOTO nm | | JP P,nm | if S=0 PC=nm | ----- | F2,m,n | IF S=0 GOTO nm | | JP PE,nm | if P=1 PC=nm | ----- | EA,m,n | IF P=1 GOTO nm | | JP PO,nm | if P=0 PC=nm | ----- | E2,m,n | IF P=0 GOTO nm | |---------------------------------------------------------------| | Výzvy podprogramů podmíněné | |---------------------------------------------------------------| | CALL Z,nm | if Z=1 CALL nm | ----- | CC,m,n | IF Z=1 GOSUB nm | | CALL NZ,nm| if Z=0 CALL nm | ----- | C4,m,n | IF Z=0 GOSUB nm | | CALL C,nm | if C=1 CAll nm | ----- | DC,m,n | IF C=1 GOSUB nm | | CALL NC,nm| if C=0 CALL nm | ----- | D4,m,n | IF C=0 GOSUB nm | | CALL M,nm | if S=1 CALL nm | ----- | FC,m,n | IF S=1 GOSUB nm | | CALL P,nm | if S=0 CALL nm | ----- | F4,m,n | IF S=0 GOSUB nm | | CALL PE,nm| if P=1 CALL nm | ----- | EC,m,n | IF P=1 GOSUB nm | | CALL PO,nm| if P=0 CALL nm | ----- | E4,m,n | IF P=0 GOSUB nm | |---------------------------------------------------------------| | Povely k návratu podmíněné | |---------------------------------------------------------------| | RET Z | if Z-1 RET | ----- | C8 | IF Z=1 RETURN | | RET NZ | if Z=0 RET | ----- | C0 | IF Z=0 RETURN | | RET C | if C=1 RET | ----- | D8 | IF C=1 RETURN | | RET NC | if C=0 RET | ----- | D0 | IF C=0 RETURN | | RET M | if S=1 RET | ----- | F8 | IF S=1 RETURN | | RET P | if S=0 RET | ----- | F0 | IF S=0 RETURN | | RET PE | if P=1 RET | ----- | E8 | IF P=1 RETURN | | RET PO | if P=0 RET | ----- | E0 | IF P=0 RETURN | ----------------------------------------------------------------- Podmíněné skokové příkazy mají stejnou funkci jako if v BASICu. Nabízejí jedinou možnost řešit ve strojovém kódu rozhodování. Na těchto podmíněných skokových příkazech se vždy testuje stav jednoho CPU Flagu, a podle výsledku testu je skok proveden, nebo se neděje nic. Podívejme se na povel ,,JP Z,nm'' (Jump if Z set, česky skoč když Z je nastaveno). Skok je proveden jen když Z-Flag je nastaven na 1. Toto se musí napřed provést příslušným povelem, např. porovnávacím příkazem jako "CP 5" (Compare A with 5, česky porovnej A s 5). Když je při porovnání A=5, pak bude Z=1, jinak je Z=0. Příklad: adresa kód povel Analog BASIC 8000: 06 11 LD B,11 8000:B=11 8002: FE 05 CP 05 8002:IF A=5 GOTO 8009 8004: CA 09 80 JP Z,8009 8007: 06 00 LD B,00 8007:B=00 8009: C9 RET 8009:RETURN str.39 Než budete startovat tento program, dejte do akumulátoru A hodnotu např. A=12. Když teď (po PC=8000) startujete program, dopadne porovnání A s 5 negativně a nebude proveden žádný skok, takže na konci bude v B reg. hodnota 00. Naproti tomu když znovu budete startovat program s A=5, pak najdete v B reg. hodnotu 11, protože porovnání dopadlo pozitivně a povel "LD B,00" byl přeskočen. Porovnávací povely samozřejmě nejsou jediné povely, které ovlivňují Flaggy. To totiž dělá mnoho aritmetických povelů např. 8bitové úbytkové příkazy (dekrementní). Povel "DEC A" (Decrement A, česky sniž A) snižuje obsah akumulátoru o 1. Vezmeme-li k tomu ještě povel JP NZ (Jump if Z reset, česky skoč když Z je vynulováno), pak lze programovat počítací smyčku, ekvivalent v BASICu pro FOR .... NEXT. Přiklad: adresa kód povel Analog BASIC 8000: 3D DEC A 8000:A=A-1 8001: C2 00 80 JP NZ,8000 8001:IF A<>0 GOTO 8000 8004: C9 RET 8004:RETURN Při startu programu s A=7 a < >, proběhne smyčka 7krát, jak můžete sledovat při zpracování po jednotlivých krocích.Při prvním průběhu bude A=6, při druhém A=5......při sedmém bude A=0 a tím se Z Flag nastaví na 1, takže teď se již neuskuteční zpětný skok a program je ukončen. Takové počítací smyčky se ve strojovém jazyku používají hodně, přičemž se často použije jeden registr jako počítadlo. Samozřejmě předchází část programu před tímto sledem povelů, která se uzavře do této smyčky, přičemž se skáče o více než 3 byty od příkazu JP NZ zpět. Má se totiž opakovat určitá část programu a ne nic, jako v našem příkl. Relativní skoky nepodmíněné a podmíněné ----------------------------------------------------------------- | Mnemonic | Působení | Flagy | Hexkód | Analog BASIC | |---------------------------------------------------------------| | Relativní skoky nepodmíněné | |---------------------------------------------------------------| | JR e | PC=PC+e | ----- | 18,e-2 | GOTO PC+e | |---------------------------------------------------------------| | Relativní skoky podmíněné | |---------------------------------------------------------------| | JR Z,e | if Z=1 PC=PC+e | ----- | 28,e-2 | IF Z=1 GOTO PC+e| | JR NZ,e | if Z=0 PC=PC+e | ----- | 20,e-2 | IF Z=0 GOTO PC+e| | JR C,e | if C=1 PC=PC-e | ----- | 38,e-2 | IF C=1 GOTO PC+e| | JR NC,e | if C=0 PC=PC-e | ----- | 30,e-2 | IF C=0 GOTO PC+e| | | | | | | | DJNZ e | B=B-1 | ----- | 10,e-2 | B=B-1 | | | if B<>0 PC=PC+e| | | IFB<>0 GOTO PC+e| ----------------------------------------------------------------- Povely JR (Jump relative, česky skoč relativně) se liší od JP povelů pouze tím, že se neudává adresa přímo, kam má skok vést, ale pouze o kolik bytů vpřed nebo nazpět se má skočit. Začíná se počítat u adresy u které stojí JR povel jako u nulového bodu. Odtud lze skákat až do 129 bytů dopředu nebo až o 128 bytů dozadu. Kolik bytů se má přeskočit udává se hodnotou e, e je 8bitové číslo s předznamenáním, je-li pozitivní pak je PC povýšeno o e (skok dopředu), je-li e negativní, pak se PC jako komplementární dvojice snižuje o e (viz dodatek A) (skok dozadu). str.40 Předcházející příklad povelu s JP vypadá s JR povely následovně. Příklad: adresy kód povel Analog BASIC 8000: 18 02 JR 04 8000:GOTO 8000+4 8002: 06 99 LD B,99 8002:B=99 8004: 0E 99 LD C,99 8004:C=99 8006: 18 FE JR 00 8006:GOTO 8006+0 8008: C9 RET 8008:RETURN Nabírací povel pro B reg. se přeskočí stejně jako nahoře a program končí rovněž v nekonečné smyčce. Vyzkoušejte si to trasováním programu. Prohlédnete-li si podrobně příklad, budete se ptát proč povel JR 04 dává kód "18 02". Věc je jednoduchá: Abychom při zadávání programu mohli pohodlně vycházet z adresy u které začíná povel JR, udáte jinou hodnotu pro rozpětí skoku než je pro CPU potřebná. CPU totiž bere stav počítadla po přečtení JR povelu jako nulový bod, tedy adresa prvního bytu za povelem JR. Programování pomocí povelů JR nabízí dvě přednosti: Za prvé se programy zkracují, protože JR povel potřebuje jen dva byty, za druhé lze programy v paměti volně přesouvat, když jsou použity pouze JR povely. Jelikož u "JP 8004" stojí 8004 v programovém kódu, vždy se tedy skáče na 8004, i když se program má ukládat jinde. Při "JR 04" je naopak zcela lhostejné, kde tento povel stojí, je prováděn vždy skok 4 byty dopředu. Bohužel neexistuje u většiny 8bitových procesorů žádný relativní podprogramový skok, kromě toho je délka skoku omezena na 127 bytů, takže pro delší programy je nutno použít jiných technik, mají-li být relokatibilní (přemístitelné). Pro krátké programy však stačí používáme-li pouze relativní skoky. Právě tak jako pro absolutní skokové příkazy existují též pro podmíněné povely relativních skoků. Ovšem se dá u relativních skoků dotazovat pouze na C- a Z- Flagy. Předcházející příklad s povelem JP NZ vypadá s povelem JR NZ následovně: Příklad: adresa kód povel Analog BASIC 8000: 3D DEC A 8000:A=A-1 8001: 20 FD JR NZ,8000-$ 8001:IF A<>0 GOTO 8000 8003: C9 RET 8003:RETURN Zde máte druhou a taky komfortní možnost udat v Line- assembleru cíl skoku. Namísto pracného výpočtu rozpětí skoku, zadáte jednoduše absolutní adresu skoku. Aby assembler věděl, že z ní má přepočítat relativní hodnotu, připojíte k adrese -$ (poznámka pro specialisty: $ je přesně aktuální stav PC). str.41 Povel DJNZ (Decrement and Jump if Not Zero) je zvláštností, protože sdružuje hned obě funkce smyčky počítadla v jednu. Registr B se sníží, a dokud obsah B nedosáhne 00 se provádí skok. Příklad: adresa kód povel Analog BASIC 8000: 10 FE DJNZ 8000-$ 8000:B=B-1:IF B<>0 GOTO 8000 8002: C9 RET 8002:RETURN Tento příklad funguje stejně jako předcházející, pouze že se počítá v B. Kromě toho se musí v B předesílat hodnota 6, jestliže chceme nechat smyčku proběhnout 7 krát. Jak vidět povel DJNZ šetří byt v programu, a co je daleko důležítější, nemění žádný Flag jako je tomu u povelů dekrementních. Tuto vlastnost se naučíme teprve později oceňovat, teď budiž podotknuto, že právě u počítadlových smyček potřebujeme často informace flagů v opakované části programu, takže nesmí být zfalšovány dekrementním povelem. 5.2 NABÍRÁNÍ A UKLÁDÁNÍ ----------------------- Těmito povely se transferují data mezi registry a paměťovými buňkami. Zčásti jsou vám nabírací povely již známy. Jinak jsou tyto povely velice neproblematické, proto se nechceme dlouho zdržovat u základních věcí. 8bitové nabírací a střádací povely ---------------------------------- ----------------------------------------------------------------- | Mnemonic | Působení | Flagy | Hexkód | Analog BASIC | |---------------------------------------------------------------| | 8 bitové povely nabírací | |---------------------------------------------------------------| | LD A,r | A=r | ----- | 78+reg | A=r | | LD A,n | A=n | ----- | 3E,n | A=n | | LD A,(nm) | A=(nm) | ----- | 3A,m,n | A=PEEK (nm) | | LD A,(BC) | A=(BC) | ----- | 0A | A=PEEK (BC) | | LD A,(DE) | A=(DE) | ----- | 1A | A=PEEK (DE) | | LD A,(HL) | A=(HL) | ----- | 7E | A=PEEK (HL) | | | | | | | | LD B,r | B=r | ----- | 40+reg | B=r | | LD B,n | B=n | ----- | 06,n | B=n | | LD B,(HL) | B=(HL) | ----- | 46 | B=PEEK (HL) | | | | | | | | LD C,r | C=r | ----- | 48+reg | C=r | | LD C,n | C=n | ----- | 0E,n | C=n | | LD C,(HL) | C=(HL) | ----- | 4E | C=PEEK (HL) | | | | | | | | LD D,r | D=r | ----- | 50+reg | D=r | | LD D,n | D=n | ----- | 16,n | D=n | | LD D,(HL) | D=(HL) | ----- | 56 | D=PEEK (HL) | | | | | | | | LD E,r | E=r | ----- | 58+reg | E=r | | LD E,n | E=n | ----- | 1E,n | E=n | | LD E,(HL) | E=(HL) | ----- | 5E | E=PEEK (HL) | str.42 | | | | | | | LD H,r | H=r | ----- | 60+reg | H=r | | LD H,n | H=n | ----- | 26,n | H=n | | LD H,(HL) | H=(HL) | ----- | 66 | H=PEEK (HL) | | | | | | | | LD L,r | L=r | ----- | 68+reg | L=r | | LD L,n | L=n | ----- | 2E,n | L=n | | LD L,(HL) | L=(HL) | ----- | 6E | L=PEEK (HL) | |---------------------------------------------------------------| | 8mi bitové střádací povely | |---------------------------------------------------------------| | LD (nm),A | (nm)=A | ----- | 32,m,n | POKE nm,A | | LD (BC),A | (BC)=A | ----- | 02 | POKE BC,A | | LD (DE),A | (DE)=A | ----- | ED,4F | POKE DE,A | | | | | | | | LD (HL),r | (HL)=r | ----- | 70+reg | POKE HL,r | | LD (HL),n | (HL)=n | ----- | 36,n | POKE HL,n | ----------------------------------------------------------------- Aby se povelové tabulky moc nerozrůstaly, byly některé povely shrnuty. Všude tam, kde v rubrice "mnemende" je ,r', může se povel utvořit spolu s dole uváděným registrem B....A. Aby se dal zjistit Op-kód žádaného povelu, přičítá se k povelovému kódu příslušná hodnota registru ,reg' podle uvedené tabulky: --------------------------------- | r | B | C | D | E | H | L | A | --------------------------------- |reg| 0 | 1 | 2 | 3 | 4 | 5 | 7 | --------------------------------- Povel LD A,(8900) "naplní" akumulátor obsahem buňky 8900, povel LD (8900),A "ukládá" obsah A do paměť. buňky 8900. Pomocí LD A,B se obsah reg. B přesune do A reg., atd. Pro "naplnění" akumulátoru nebo střádání v akumulátoru je k dispozici registračně přímý a nepřímý způsob adresování (viz 4.5). Příklad: přímý : LD (8900),A POKE 8900,A nepřímý : LD HL,8900 HL=8900 LD (HL),A POKE HL,A Oba příklady mají stejnou působnost, A je uložen do paměť. buňky 8900. Nepřímé adresování je zde krajně nepříznivé, protože musí napřed nabrat HL reg. adresou pak teprve je možné "vybrat" akumulátor. Výhoda nepřímého adresování je však rychlost, když se plní více paměť. buňek. Dejme tomu, že budeme chtít vymazat paměť v rozsahu 8900-890f t.j. vložit hodnotu 00. Při přímém adresování by to vypadalo takto : LD A,0 A=0 LD (8900),A POKE 8900,A LD (8901),A POKE 8901,A LD (8902),A POKE 8902,A . . . . LD (890F),A POKE 890F,A str.43 Potřebujeme tedy 16 povelů LD, aby se dal smazat tento rozsah v paměti. Programujeme-li však počítadlovou smyčku (viz 5.1) a při tom použijeme nepřímé adresováni, bude to podstatně příznivější. Při tom ale potřebujeme povel INC (Increment, česky zvyšuj), kterým se obsah použitého pointerregistru (viz 4.4) vždy o 1 zvýší. Příklad: adresa kód povel analog BASIC 8000: 21 00 89 LD HL,8900 8000:HL=8900 8003: 06 10 LD B,10 8003:B=10 8005: 3E 00 LD A,00 8005:A=00 8007: 77 LD (HL),A 8007:POKE HL,A 8008: 23 INC HL 8008:HL=HL+1 8009: 10 FC DJNZ 8007-$ 8009:B=B-1:IF B<>0 GOTO 8007 800B: C9 RET 800B:RETURN Podstatná část tohoto programu se odehrává mezi 8007 a 8008, tam je totiž programová smyčka ve které se transferuje obsah akumulátoru do té paměťové buňky jejíž adresa stojí v HL reg. CPU. Následně je HL zvýšeno o 1, takže příští paměťovou buňku lze aktivovat a opakovat smyčku tak často, dokud B nedosáhne 00. Na záčátku programu je nutno se postarat, aby HL začínalo správnou adresou, v B reg. bude předem udán počet průběhů smyčky a do A dána 00, kterou se má doplnit paměťový rozsah. Chcete-li program vyzkoušet, nezapomeňte do paměťového rozsahu něco zadat, abyste později poznali zda tento rozsah byl skutečně vymazán. Použité nepřímé adresování nabízí dvě podstatné přednosti : Za prvé je program podstatně kratší než za přímého adresování, obsazuje pouze 12 bytů místo 51. Za druhé se dají programy napsat nepřímým adresováním tak, že se dají používat všeobecněji. Můžete totiž s příkladovým programem vymazat též paměťový rozsah mezi 8920 a 892F. K tomu potřebujete pouze vložit do HL reg. hodnotu 8920 a pak skočit na adresu 8003. Zkuste si to pomocí traceru. Zadejte HL=8920 a PC=8003 a startujte program. Teď se bude nový rozsah nulovat. Příklad: adresa kód povel Analog BASIC 8010: 21 20 89 LD HL,8920 8010: HL=8920 8013: 18 EE JR 8003-$ 8013: goto 8003 Pro nový program, který teď začíná na adr. 8010 a smazává rozsah mezi 8920 a 892F, potřebovali jsme pouze 5 bytů. Tímto způsobem se šetří místem v paměti, a čím méně musíme dodatečně programovat, tím méně se naskytne příležitost udělat chybu. Jako další příklad vám ukážeme ještě menší program blokového transferu, pomocí kterého můžete jeden paměťový rozsah překopírovat na jiné místo. Příklad: adresa kód povel Analog BASIC 8000: 7E LD A,(HL) 8000: A=PEEK (HL) 8001: 12 LD (DE),A 8001: POKE DE,A 8002: 23 INC HL 8002: HL=HL+1 8003: 13 INC DE 8003: DE=DE+1 8004: 10 FA DJNZ 8000-$ 8004: B=B-1 IF B<>0 GOTO 8000 8006: C9 RET 8006: RETURN str.44 Než odstartujete tento program, musíte ovšem vložit do HL artovací adresu rozsahu který má být kopírován, dále do DE startovací adresu, kam se má kopírovat, a do B počet kopírovaných bytů, naboť jinak byste mohli rozházet a smíchat celou paměť. Povely blokového transferu -------------------------- Protože takové povelové kombinace jako "LD A,(HL)", "LD (DE),A","INC HL", "INC DE" se vyskytují velmi často napadlo fy ZILOG něco velmi užitečného, aby se zkrátily programy a současně také zrychlily. Toto jsou povely, které vykonávájí oboje: transferují byty a současně zvyšují resp. snižují pointery. ----------------------------------------------------------------- | Mnemonic | Působení | Flagy | Hexkód | Analog BASIC | |---------------------------------------------------------------| | Povely blokového transferu (přenosu) | |---------------------------------------------------------------| | LDI | (DE)=(HL) | --0P0-| ED,A0 | POKE DE,PEEK(HL)| | | DE=DE+1 | | | HL=HL+1:DE=DE+1 | | | HL=HL+1,BC=BC-1| | | BC=BC-1 | | | | | | | | LDIR | (DE)=(HL) | --000-| ED,B0 | POKE DE0 THEN 10| | | | | | | | LDD | (DE)=(HL) | --0P0-| ED,A8 | POKE DE,PEEK(HL)| | | DE=DE-1 | | | HL=HL-1:DE=DE-1 | | | HL=HL-1,BC=BC-1| | | BC=BC-1 | | | | | | | | LDDR | (DE)=(HL) | --000-| ED,B8 | POKE DE,PEEK(HL)| | | DE=DE-1 | | | HL=HL-1:DE=DE-1 | | | HL=HL-1,BC=BC-1| | | BC=BC-1 | | | bis BC=0 | | | IF BC<>0 THEN 10| ----------------------------------------------------------------- Při povelech "LDI" a "LDD" ukazuje P-Flag (Parita), zda BC reg. již je na nule či nikoliv. Když BC <> 0000 je P=1 a když BC=0000 je P=0. Tyto povely jsou velice výkonné, protože paměťové buňky resp. celé paměťové rozsahy jsou transferovány bez okliky přes akumulátor. LDI při tom značí "Load and Increment" LDD "Load and Decrement" LDIR "Load Increment and Repeat" LDDR "Load Decrement and Repeat" Právě popisovaný program blokového transferu se tím dá zkrátit na jediný povel. Příklad: adresa kód povel Analog BASIC 8000: ED B0 LDIR 8000: POKE DE,PEEK (HL): HL=HL+1:DE=DE+1:BC=BC-1: IF BC<>0 GOTO 8000 8002: C9 RET 8002: RETURN str.45 Působení programu zůstává přitom exaktně stejné jako nahoře, nehledě k tomu, že po provedení jsou Flagy H, P, a N nastaveny zpět. Počítadlovou proměnnou je tentokrát registrová dvojice BC, musí se tedy napřed zadat počet průběhů. 16bitové povely plnění a ukládání --------------------------------- ----------------------------------------------------------------- | Mnemonic | Působení | Flagy | Hexkód | Analog BASIC | |---------------------------------------------------------------| | 16 ti bitové nabírací povely (Load) | |---------------------------------------------------------------| | LD BC,nm | BC=nm | ----- | 01,m,n | BC=nm | | LD DE,nm | DE=nm | ----- | 11,m,n | DE=nm | | LD HL,nm | HL=nm | ----- | 21,m,n | HL=nm | | LD SP,nm | SP=nm | ----- | 31,m,n | SP=nm | | | | | | | | LD BC,(nm)| C=(nm),B=(nm+1)| ----- |ED4B,m,n| C=PEEK (nm) | | | | | | B=PEEK (nm+1) | | LD DE,(nm)| E=(nm),D=(nm+1)| ----- |ED5B,m,n| E=PEEK (nm) | | | | | | D=PEEK (nm+1) | | LD HL,(nm)| L=(nm),H=(nm+1)| ----- | 2A,m,n | L=PEEK (nm) | | | | | | H=PEEK (nm+1) | | LD SP,(nm)| SP^L=(nm) | ----- |ED7B,m,n| SP^L=PEEK (nm) | | | SP^H=(nm+1) | | | SP^H=PEEK (nm+1)| |---------------------------------------------------------------| | 16 ti bitové povely střádací | |---------------------------------------------------------------| | LD (nm),BC| (nm)=C,(nm+1)=B| ----- |ED43,m,n| POKE nm,C | | | | | | POKE nm+1,B | | LD (nm),DE| (nm)=E,(nm+1)=D| ----- |ED53,m,n| POKE nm,E | | | | | | POKE nm+1,D | | LD (nm),HL| (nm)=L,(nm+1)=H| ----- | 22,m,n | POKE nm,L | | | | | | POKE nm+1,H | | LD (nm),SP| (nm)=SP^L | ----- |ED73,m,n| POKE nm,SP^L | | | (nm+1)=SP^H | | | POKE nm+1,SP^H | | LD SP,HL | SP=HL | ----- | F9 | SP=HL | ----------------------------------------------------------------- Působení je i zde velmi zřetelné. Pomocí "LD BC,nm" lze BC reg. přímo naplnit 16bitovou hodnotou, a sice tak, že do B reg. se uloží "n" a do C reg. "m". Povelem "LD BC,(nm)" se naplní C reg. hodnotou z paměťové buňky nm a do B reg. hodnotu z paměťové buňky nm+1. Pravý opak dělá povel "LD (nm),BC", obsah C reg. je převeden do paměťové buňky nm a z B reg. do paměťové buňky nm+1. Věc má však své záludnosti. S povely "LD SP,nm", "LD SP,(nm)" a "LD SP,HL" by se mělo zacházet velice opatrně. Těmito povely totiž plníme Stack Pointer SP, který je však stále potřebný pro CPU, aby bylo možno např. vrátit se z podprogramů zpět. Neúmyslná změna SP může tedy mít katastrofální následky a způsobit zhroucení programu. Proto byste prozatím neměli raději tyto povely vůbec použivat. str.46 Plnící a střádací povely se Stackem ------------------------------------- Těmito povely lze odkládat nebo z CPU stacku odebírat zpět obsahy registrů. Přitom je nutné přihlédnout ke struktuře stacku, která spočívá na Last-in-First-out (viz 1.2 a 4.1). Protože k těmto povelům není žádný smysl dávající analog BASIC, neukážeme také žádný v následující tabulce : ----------------------------------------------- | Mnemonic | Působení | Flagy | Hexkód | |---------------------------------------------| | Stackové povely | |---------------------------------------------| | PUSH BC | SP=SP-1,(SP)=B | ----- | C5 | | | SP=SP-1,(SP)=C | | | | PUSH DE | SP=SP-1,(SP)=D | ----- | D5 | | | SP=SP-1,(SP)=E | | | | PUSH HL | SP=SP-1,(SP)=H | ----- | E5 | | | SP=SP-1,(SP)=L | | | | PUSH AF | SP=SP-1,(SP)=A | ----- | F5 | | | SP=SP-1,(SP)=F | | | | | | | | | POP BC | C=(SP),SP=SP+1 | ----- | C1 | | | B=(SP),SP=SP+1 | | | | POP DE | E=(SP),SP=SP+1 | ----- | D1 | | | D=(SP),SP=SP+1 | | | | POP HL | L=(SP),SP=SP+1 | ----- | E1 | | | H=(SP),SP=SP+1 | | | | POP AF | F=(SP),SP=SP+1 | ----- | F1 | | | A=(SP),SP=SP+1 | | | ----------------------------------------------- Povely PUSH (odlož na stack) můžete odložit obsah reg. na stack. Obsah registru leží pak nahoře na stacku. Povelem POP (odeber ze stacku) můžete si odebrat hodnotu, která je na vrchu první a naplnit jí registr. Hodnota, která leží nahoře na stacku se někdy označuje též jako TOS (Top of Stack - první hodnota na stacku). Často se používá možnost odkládání obsahů registrů do stacku, zvlášť při delších programech, protože se velmi brzy ukáže, že CPU registry již nevystačí a je zapotřebí další registr. Pak se právě odloží jeden reg. na stack a použije se pro nový účel do té doby, až budeme potřebovat zase původní obsah registru, který si vezmeme opět ze stacku zpět. Právě tak je nutno před vyvoláním podprogramu "zachraňovat" obsahy registrů ze stacku a po návratu z podprogramu je opět ze stacku vybírat, protože většina podprogramů mění obsahy registrů. Příklad: Zadejte v test módu A=11, BC=1111, DE=1111, HL=1111, F=11111111, IX==1111, IY=1111. Teď startujte s 003E ještě jednou pípavý podprogram (Beep). Pohlédnete-li hned na registry najdete : ------------------------------------------ | A: BC: DE: HL: F: SZ-H-PNC | | 00 1111 1111 1111 01000100 | | | | IX: IY: PC: SP: | | 1111 1111 003E A000 | ------------------------------------------ str.47 Uvidíte, že obsah A a F byl podprogramem změněn. Mohou nastat dosti nepříjemné následky, když se v nějakém vlastním strojovém programu vyvolá Beep-program a neuvědomíme-li si, že se například zničí obsah akumulátoru, nebo Flag reg. Tedy když dále potřebujeme obsah A a F, napíšeme následující sekvenci : adresa kód povel 8000: F5 PUSH AF 8001: CD 3E 00 CALL 3E 8004: F1 POP AF 8005: C9 RET Tím je zaručeno, že obsah A a F zůstane zachován. Vyzkoušejte si to!! V případě častější potřeby podprogramu, je výhodné se postarat v samotném podprogramu o to, aby obsahy registrů zůstávaly pokud možno zachovány. To se dosáhne povely PUSH na začátku podprogramu a povely POP na konci podprogramu. Tím způsobem se již nemůsíme při každém vyvolání podprogramu starat o to, které registry by byly případně zničeny. Při tom se šetří čas, snižuje se pravděpodobnost programovacích chyb a zmenšuje se často též ještě pro program potřebné místo v paměti. Když odložíme více hodnot na stack, musí se dát pozor na pořadí, protože stack je zásobník Last-in-First-out, t.j. hodnoty jsou vraceny v obráceném pořadí, než byly na stack ukládány. Příklad: PUSH BC PUSH DE PUSH HL .. .. POP HL POP DE POP BC Zde se hodnoty opět naplní do registrů ze kterých byly odebrány. Jinak je v následujícím příkladě, kde obsahy registrů BC a DE byly zaměněny. Příklad: PUSH DE PUSH BC .. .. POP DE POP BC Tuto možnost zaměňovat obsahy registrů lze přirozeně též využít v dobrém smyslu. Dále nutno dbát na to, abyste "stoh" zanechali vždy tak, jak byl v původním stavu. Všechny hodnoty které byly na stack odloženy, musí taky zase vrátit zpět. Jinak se CPU při příštím povelu RET v programu dokonale rozhází. Čeká tu na vás na nejhořejším místě stacku zpáteční adresa z podprogramu (viz 5.1) a jestliže místo toho dostanete naservírován nějaký obsah registru, skočí na nějakou nesmyslnou adresu. str.48 5.3 Aritmetické povely ---------------------- Teď přicházíme k početním operacím, které můžete se Z80 provádět. Naprvní pohled je člověk normálně zklamán, jak málo takový 8bitový procesor vůbec umí. Na druhý pohled pak vidíme co všechno se s těmito povely dá provádět. Sčítací povely -------------- ----------------------------------------------------------------- | Mnemonic | Působení | Flagy | Hexkód | Analog BASIC | |---------------------------------------------------------------| | 8mi bitové sčítání | |---------------------------------------------------------------| | ADD A,A | A=A+A | SZHP0C| 87 | A=A+A | | ADD A,B | A=A+B | SZHP0C| 80 | A=A+B | | ADD A,C | A=A+C | SZHP0C| 81 | A=A+C | | ADD A,D | A=A+D | SZHP0C| 82 | A=A+D | | ADD A,E | A=A+E | SZHP0C| 83 | A=A+E | | ADD A,H | A=A+H | SZHP0C| 84 | A=A+H | | ADD A,L | A=A+L | SZHP0C| 85 | A=A+L | | ADD A,n | A=A+n | SZHP0C| C6,n | A=A+n | | ADD A,(HL)| A=A+(HL) | SZHP0C| 86 | A=A+PEEK (HL) | | | | | | | | ADC A,A | A=A+A+C | SZHP0C| 8F | A=A+A+C | | ADC A,B | A=A+B+C | SZHP0C| 88 | A=A+B+C | | ADC A,C | A=A+C+C | SZHP0C| 89 | A=A+C+C | | ADC A,D | A=A+D+C | SZHP0C| 8A | A=A+D+C | | ADC A,E | A=A+E+C | SZHP0C| 8B | A=A+E+C | | ADC A,H | A=A+H+C | SZHP0C| 8C | A=A+H+C | | ADC A,L | A=A+L+C | SZHP0C| 8D | A=A+L+C | | ADC A,n | A=A+n+C | SZHP0C| CE,n | A=A+n+C | | ADC A,(HL)| A=A+(HL)+C | SZJP0C| 8E | A=A+PEEK (HL)+C | ----------------------------------------------------------------- Symbolika (mnemonic) těchto povelů se vysvětluje následovně: "ADD" znamená prostě sečti, "ADC" znamená (Add with carry, česky sečti s přenosem). Příklad: adresa kód povel Analog BASIC 8000: 83 ADD A,E 8000: A=A+E 8001: C9 RET 8001: RETURN Pomocí tohoto krátkého povelového sledu se obsah reg. E přičte k akumulátoru a je v akumulátoru uložen. Vyzkoušejte si to s učebním souborem tak, že zadáte např. DE=0005 a A=03 a startujete program. Následně má A hodnotu 08. Pozorujete-li působení povelu ADC, budete se nejprve ptát, proč je obsah Carry Flagu C (tedy 0 nebo 1) vždy spolu sečten. Ale přičítání Carry Flagu je rafinovanou záležitostí, tím totiž lze při sečítání vícemístných čísel přenos přesunout z jednoho byte na další (viz 1.5). A to se děje následovně : Při sečítání dvou 8bitových hodnot, jak tomu je v právě uvedeném příkladu, se může vyskytnout přenos na 9.bit, např. když se budou sečítat následující hodnoty. str.49 Příklad: hexadecimál decimál binár DE=FF 255 11111111 A =05 5 00000101 Výsledek součtu je pak exaktně: 104 260 100000100 Akumulátor může však pojmout pouze 8 bitů a obsahuje po součtu: A=04 4 00000100 Na dvojkovém znázornění vidíte, že v akumulátoru byla právě první 1 přehozena. Tato 1 stojí po součtu v Carry Flagu. Tuto skutečnost teď můžeme použít abychom buď signalizovali chybu přeběhnutí podmíněným skokovým povelem k běžnému postupu při chybě, nebo abychom tuto 1 v Carry použili k přenosu na další místo. Poslední pak jde automaticky s povelem ADC, protože při sčítání dalších míst se Carry přece automaticky přidává. Následující program sečítá registr DE s reg. HL a ukládá výsledek do registru HL (HL=HL+DE). Příklad: adresa kód povel Analog BASIC 8000: 7D LD A,L 8000:A=L 8001: 83 ADD A,E 8001:A=A+E 8002: 6F LD L,A 8002:L=A 8003: 7C LD A,H 8003:A=H 8004: 8A ADC A,D 8004:A=A+D+C 8005: 67 LD H,A 8005:H=A 8006: C9 RET 8006:RETURN Vidíte že stejně jako u písemného sečítání se začíná od zadu, sčítají se nejdříve L byty. Jako další se pak sečtou H byty, přičemž se zohledňuje přenos z L bytu, protože stojí ještě v C Flagu. Vyzkoušejte si program, že zadáte HL=00FF a DE=0005. Najdete pak správný výsledek HL=0104. ----------------------------------------------------------------- | Mnemonic | Působení | Flagy | Hexkód | Analog BASIC | |---------------------------------------------------------------| | 16ti bitový součet | |---------------------------------------------------------------| | ADD HL,BC | HL=HL+BC | --H-0C| 09 | HL=HL+BC | | ADD HL,DE | HL=HL+DE | --H-0C| 19 | HL=HL+DE | | ADD HL,HL | HL=HL+HL | --H-0C| 29 | HL=HL+HL | | ADD HL,SP | HL=HL+SP | --H-0C| 39 | HL=HL+SP | | | | | | | | ADC HL,BC | HL=HL+BC+C | SZHV0C| ED,4A | HL=HL+BC+C | | ADC HL,DE | HL=HL+DE+C | SZHV0C| ED,5A | HL=HL+DE+C | | ADC HL,HL | HL=HL+HL+C | SZHV0C| ED,6A | HL=HL+HL+C | | ADC HL,SP | HL=HL+SP+C | SZHV0C| ED,7A | HL=HL+SP+C | ----------------------------------------------------------------- str.50 Zde je symbolika stejná jako u 8bitových součtových povelů, avšak sečítají se 16-ti bitové hodnoty k HL registru. 16bitové součtové povely se používají hlavně při přepočtu adres, lze je však právě tak dobře použít pro součet větších binárních čísel. Shora uvedený součtový program vypadá s 16bitovými součtovými povely takto : Příklad: adresa kód povel Analog BASIC 8000: 19 ADD HL,DE 8000: HL+DE 8001: C9 RET 8001: RETURN Součet negativních čísel ------------------------ Zadejte HL=0109 a DE=FFFF a startujte ještě jednou poslední program. V HL najdete teď hodnotu 0108, protože výsledek teď přesahuje 16 bitů. Však pohleďte blíže na výsledek. Účinek součtu FFFF je patrně tentýž jako při odečtu 1: 0109-1=0108. Zadejte DE=FFFE, hodnotu pro HL nechte stát a startujte znovu. Najdete výsledek HL=0106, tedy subtrakce od 2. Tento sled věcí se využívá při znázorňování negativních 16bitových čísel. Vezmeme místo 1 hodnotu FFFF, namísto 2 hodnotu FFFE, namísto 3 hodnotu FFFD,.... Tento způsob se nazývá komplementární dvojice (viz dodatek A3). Získáme komplementární dvojici 16bitového čísla tím, že se toto číslo odečte od hex 10000: -9 je v komplementární dvojici 10000-9=FFFF7. Normálně se rozděluje rozsah u 16bitových čísel s předznaménkem takto : 0000 až 7FFF Odpovídá pozitivním číslům 0 až 32767 FFFF až 8000 Odpovídá negativním číslům -1 až -32768 Přesně stajný trik lze použít u 8bitových hodnot : 00 až 7F Odpovídá 0 až 127 FF až 80 Odpovídá -1 až -128 Na tomto způsobu vyjadřování čísel je praktická ta skutečnost, že u všech součtů ve strojovém jazyce zohledňuje automaticky správné předznaménko. Teď rozumíte také funkci V Flagu. Tento Flag je nastaven, když se při součtu čísel s předznaménkem překročí hodnotový rozsah. Když např. k 7F se přičte 1, což dává výsledek 80. Jednalo-li se však o čísla s předznaménkem, byla k 127 přičtena 1, což dává 128. 128 však není jako 8bitové číslo s předznamenáním již vytvořitelné, výsledek 80=-128 je nesmysl. A právě toto hlásí V Flag CPU, pročež je také nazýván "přetokoý flag" (overflow). str.51 32-bitový součet ---------------- Po této exkurzi k negativním číslům, chceme vám představit ještě program sčítání 32 bitových čísel. Rozsah hodnot výsledku je již přitom mezi 0 a 4,294,967,295. Sečítaná čísla mají být ukládána v rozsahu adres 8100...8103 a 8104...8107, výsledek součtu pak ukládán do 8100...8103. Čísla obsazují tedy vždy 4 byty. Teď vám neudáme žádný Analog BASIC, nýbrž raději budeme program komentovat. Příklad: adresa kód povel komentář 8000: A7 AND A vysaď Carry=0 8001: 11 03 81 LD DE,8103 1.číslo a výsledek 8004: 21 07 81 LD HL,8107 2.číslo 8007: 06 04 LD B,04 počet bytů 8009: 1A LD A,(DE) 1.číslo nabrat 800A: 8E ADC A,(HL) 2.číslo sečíst 800B: 12 LD (DE),A výsledk uložit 800C: 1B DEC DE 1.číslo na příští byte 800D: 2B DEC HL 2.číslo na příští byte 800E: 10 F9 DJNZ 8009-$ opakovat od 8009 8010: C9 RET Je sčítáno ve smyčce mezi 8009 a 800F. Napřed se nabírá jeden byte druhým číslem, pak jeden byte prvního čísla je sečten a výsledek uložen ve stejném místě. Potom se sníží pointerreg. DE a HL. Aby se na začátku současně nasečetl žádný přenos, stojí povel AND A na začátku programu. Nechává akumulátor beze změny a nastaví pouze Flagy (Carry=0). To je nutné, protože Z-80 nemá žádný povel CLC (Clear Carry). Nejlépe bude, když si vyzkoušíte program a projedete průběh po jednotlivých krocích. Proto vložte například do pamětových buňek 8100...8103 číslo 11 22 33 44 a do buňek 8104...8107 číslo 99 AA BB CC. Nakonec najdete výsledek v paměťových buňkách 8100...8103 : AA CC EF 10. Teď také vidíte, proč je tak důležité, že povel DJNZ neovlivňuje Carry flag. V programové smyčce se provádí v Carry flagu přesun přenosu při součtu na příští. Korektura BCD ------------- Zatím jsme pracovali pouze s dvojkovými čísly, naše CPU ale umí taky provádět součty decimálně. Při tom používá vyjádření BCD (Binary Coded Decimal, česky dvojkově kódované decimální). ----------------------------------------------| | Mnemonic | Působení | Flagy | Hexkód | |---------------------------------------------| | DAA | přizpůsob A | SZHP-C| 27 | | | decimálně | | | ----------------------------------------------- DAA znamená "Decimal adjust Accumulator, česky přízpůsob akumulátor decimálně". Tento povel musí být řazen bezprostředně po sčítacím nebo odečítacím povelu, protože se dotazuje na N- a H- Flag. V BCD systému se decimální čísla znázorňují následovně (viz dodatek A3). Pro každou decimální cifru se použije 4 bity, tedy půl bytu. V těchto čtyřech bitech se cifry 0...9 vyjadřují zcela normálně jako dvojková hodnota, tedy např. 9 jako 1001. Rozdíl vůči dosud užívanému šestnáctkovému vyjádření spočívá v tom, že hodnoty A...F jsou zakázané a jsou dovoleny pouze 0 až 9. Nejvyšší hodnota v jednom byte je přitom 99 a nikoli již FF. Transport přenosu pomocí Carry bitu funguje právě tak jako u dvojkových čísel. str.52 Součet 8mi místných čísel ------------------------- Protože všechno probíhá jako při dvojkovém součtu, můžete vzít poslední příklad (32bitový součet) a z toho udělat program na sčítání 8mi místných decimálních čísel. K tomu se jen musí vložit po povelu ADC povel DAA. Program pak vypadá takto: Příklad: adresa kód povel komentář 8000: A7 AND A nasdtav Carry=0 8001: 11 03 81 LD DE,8103 1.číslo a výsledek 8004: 21 07 81 LD HL,8107 2.číslo 8007: 06 04 LD B,04 počet bytů 8009: 1A LD A,(DE) 1.číslo nabrat 800A: 8E ADC A,(HL) 2.číslo sečíst 800B: 27 DAA BCD-přizpůsobení 800C: 12 LD (DE),A výsledek uložit 800D: 1B DEC DE 1.číslo na příští byte 800E: 2B DEC HL 2.číslo na příští byte 800F: 10 F8 DJNZ 8009-$ opakuj od 8009 8011: C9 RET Vložíte-li pak do 8100...8103 číslo 12309999 a do 8104...8107 číslo 30000001, najdete po proběhnutí programu výsledek v 8100. ..8103 : 42310000. Vidíte zřetelně, že bylo sčítáno decimálně protože jinak byste nalezli výsledek 4230999A.