Toinen 6502 assembler-ohjelmani valmistui juuri

Kuten kerroin aiemmassa blogissani Ensimmäinen 6502-konekieliohjelma tehty, olen juuri tutustumassa hieman paremmin 6502 assembler-ohjelmointiin. Opiskelen sitä virtuaalisessa, emuloidussa Commodore 64 mikrotietokoneessa. Ajan siis koodiani VICE-emulaattorin alaisuudessa.

Varsinaisen kehitystyön eli ohjelmoinnin ja kääntämisen teen kuitenkin Fedora Linux 27:ssä käyttäen CC65 ristiinkääntöohjelmistoa, jossa on tuki C-kielellekin. Sivumennen sanottuna kuten useimmat muutkin C-kääntäjät, CC65 kääntää C-kielen assembleriksi, josta sen erillinen assembler-kääntäjä tekee lopulta ajettavaa konekieltä. Olen joskus monta vuotta sitten kokeillut CC65:n C-kääntäjää ja todennut, että kyllähän sillä C64 koodia saa aikaan. Mutta Commodore 64:n muistiavaruus on nykystandardeilla mitattuna niin käsittämättömän pieni, että suositeltavampaa lienee vääntää suoraan käsin assembleria mikäli mielii minimoida muistinkäytön ja optimoida ohjelmansa mahdollisimman tehokkaaksi.

Ensimmäinen 6502 ohjelmani oli pituudeltaan vain kolme riviä ja se oli niin yksinkertainen, että sen selittämiseen ei juuri kommentteja tarvittu. Ajattelin, että seuraava pieni 6502/C64-projektini voisi olla yhden spriten luominen ruudulle ja sen ohjaileminen joystickin avulla. Olen joskus koodannut BASIC-kielellä pelin Commodore 64:lle, mutta se oli hyvin alkeellinen ja hidas BASIC-tulkin käytön takia. Suoraan sanottuna peli oli aika kammottava, mutta olipahan kuitenkin jonkinlainen toimiva ohjelma silti.

Jotta 6502 koodaus onnistuisi, minun piti palautella mieliin miten Commodore 64:ää ylipäänsä ohjelmoidaan. Tässä assemblerin maailmassa liikutaan niin lähellä raakaa rautaa, että mitään korkean tason abstrakteja ohjelmakirjastoja ei ole mahdollista käyttää. Eikä C64:ssä tietysti olisi resurssejakaan sellaisten ajonaikaiseen pyörittämiseen. Commodore 64 tukee raudan tasolla sprite-grafiikkaa ja spritejen luominen ja manipuloiminen on periaatteessa helppoa, mutta se vaatii VIC-II eli grafiikkapiirin rekisterien käpistelyä ja tarkkoja esivalmisteluja. Grafiikkaraudalle on mm. kerrottava tarkkaan missä päin näyttömuisti sijaitsee, jotta se osaisi vastaavasti poimia sen lopusta sprite pointerin välityksellä tiedon siitä missä spritejen muodostava binäärinen data on. VIC-II piirille voi myös kertoa minkä värinen sprite on ja pitääkö sen pikselikoko tuplata sivu- tai pystysuunnassa jne. Grafiikkaa hoitava rauta osaa renderoida ja liikuttaa sprite-grafiikkaa ilman softaa, mutta toki VIC-II:ta pitää ohjeistaa kirjoittamalla sen rekistereihin tarvittavat arvot. Pelikäytössä on myös se hyödyllinen ominaisuus, että grafiikkapiiri osaa raudan tasolla tunnistaa spritejen yhteentörmäykset.

En yrittänyt googlettaa valmista koodia, vaan koetin etsiä tietoja siitä miten spritejä käytettiinkään C64:ssä. Vahingossa kuitenkin päädyin Githubiin ja löysin sieltä yksinkertaisen koodin, joka rakensi yhden spriten. Koodissa oli myös rutiinit, jotka liikuttivat spriteä näppäinten painallusten perusteella. Muistaakseni muokkasin koodia jonkin verran, jotta sain sen syntaksin CC65:n assemblerin ymmärtämään muotoon. Paljon ei kuitenkaan tarvinnut tehdä kun sen sai käännettyä ja testattua VICE:ssä. Aloin muokata koodia enemmän omaan makuuni sopivaksi.

Minun hermoni ei kestänyt sitä neliömäistä esittelyspriteä, jonka data oli siis täynnä päällä olevia bittejä. Ajattelin, että voisin kaivaa Manic Miner pelistä Miner Willyn spritedatan ja käyttää sitä opetteluohjelmassani. Se on kuitenkin viihdyttävämpi kuin pelkkä neliö. Spritejen luominen on helppoa eli sen osaa tehdä fiksu lapsikin C64:llä. Yksivärinen sprite koostuu datablokista, jonka koko on 64 tavua, mutta viimeinen tavu on vain spritedatan alignointiin 64 tavun muistirajoille, jotta matematiikka olisi helpompaa. Spriten näkyvä osa siis koostuu 63:sta tavusta.

