Eljött végre a pillanat, amit mindenki annyira várt! Rendhagyó módon mi most nem egy HELLO WORLD-el kezdünk, hanem valami sokkal látványosabbal. Ami nem mellesleg sokkal egyszerűbb is, mint egy Hello World, valamint segít megalapozni a továbbiakat.
Izzítsuk be az emut, tehénkedjünk az Action Replay menüjében az F7-re (fastload), és írjuk be az alábbi parancsot:
POKE 53280,13
Az enter leütése után valami csoda történik, és a kék keret világoszöldre vált. Miféle gonosz mágia lehet ez? És mi a fene az a POKE?
A POKE működése pofonegyszerű, a megadott memóriacímre beírja a megadott számot. Esetünkben a cím a memória 53280-dik byteja, a beleírt érték pedig a 13. Így tudjuk könnyedén megváltoztatni a memória bármelyik területének a tartalmát.
Honnan jönnek ezek a számok és mit jelentek?
Gépünkben van egy grafikus chip, a VIC-II, ami a látott kép megjelenítését végzi. Kipakolja a képernyőre a betűket, színeket, és mindent. Egyszerűsége ellenére mégis nagyszerű, és viszonylag könnyen vezérelhető. Gondolom már sejthető, hogy ezzel a POKE-al valamiféle utasítást adtunk neki az előbb.
Ez a chip is rendelkezik regiszterekkel, amiken keresztül a működését lehet befolyásolni. Elég sok van neki, szám szerint 46 darab. Ezek a regiszterek csúnya szóval élve, "be vannak fűzve" a C64 memóriájába, vagyis egy adott memóriacím írásával/olvasásával, egy adott regisztert tudjuk piszkálni. A regiszterek írásával/olvasásával a chip által generált képet tudjuk kedvünk szerint manipulálni. Vagyis itt már kezdünk belecsúszni a korábban említett hardverközeliségbe, hiszen közvetlenül a vasat piszkáljuk.
Ez a 46 regiszter a memóriában az 53248 és 53294 közötti memóriaterület. A fent "megpókolt" 53280 az bizony pont a keret színéért felelős regiszter, amibe benyomtuk a 13-as számot, ami a világoszöldet jelenti.
A VIC sok tekintetben rettenetesen faék, és kőbuta. Agyatlanul minden egyes képernyőfrissítéskor az itt tárolt színkódnak megfelelő színnel fogja kirajzolni a keretet. Ha beírunk egy másik értéket ugyanide, akkor a számnak megfelelő színre vált. Nem kérdez, nem ellenőriz, nem rinyál, és nem dob hibaüzenetet semmire.
Don't panic!
Nem kell beszarni, ez a sok hülye szám elsőre ijesztő lehet, de természetesen, mint minden rendes programozónak, nekünk is vannak referenciáink. A regiszterek listája, és a színek számkódjai is mind megtalálhatóak a VIC-II Wikipédia oldalán. Meg még persze 1000 más helyen a neten :)
A későbbiekben természetesen elkezdünk majd használni mindenféle emberközelibb fejlesztőeszközöket, és egyebeket, amik majd megkönnyítik a dolgunkat, de szeretném az alapoknál kezdeni, hogy lehetőleg mindenki értse is azt, amit csinál. :)
Aki megnézte a linkelt Wiki oldalt, annak feltűnhetett, hogy sehol sincs 53280, viszont a 32. regiszternél vagyis "border color"-nál egy D020 áll.
Nem megyek most bele a kettes (bináris), és tizenhatos (hexadecimális) számrendszer mélységeibe, akit érdekel, talál rengeteg infót róla a neten. Elégedjünk meg most annyival, hogy a BASIC mindig a jól ismert 10-es számrendszert használja, mi viszont a 16-ost fogjuk majd javarészt a jövőben. A doksik is mind ezt használják, szóval elkezdünk átszokni rá. Aki szeretné, kipróbálhatja ezen az átváltó oldalon, hogy a D020 hexadecimális szám, az biza 53280 a 10-es (decimális) számrendszerben. A későbbiekben a hexadecimális számokat dollárjellel fogjuk jelölni, vagyis $D020.
Ha már eddig eljutottunk, jöjjön egy kis játék, állítsuk át a belső kék rész színét is, mondjuk sötétzöldre. A wiki oldalon kikereshetjük a "Background color 0" regiszter címét, ami $D021, valamint a sötétzöld számkódját, ami 5.
Mivel még BASIC-nél tartunk, a hexa $D021 decimális megfelelője az 53281, az 5 meg értelemszerűen ugye simán 5 😄
A végeredmény ez lesz:
Hoppáhoppá, vezéreljük a grafikus chipet! Közvetlenül a vasat, a hardvert! Hát nem nagyszerű? Természetesen ennél jóval menőbb dolgokra is képes a masina, de most még ne rohanjunk annyira előre. Egy elefántot is csak falatonként lehet megenni 😉
Próbáljuk ki a következő BASIC programot. (Én most kireseteltem a gépet Alt+F9-el a screenshot kedvéért, és amiatt kék újra minden)
Jelenleg már megfelelő tudással rendelkezik mindenki, hogy magától rájöjjön, hogy itt mi bizony gyorsan változtatjuk majd a keret színét a világoszöld (13), és a világoskék (14) között. A RUN beírása után mindenki egy standard villogásra számít majd...
ÉS NEM! Az eredmény sokkal meglepőbb!
Érdekes módon valamiféle fura, mozgó csíkozást kapunk. Ennek oka, hogy a VIC másodpercenként 50szer rajzolja ki a teljes képet, elkezdve a bal felső sarokból, és szépen soronként lefelé végigmegy. Mivel a BASIC programunk gyorsabb, mint amennyi idő kell egy ilyen teljes kép kirajzolásához, a rajzolás közben valahol a képernyő közepén már átváltja a keretszínt, majd később pedig vissza. Mire végez a teljes kép frissítésével mi menet közben többször is megváltoztatjuk a színt, amivel dolgoznia kell.
Gondolom ismerős PC-s játékokból a kikapcsolt VSync-nél jelentkező screen tearing. Az elv ugyanaz ott is. A GPU elkezdi kirajzolni a monitorra a képet, de az a rajzolás közben valahol megváltozik, és az új, következő frame-et rajzolja tovább, így a képben keletkezik egy szemmel látható törés.
A grafikus chipünk "buta", őt nem érdekli, hogy van-e értelme az adott feladatnak, csupán végrehajtja. Ha éppen a képernyő kirajzolásának a közepén váltjuk át a keret színét... Akkor ő bizony gondolkodás nélkül rajzolja tovább, immár az új színnel. Aki látott már C64-et, annak ismerős lehet ez a keret villogás, csinálunk is majd belőle érdekes dolgokat 😉
Az igazán vájtszeműek még azt is kiszúrhatják a képemen, hogy éppen hol tartott a VIC a kép kirajzolásával, mikor éppen átváltott a program kék színről zöldre. (Jobb szélen, a kereten van egy "törés")
Ha megállítjuk a programot ESC-el, akkor viszont a villogás abbamarad, és vagy kék, vagy zöld keretet kapunk, mivel az adott regiszterben lévő számérték már nem változik többé. Igy a chipünk mindig ugyanazzal a színnel rajzol majd. Másodpercenként 50-szer.
Hol van már az assembly???
Oké, rendben, jöjjön hát a lényegi rész.
Az Action Replay-ünknek egy nagyon jó kis bővítménye, a "Gépi kódú monitor"-nak, vagy csak simán monitornak nevezett program. Ezzel megnézhetjük, és átírhatjuk direktben a memória bármilyen szegletét. Emellett van benne egy egyszerű assembler is, ami nekünk most nagyon jól jön. (Igen, van a VICE-ban is beépített monitor, de attól hülyét kapok, sry)
Írjuk hát be: MON és csapjunk az enterre: (Én már megint reseteltem, hogy átláthatóbb legyen a dolog a képen, de természetesen nem muszáj)
Indításkor kiír mindenfélét, de mi ezzel most nem foglalkozunk.
Írjuk meg hát az első assembly programunkat, ami pontosan a fenti képernyővillogtató lesz. Először pötyögjük be, aztán rátérünk, hogy mit és hogyan csinál. (Innentől minden szám hexadecimális, akár áll előtte dollárjel, akár nem!)
Ha beírjuk, hogy "A 1000 LDA #$0D" és leenterezzük, valami láthatóan történik:
Hát ez átírta a sorunkat! Majd a következő sor elejére kiírta, hogy 1002:
Mi is történt? Az "A" (mint assemble) parancs mondja meg a monitor programnak, hogy mi most bizony assembly kódot fogunk beírni neki. Példánkban az 1000 azt a memóriacímet jelöli, ahová el szeretnénk helyezni a parancsunkat. (Ez már hexa 1000 vagyis $1000) Természetesen teljesen más címet is választhattam volna, hiszen van 64kb szabad RAMunk 😉
Sejthetően az LDA #$0D pedig már végre valahára valamilyen assembly kód lesz...
Pontosan így van, az LDA (LoaD to A) utasítás be tud tenni egy byte-ot a processzor A regiszterébe. (Itt esetleg vissza lehet ugrani az előző postokhoz, hogy mi is az a regiszter) Jelen esetben belerakunk egy hexa $0D értéket (ami decimálisan 13, a világoszöld). A kettőskeresszttel azt mondjuk meg, hogy mi itt most direktben kézzel megadunk egy konkrét számot. (Lesznek más jelölések is később, például a memória egy tetszőleges helyéről is beolvashatunk akár egy byte-ot.)
Mikor entert nyomunk, akkor viszont a monitor (értsd: monitorprogram) átszerkeszti a sort, és belekerül egy "A9 0D" a parancs elé. Ez valójában az utasításunk lefordított, gépi kódú megfelelője. Ugye mindenki emlékszik, a processzor csak és kizárólag számokat tud értelmezni. Az A9 valójában az "LDA #", a mögötte álló "0D" pedig az érték, amit megadtunk.
Az assembler megkönnyíti az életünket, mert nem kell hülye számokat megjegyeznünk. Valószínűleg az A9-et holnapra mindenki elfelejti, viszont az LDA-t ha 2 nap múlva meglátod, akkor be fog ugrani róla, hogy micsoda.
És mivel extra jó fej, az assembly parancsunk értelmezése, és tárolása után nagyon előzékenyen kiírja nekünk a következő sor elejére az "A 1002"-t, vagyis azt memóriacímet, ahová a következő utasítást beírhatjuk. Nem kell számolgatnunk, hogy az LDA #$0D az pont 2 byte hosszú, ezt mind intézi helyettünk.
Pötyögjük be a maradékot, és nézzük meg egyben, majd értelmezzük: (Ha valamit elgépelünk, és az assembler nem érti, egy ?-et fog kiírni. Ilyenkor nyugodtan gépeljük be újra a parancsot a következő sorba, vagy menjünk fel a kurzorral, javítsuk, és csatt rá egy enter. Ugye fullscreen editorunk van, pont mint BASIC-ben ugyanúgy működik a programsorok módosítása 😄😄
Itt már újabb utasításokat is felfedezhetünk, és szépen látható, hogy ezek milyen módon tárolódnak a memóriában.
Az STA (STore A) kb az LDA fordítottja, az A regiszter tartalmát kiírja valahova. Esetünkben most egy megadott konkrét memóriacímre, ami a már ismerős $D020. Tehát a keret világoszöldre vált majd.
Az alatta lévő két sor ugyanezt csinálja, beteszünk A-ba egy $0E értéket (ami 14 ugye-kék), és azt kiírjuk a $D020-as címre. A keret ettől világoskék lesz.
Az STA utasítások sorában megfigyelhető, hogy itt bizony már 3 szám keletkezett az utasítás előtt. Igen, a "8D" az STA kódja, a "20 D0" pedig a D020, csak fordítva. A C64 a memóriacímeket mindig ilyen fordított sorrendben tárolja, ezt hívjuk alsó/felső byte alaknak, ez sokszor előkerül majd még.
Az utolsó utasítást nem lesz nehéz kikövetkeztetni, ez a JMP (JuMP), vagyis ugyanaz, mint a BASIC-ben a GOTO. Itt természetesen nem programsort, hanem egy direkt memóriacímet adunk meg, hogy hová ugorjon a program. Mi bizony visszaugrunk a legelejére, vagyis a $1000-es memóriacímre. És itt minden kezdődik elölről.
A gépi kódot, és az assembly utasításokat összevetve könnyen észrevehető, hogy vannak parancsok, amik 2 byte, míg vannak amik 3 byte hosszúak. Valójában minden utasítás csak 1 byte, a mögötte álló plusz 1 vagy 2 byte az az utasítás paraméterei. Van utasítás, aminek nincs paramétere, az értelemszerűen csak 1 byte hosszú lesz. Szerencsére ezeket nem kell fejben tartanunk, az assembler tudja helyettünk is. (Ugye milyen kényelmes? 😆😆😆)
Az is jól látszik, hogy az assembly parancsaink mind 3 karakteresek, és általában valamilyen könnyen megjegyezhető szó rövidítései.
Futtassuk!
Ha minden tuti, a kurzor most egy "A 100D" kezdetű sorban villog, az assembler azt hiszi mi még folytatnánk a programot, és várja az utasítást. Ha itt nem adunk meg semmit, csak ráenterezünk, akkor tudja, hogy köszi nem akarunk többet pötyögni, és egy sima üres sort kapunk a kurzorral.
A programunkat futtatni a "G"-vel tudjuk (Go). Meg kell adnunk egy memóriacímet is, ami ez esetben 1000. Vagyis beírva, hogy "G 1000", a programunknak el kell indulnia.
Hasonló, de sokkal sűrűbb villogást kapunk, mint a BASIC program esetében, viszont itt már látszik a sebességkülönbség a BASIC és az assembly között. Mivel közvetlenül a processzort programozzuk, mindenféle közvetítő (esetünkben a BASIC parancsértelmező) nélkül, így kb 300-szoros sebességnövekedést értünk el.
BASIC-ben kb fél képernyőnként változott a keret színe, jelenleg pedig minden egyes sorban többször is. Ha tegyük fel, sikerülne úgy beidőzíteni a programunkat, hogy csak minden sor elején változtassa meg a keret színét, akkor érdekes effekteket érhetnénk el, mint pl a klasszikus C64-es "rasterbar"-ok.
Összefoglalva
A processzor csak számokat ért meg. Minden szám egy parancs számára. Ezeket a számokat a memóriából olvassa ki. A számokat beírhatjuk kézzel a memóriába, vagy egy assemblerrel, ami kicsit könnyebb.
Ja igen...
Most mindenki ottmaradt egy villogó képernyővel, ami a végtelenségig ezt csinálja. Hogyan is állítsuk meg?
Az ESC+PageUp lenyomása megfelel az igazi C64 RUN/STOP + RESTORE billentyűkombinációjának. Ha megnyomjuk, az egy megszakítást (interrupt) generál. Előzőleg volt már szó arról, hogy a processzor képes megszakítások kezelésére, vagyis amikor kap egy ilyen megszakításkérelmet, akkor félbehgyja az adott program futását, és valami mást csinál. A monitorprogram ezt a megszakítást átírja saját magára, így a billentyűk lenyomásakor a mi programunk futása megszakad, és a megszakítás által megadott helyen folytatódik, vagyis visszakerülünk a monitorba, egy üres prompttal.
Még néhány apróság
Lehet feltűnt már, hogy néhol monitorként, néhol assemblerként hivatkozok a programra, amit használunk. A monitor az általános összefoglaló neve magának a toolnak, amiben van assembler, disassembler, memory dumper, meg egyéb nyalánkságok.
Ez a monitor program természetesen nem csak assembly -> gépi kód átalakítást tud (assembler), hanem fordítva is, vagyis a disassembler is egyben. Magyarán a memóriából kiolvasott byte-okat értelmezve, elő tudja állítani az eredetileg általunk beírt assembly kódot is, mivel az ugye csak egy szám->3 betűs szöveg összerendelés.
Ezt a D (disassemble) paranccsal tudjuk elővarázsolni. Ha beírjuk, hogy "D 1000 1010", akkor kilistázza az 1000-1010 közötti memóriaterületet nekünk, olvasható formában.
Itt látható, hogy írhattunk volna nagyobb számot is, mint a 1010, de felesleges lett volna, hiszen a programunk a JMP-nél véget ér, utána pedig már csak a memóriában található "szemét" látszik. Az "FF" például a processzor által nem értelmezhető utasítás, így kérdőjelek jelennek meg. (A 6502/6510 nem rendelkezik 255 különböző utasítással, így némely byte értelmezhetetlen, vagy hivatalosan nem dokumentált működést eredményez)
Mivel a teljes képernyős szerkesztő mindig mindenhol működik, a kilistázott programunkat a BASIC-hez hasonlóan tudjuk módosítani is. Csak annyit kell tenni, hogy a kurzorral felmegyünk, és az LDA #$0D -t átírjuk LDA #$00 -ra, majd nyomunk egy entert. Azután a másik LDA-nál a 0E-t pedig például 01-re, majd enter.
Futtatva látható, hogy a keret most a fekete (00) és a fehér (01) színek között váltogat.
A kilistázott programot, és a képernyőt "görgetni" is tudjuk, ha lemegyünk az utolsó sorba, és nyomkodjuk a lefelé nyilat. Ilyenkor a memória listázása 1010-től folytatódik. Ugyanez igaz a képernyő tetején is. Mindenki próbálja ki, elsőre kicsit fura, de szokható. Valamint nagyon hasznos, hosszab programoknál, amik nem férnek ki egy képernyőre.
Az F5 és F7 billentyűkkel a képernyőt automatikusan folyamatosan görgethetjük a megfelelő irányba, pause: kurzor fel, kilépés a görgetésből: kurzor le.
Elsőre ennyi, mindenki emésztgesse, játszogasson, próbálgasson. Eminenseknek szorgalmi: írjuk át a programot, hogy ne a keretet, hanem a háttérszínt váltogassa. ($D021)
Lépjünk tovább, lássuk hogyan tudunk valamit megjeleníteni a képernyőn!
Némi extra adalék: Action Replay monitor parancsok (7.pontnál)
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.