Valószínűleg már kezdenek unalmassá válni a hellók, és az A betűk, csináljunk valami izgalmasabbat!
Van már működő Turbo Assemblerünk, így mindenki töltse be, indítsa el, és gépelje be a lenti kis szösszenetet.
Miután mindenki kiszörnyülködte magát, hogy mi is ez, lássuk lépésenként.
- LDA, LDX ... Az A legyen nulla, és X legyen $FF. Világos.
- STA ... a (képernyőram kezdete + X) -edik byteba írjuk ki ezt a nullát. Ahem, semmi extra, megjelenik majd ott egy karakter, valahol a 7. sorban ( 6*40 + 15 = 255). A nullás karakter az a kukac lesz -> @
- DEX ... Csökkentsük az X értékét. Ez is oké...
- BNE, vagyis ugrás vissza a "torol" címkéhez, ami majd megint kiír egy karaktert, megint csökkent, és így tovább
De honnan tudja a BNE, hogy most neki mikor is kell ugrania, és ezt meddig csinálja? Vegyük át újra ezt a 2 utasítást.
- A BNE akkor ugrik, ha a státuszregiszter Z (Zero) bitje 0
- A BEQ akkor ugrik, ha a státuszregiszter Z (Zero) bitje 1
Az előző ciklusainknál a CPX parancs az összehasonlításnál azt csinálta, hogy kivonta X-ből a megadott számot. Ha X = szám, akkor a kivonás eredménye nulla. Ha valamilyen aritmetikai művelet eredménye nulla, akkor a proci automatikusan beállítja a státuszregiszter Z (Zero) bitjét 1-re, hogy ezzel jelezzen nekünk.
Hátha így könnyebb:
CPX #<szam> ==> if ( X == szam ) then Z=1 else Z=0
Ezt a státuszbit állítgatós dolgot minden számítási művelet elvégzi, így a DEX is. Hiszen mit csinál a DEX? Kivon egyet az X regiszter tartalmából, és azt visszateszi X-be.
Ha ennek a műveletnek az eredménye nulla... akkor a Z bit 1 re állítódik!
Tehát egészen addig ugrálunk vissza, amíg a Z bit 0. Ha 1 lesz az értéke, akkor az azt jelenti, hogy az X regiszter jelenlegi tartalma nulla. Így hát nem ugrunk vissza, és a következő utasításnál folytatódik a program végrehajtása, ami egy RTS. Vagyis kilépünk.
Miért jó ez nekünk? Mert ha $FF-től megyünk lefelé $00-ig, akkor megspórolunk egy összehasonlítást, egy CPX-et. Kisebb kód, boldogabb kóder. Meg azért, hogy lássunk ilyet is. ;)
Na nézzük, mit művel a programunk.
Nem túl szép, de legalább (majdnem) a várt eredményt hozta. A program lefutott, aztán a BASIC parancsértelmező persze belerondított a kimenetünkbe a hülye "ready"-jével, de ezzel mi most nem törődünk.
Viszont van egy kis bibi, a bal felső sarokba már nem jutott a kukacból. Hogyan is lehet ez?
Vegyük át szép sorban. Tegyük fel, X folyamatosan csökken és most már X=1:
- Az STA kidobja $0400+1 -re a kukacot
- Csökkentjük X-et => 0 lesz
- A BNE így már nem ugrik vissza, és a programunk végetér
Nincs STA, ami kirakná $0400-ra a kukacot. Nosza, gondolhatnánk, rakjunk a BNE után egy STA $0400-at, és probléma megoldva. Az ötlet nem rossz, de az még egy plusz utasítás. Lehet ezt rövidebben is.
8 bites regiszterek
Sajnos egy kicsit bele kell bonyolódni a bináris számábrázolás mélységeibe. Vegyünk pár 10-es számrendszerbeli számot és nézzük meg bináris megfelelőjüket
0 | 00000000 |
1 | 00000001 |
2 | 00000010 |
3 | 00000011 |
... | ... |
255 | 11111111 |
8 biten pont ennyi számot vagyunk képesek tárolni. 0 és 255 között. Mi történik, ha a 255-höz hozzáadunk még 1-et? 256 lesz az eredmény, ami binárisan:
... | ... |
255 | 11111111 |
256 | 1 00000000 |
Hoppá, 9 bitre lenne szükségünk hozzá, de nekünk csak 8 darab van. És mi történt a meglévő 8-al? Látható, mind nulla lett, a 9. bitet pedig mivel nem tudjuk hova eltárolni, az gyakorlatilag elveszett. (Esetünkben most épp)
Így a 255 + 1 összeadás ereménye túlcsordul (overflow), és 0 eredményt ad. Hasonló módon a 0 - 1 kivonás pedig alulcsordul, és 255 lesz az eredmény.
A kivonást nem vesszük most itt át részletesen, mert feleslegesen bonyolítanánk az életünket.
Ez a gyakorlatban azt jelenti, hogy egy regiszter, vagy memóriacím tartalmát egy sima összeadással, vagy kivonással folyamatosan "körbe-körbe" tudjuk pörgetni 0-255 között. Nem akad ki, nem kapunk hibaüzenetet, csak a számok alul, vagy felülcsordulnak.
Ezt a tulajdonságukat kihasználva, nézzük meg mi történik, ha módosítjuk a programunkat, és átírjuk a kezdő
LDX #$FF -et LDX #$00-ra?
Minden más maradt a régiben. Elvileg most úgy indul a programunk, hogy X=0, vagyis az STA kiírja $0400-ra a kukacot. Hopp, ez az egy maradt ki az előbb!
Aztán a DEX csökkenti X-et, és a fentiek szerint alulcsordul, vagyis most X=$FF. A BNE visszaugrik a ciklus elejére, hiszen X nem nulla (hanem $FF). Innentől ugyanúgy fut a ciklusunk, mint mikor $FF-ről indítottuk.
És kaptunk kereken 256 darab kukacot. (Aki szorgalmas, piros pontért megszámolhatja) Nagyszerű.
De mi is a cél? Mit akarunk ezzel elérni? Én személy szerint szeretném feltölteni az egész képernyőt kukacokkal. De van egy kis bibi.
A képernyő 40x25 = 1000 byte, mi pedig csak 0-255-ig tudunk elszámolni. Kellene valami trükk.
Jelenleg $0400-$04FF között töltöttük fel a kukacokkal a memóriát. Ahhoz hogy $0500-tól folytassuk, csinálhatunk akár egy ilyet is:
Hiszen semmi sem tiltja, hogy a cikluson belül egyszerre írjuk ki $0400+X -re, és $0500+X-re a kukacot. Valamint az assembler ismeri a matematikai műveleteket, majd szépen kiszámolja, hogy mennyi az annyi.
Futtassuk:
Az eredmény jól láthatóan siker. $0400-$05FF-ig csak kukac, pontosan 2x256 darab. Ezen felbuzdulva adjunk hozzá még két sort a programunkhoz.
Így már a ciklusban a képernyőre egyszerre 4 helyen írjuk ki a kukacot, majd lépdelünk visszafelé.
A végeredmény teljesen lenyűgöző, mindenhol kukac!
Itt ismét egy kis kitérő. Azzal már tisztában vagyunk, hogy $0400-tól van a képernyő ram. Azt viszont még nem tudjuk, hogy $0800-tól kezdődik a BASIC RAM, ahol a BASIC programok tárolódnak.
Ha kiszámoljuk, látható, hogy mi most pont 4x256 vagyis 1024 byteot írtunk a képernyő memória kezdetétől. Így a legutolsó byte, ahova kiírtuk a kukacot az a $07FF.
Viszont a képernyő ram az csak 1000 byte. Utána következik egy 24 byteos "lyuk", és ezután kezdődik a BASIC RAM. Viszont ebből a "lyuk"ból az utolsó 8 byteot a grafikus chip felhasználja bizonyos célokra. Esetünkben ez most nem jelent gondot, mert még nem használtunk olyan funkciókat, amik elromolhatnának emiatt.
De érdemes észben tartani, hogy a videoram kereken 1000 byte, és 1000-nél több karaktert nem érdemes kiírnunk, ha nem szeretnénk meglepetéseket :)
Teli van hát a képernyőnk, jaj de csodás. Viszont ez nem túl izgalmas, teleírtuk kukaccal. Mi van akkor ha más karakterekkel szeretnénk telepakolni? A válasz egyszerű, módosítjuk az első sorban az LDA utáni értéket.
Nyugodtan próbálkozzunk vele, írjuk át bármilyen tetszőleges számra, érdemes kipróbálni mi történik, ha $80-nál nagyobb értéket írunk be. :)
Csavarjunk egyet a történeten, írjuk át úgy a programot, hogy a kiírásra szánt karakter kódját egy memóriacímről olvassa be.
Csakúgy, ahogyan az STA, az LDA is képes abszolút címzésre, vagyis az LDA $xxxx egy adott memóriacím tartalmát tölti be az A-ba.
Módosítsuk a programunkat az alábbiak szerint:
Mi is változott?
A program végén megadtunk egy "karakter" címkét, mögötte pedig egyetlen byte-ot "kézzel". A ".byte" egy megadott számot tárol majd a memóriában. Jelenleg a $01-et, pont az RTS utáni címen. Nem kell aggódnunk miatta, hiszen a programunk futása az RTS-nél mindig végetér, így nem futhat "zátonyra" a végrehajtás.
Az első sorban az LDA #$00 helyett pedig most egy LDA $memóriacím van valójában, ami a "karakter"-re mutat. Ahogyan a programunk növekszik méretben, a "karakter" mindig az RTS utáni byte-ot fogja jelenteni. Szerencsére nekünk a számokkal most sem kell törődnünk, mindent intéz az assembler.
Lefuttatva szép, A-kkal teleírt képernyőt kapunk. Itt szintén eljátszhatunk, hogy átírogatjuk a .byte utáni értéket. Az eredmény ugyanaz lesz, mint előzőleg a direkt LDA paraméter változtatással.
Igen ám, de itt már belevihetünk egy kis trükközést, hogy mókásabb legyen a dolog:
A program elejéhez adjunk hozzá egy "eleje" címkét, így később vissza tudunk ugrani ide.
Az RTS-t cseréljuk ki az INC, és a JMP utasításokra.
INC $xxxx - Increment
Az INC parancs nagyon egyszerű, a megadott memóriacím tartalmát növeli 1-el. (Párja a DEC pedig csökkenteni tud)
Ez most a "karakter" által mutatott byte-unk memóriacíme, aminek az értékét megnöveljük. Majd a JMP visszaugrik a program elejére. Ahol az LDA beolvassa A-ba a memóriacím tartalmát, és megy minden a megszokott módon.
Mivel nincs RTS, már megint van egy végtelen ciklusunk. PageUp-pal ugyanúgy visszatérhetünk az assemblerbe.
A programunk egy érdekes, villódzó képernyőt produkál, ahol a karakterek pörögnek szépen sorban egymás után.
Ha kireseteljük a gépet Alt+F9-el, a memória tartalma nem törlődik, így nyugodtan megleshetjük a lefordított programunkat a monitorprogrammal.
Úgy tűnik az assembler mindent jól csinált. Szerencsétlenségemre mikor megnyomtam a resetet, a "karakter" értéke éppen $4C volt, ami pont a JMP gépi kódú megfelelője. Így a disassembler megpróbálja JMP-ként kiírni. Természetesen a mögötte lévő 2 byte az $A7 és a $50 már csak valami maradék szemét a memóriából.
Végszóként
Lehet néhányan elgondolkodtak azon, hogy miért nem növeltük az A értékét, pl az INX utasításhoz hasonló módon, ami az X-et növeli 1-el.
Mert nem lehet.
Sajnos nincs ilyen parancs, ami az A-t növeli 1-el, vagy csökkenti. Össze lehet adatni az A tartalmát pl egy megadott memóriacímmel. (Ahol mondjuk egy 1-es értéket tárolunk) De egyszerűbbnek éreztem ezen a módon megmutatni :)
Nem meglepő módon például ilyen sem létezik: LDX $xxxx,A vagy STX $xxxx,A
A bejegyzés trackback címe:
Kommentek:
A hozzászólások a vonatkozó jogszabályok értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a Felhasználási feltételekben és az adatvédelmi tájékoztatóban.
tájbor1001110 2021.12.07. 23:49:55
tájbor1001110 2022.09.15. 11:20:22
Megpróbáltam a saját, számomra 'egyértelműbbnek' tűnő logikával, miszerint nem csökkentem, hanem növelem az X értékét:
(Nyilván van valamilyen előnye az X csökkentésének, gyorsabb vagy akármi miatt jobb.)
init
lda char
ldx #$00
...
sta scr,x
sta scr+$0100,x
sta scr+$0200,x
sta scr+$0300,x ;ezt már nem bírja el...
inx
bne cycle
inc char
jmp init
...
A kód lefut, egészen addig amíg hozzá nem adom az scr+$0300,x sort!
A fordítás nem dob hibát, gondoltam az utolsó 255byte írásakor már valamit csak felülírok a memóriában... Aztán bepötyögtem a Te kódodat (ugyanazokkal a label-ekkel/konstansokkal) és ugyanaz az eredmény, a kód lefordul de nem fut le, a felső sor valahanyadik oszlopába rak egy "!" karaktert és ennyi...
Hol lehet a bukfenc?
tájbor1001110 2022.09.15. 11:23:54
"A fordítás nem dob hibát..."
Felesleges megjegyzés. Nyilván nem, hiszen szintaktikailag nincs a kódban hiba. (:
Más: Nem lesz folytatása az assembly tutorialnak?
tájbor1001110 2022.09.15. 11:32:48
Dey · http://c64rulez.blog.hu 2022.09.15. 11:47:33
Előszedek egy emut, és ránézek majd.
Hát... Lehet folytatás, ha ekkora az igény rá :)
tájbor1001110 2022.09.15. 13:18:56
Persze, van igény. Szvsz ez egy jó leírás volt. Tetszett az is pl. hogy nem CBM studio-t meg kick assemblert használsz. Nem kell semmi csili-vili, a nyers vason szeretném megtanulni aztán majd később lehet lazázni. Jó lenne pl. látni azt is hogy hogyan lehet lebegőpontos számokat kezelni (nem a basic rutinjával), persze ezt csak szorgalminak, valamikor a végén.