En yhtään muistanut miten löytäisin Manic Minerista sen spritedatan, mutta käynnistin pelin VICE emulaattorissa ja aloin googlettaa.

Ensimmäinen ongelmani oli selvittää mistä tekstiruutu-RAM alkaisi. Sain selville, että osoitteessa 53272 olisi se tieto. Jossain luki nimittäin seuraavaa:

When in text screen mode, the VIC-II looks to 53272 for information on where the character set and text screen character RAM is located: The four most significant bits form a 4-bit number in the range 0 thru 15: Multiplied with 1024 this gives the start address for the screen character RAM. Bits 1 thru 3 (weights 2 thru 8) form a 3-bit number in the range 0 thru 7: Multiplied with 2048 this gives the start address for the character set.

Komensin Linuxin bash-shellissä:

printf "%x\n" 53272
d018

Eli heksadesimaalina osoite on $d018. Sitten käynnistin VICE:n konekielimonitorin ja katsoin mikä arvo kyseisessä muistipaikassa oli:

disass d018 d018
1d 71 f0

Siis 1d eli desimaalina 29. Tarkastellaan sitä 8-bittisenä bittijonona:

1286432168421
00011101

Tuosta nähdään, että neljästä merkitsevimmästä bitistä yksi on päällä.

8421
0001

Googlen tiedon mukaan tavu siis "halkaistaan kahtia" ja nuo merkitsevimmät tavut tulee tulkita 4-bittisenä lukuna välillä 0-15 desimaalina. Näemme, että arvoksi tulee tällöin 1.

Toisin sanoen tekstiruutu-RAM alkaa normaalisti osoitteesta 1024, joten sprite pointerit alkavat osoitteesta 2040 desimaalina eli 07f8 heksana. Sitten taas VICE:n konekielimonitoriin tutkimaan arvoa sieltä:

disass 07f8 07f8
d2

d2 on desimaalina 210. Commodore 64 Programmer's Reference Guidessa kerrotaan, että sprite 0:n eli ensimmäisen spriten datan sijainti saadaan selville seuraavalla kaavalla:

LOCATION = (BANK * 16384) + (SPRITE POINTER VALUE * 64)

missä BANK eli muistipankki on välillä 0-3. Laskin expr 210 \* 64 ja tein bash-skriptin:

for bank in 0 1 2 3; do
result=$(( $bank * 16384 + 13440))
printf "%d %x\n" $result $result
done

Tulos oli:

13440 3480
29824 7480
46208 b480
62592 f480

Sitten jälleen VICE:n konekielimonitoriin ja komento ms 3480. Meitä onnisti ja Miner Willy on löytynyt!

miner willy discovered

Sitten konekielimonitorissa muistista dumppi komennolla mem 3480 34bf. Copy-pastetin heksaluvut talteen ja sijoittelin ne assembler-ohjelmaani. Kaikki toimi hienosti. Sen jälkeen yritin korvata näppäinohjauksen joystick-ohjauksella.

Joystick-rutiinini saattoi jopa olla lähellä toimivaakin, mutta konekielen valtavan nopeuden takia en tajunnut, että ohjaus oli holtitonta. En ole täysin varma teinkö bittivertailut oikein, joten epäilin rutiinissani olevan jotakin vikaa vaikka se olikin yksinkertainen. Googlella löysin Bill Hindorffin joystick-rutiinin, joka on alunperin julkaistu Commodore 64 Programmer's Reference Guide kirjassa sivulla 345. En ensin tajunnut miten rutiini toimii, mutta kommenteissa selitettiin asia ja erityisesti se miten fire-napin tila välittyy rutiinin kutsujalle. Se ei ollut koodista lukemalla ihan itsestäänselvää kun ei ole tottunut 6502 arkkitehtuurin syvällisiin hienouksiin. Hindorffin rutiinissa on kaksi yhden tavun kokoista tilamuuttujaa, dx ja dy, joihin rutiini tallentaa onko X- tai Y-akselilla ollut liikettä. Tulitusnapin tila palautuu CARRY-lipun avulla eli 0 tarkoittaa, että fire-nappia on painettu. Rutiini käyttää elegantisti LSR eli Logical Shift Right -komentoa. Oli mukavaa nähdä miten sitä voidaan hyödyntää assembler-koodin tiivistämisessä ja tehostamisessa.

Nyt ohjelmani on valmis ja se tuntuu toimivan oikein. Joystick #2 ohjaa Miner Willyä ruudulla neljään pääilmansuuntaan ja tulitusnapin painallus lopettaa ohjelman suorituksen ja resetoi Commodore 64:n tutulla SYS64738 käskyn konekielisellä vastineella.

Tässä on ohjelma tar.gz pakettina ja mukana tulee Makefile kääntämistä varten: second.tar.gz

Paketti puretaan, käännetään ja käynnistetään seuraavilla komennoilla:

tar xzvf second.tar.gz
cd second
make
x64 second.prg &

Kun VICE:n C64-emulaatio on ylhäällä, konekieliohjelma käynnistetääm Commodore 64:n sisältä BASIC-tulkista komennolla SYS49152 .

Lopetan nyt tähän tältä erää.