Primi passi in Assembly
Bene, ora che sappiamo cosa fanno gli strumenti, vediamo che lingua parla RGBASM.
Prenderò una breve porzione dell’inizio di hello-world.asm
, in modo da essere d’accordo sui numeri di riga e da avere un’evidenziazione della sintassi anche se il vostro editor non la supporta.
INCLUDE "hardware.inc"
SECTION "Header", ROM0[$100]
jp EntryPoint
ds $150 - @, 0 ; Make room for the header
EntryPoint:
; Shut down audio circuitry
ld a, 0
ld [rNR52], a
Analizziamolo insieme. Sappi che per il momento salteremo molte delle funzionalità di RGBASM; se fossi curioso di saperne di più, dovrai aspettare fino alla seconda o terza parte oppure leggere la documentazione.
Commenti
Iniziamo dalla riga 10, che dovrebbe essere grigia nel riquadro qui sopra.
I punti e virgola ;
indicano un commento.
I commenti (che finiscono alla fine della riga) sono ignorati dall’assembler, indipendentemente dal contenuto.
Come vedi alla riga 7, puoi anche inserire commenti dopo aver scritto altro.
I commenti sono molto importanti in tutti i linguaggi di programmazione: ti aiutano a descrivere la funzione del tuo codice. È più o meno la differenza tra “scalda il forno fino a 180°C” e “scalda il forno a 180°C, se lo scaldassi di più la torta brucerebbe”. I commenti sono utilissimi in ogni linguaggio di programmazione, ma in Assembly sono ancora più importanti: infatti, il codice Assembly è molto più astratto.
Istruzioni
Assembly is a very line-based language. Each line can contain one of two things:
- a directive, which instructs RGBASM to do something, or
- an instruction1, which is written directly into the ROM.
Parleremo delle direttive più avanti, per ora concentriamoci sulle istruzioni: per esempio, nello snippet qui sopra, ignoreremo le righe 1 (INCLUDE
), 7 (ds
) e 3 (SECTION
).
Per continuare l’analogia con la preparazione di una torta, le istruzioni sono come i passi di una ricetta. Il processore (CPU) esegue un’istruzione alla volta. Istruzione dopo istruzione… dopo un po’ si arriva al risultato! Come cuocere una torta, disegnare “Hello World”, oppure mostrarti un tutorial sull’Assembly del GameBoy!
Le istruzioni sono composte da una mnemonica, un nome con cui le puoi invocare, e dei parametri, ovvero su cosa va eseguita l’operazione. Ad esempio: in “sciogli il cioccolato ed il burro in una padella” l’istruzione è tutta la frase; la mnemonica sarebbe l’azione, ovvero sciogli, mentre i parametri sono gli oggetti della frase (cioccolato, burro, padella).
Cominciamo dall’istruzione più importante: ld
.
ld
sta per “carica”, e semplicemente copia i dati contenuti nel secondo parametro (“RHS”) nel primo (“LHS”).
Per esempio, guardiamo la riga 11 del nostro programma, ld a, 0
: copia (“carica”) il numero zero nel registro a
2.
Per fare un altro esempio, a riga 33 troviamo ld a, b
: significa semplicemente “copia il valore di b
in a
.
Instruction | Mnemonic | Effect |
---|---|---|
Load | ld | Copies values around |
ℹ️
Per via delle limitazioni del processore, non tutte le combinazioni di operandi sono valide per ld
e per molte altre istruzioni; ne parleremo in seguito, quando arriverà il momento di scrivere il nostro codice.
🤔
RGBDS ha una lista delle istruzioni del GameBoy che vale la pena tenere tra i preferiti, e che si può anche consultare dal terminale scrivendo man 7 gbz80
se RGBDS è installato sulla propria macchina (tranne su Windows…).
Le descrizioni che trovate in quella pagina sono più concise: sono intese come un promemoria, non come un tutorial.
Direttive
Quindi, in un certo senso, le istruzioni sono destinate al processore del GameBoy mentre i commenti sono destinati al programmatore. Ma alcune righe non sono né l’una né l’altra cosa, e sono invece dei metadati destinati a RGBDS stesso. Queste sono chiamate direttive e il nostro “Hello World” ne contiene tre.
Includere un altro file
INCLUDE "hardware.inc"
La riga 1 include hardware.inc
3.
Include
ndo un file è come se copiassimo il suo contenuto alla riga dove inseriamo la direttiva.
Così facendo, si può riciclare facilmente il codice in diversi file: se, ad esempio, due file a.asm
e b.asm
includono hardware.inc
basta modificare il file perché le modifiche si applichino ad a.asm
e b.asm
.
Se invece copiassi a mano il contenuto di hardware.inc
in a.asm
e b.asm
dovresti modificare il contenuto di entrambi ogni volta che vuoi apportare un cambiamento, che non è solo uno spreco di tempo ma aumenta la possibilità di commettere errori.
hardware.inc
definisce alcune costanti molto utili per interfacciarsi con l’hardware del GameBoy.
Le costanti non sono altro che dei nomi a cui è assegnato un valore: scrivere una costante equivale a scrivere il valore che le è assegnato.
Questo torna molto utile: è molto più semplice ricordare il registro “LCD Control” (impostazioni dello schermo) col nome rLCDC
piuttosto che ricordare l’indirizzo $FF40
.
Parleremo delle costanti in modo più approfondito nella Parte Ⅱ.
Sezioni
Spieghiamo innanzitutto che cos’è una “sezione”, poi vedremo che cosa fa la riga 3.
Una sezione rappresenta un intervallo contiguo di memoria che, di base, finisce da qualche parte non nota in anticipo.
Se si vuole vedere dove finisce ogni sezione si può chiedere a RGBLINK di generare un “file mappa” con l’opzione -m
:
rgblink hello-world.o -m hello-world.map
…e possiamo vedere, per esempio, dove è finita la sezione "Tilemap"
:
SECTION: $05a6-$07e5 ($0240 bytes) ["Tilemap"]
Le sezioni non possono essere divise da RGBDS, che è utile ad esempio per il codice poiché il processore esegue le istruzioni una dopo l’altra (a parte con i salti, che vedremo più avanti). Va trovato il giusto equilibrio per il numero di sezioni: non troppe ma neanche troppo poche, anche se in genere non ha molta importanza fino a quando non si inizia a parlare di banche di memoria.
Quindi, per ora, assumiamo che una sezione debba contenere cose che “vanno insieme” dal punto di vista topico, ed esaminiamo una delle nostre.
SECTION "Header", ROM0[$100]
Quindi!
Cosa fa questa riga?
Altro non è che la dichiarazione di una nuova sezione; tutte le istruzioni e i dati dopo questa riga e fino alla successiva dichiarazione SECTION
saranno inseriti in questa sezione appena creata.
Prima della prima direttiva SECTION
non c’è una sezione “attiva”: scrivere dati o codice al di fuori di una sezione ci darà l’errore Cannot output data outside of a SECTION
.
Il nome della nuova sezione è “Header
”.
I nomi delle sezioni possono contenere qualsiasi carattere (e anche essere vuoti, se si vuole) e devono essere unici4.
La parola chiave ROM0
indica a quale “tipo di memoria” appartiene la sezione (ecco un elenco).
Ne parleremo nella Parte Ⅱ.
Dove dice [100$]
invece è più interessante, perché è un’indicazione speciale per questa sezione.
Se ricordi, prima abbiamo detto che:
a section […] by default, ends up somewhere not known in advance.
Però alcune parti della memoria sono speciali, e quindi a volte è necessario che una sezione specifica copra un intervallo di memoria specifico.
Per permetterlo, RGBASM ha la sintassi [addr]
che forza l’indirizzo iniziale della sezione a essere addr
.
In questo caso, l’intervallo di memoria $100-$14F è speciale perché è l’header della ROM. Parleremo dell’header tra un paio di lezioni, ma per il momento basta sapere che non dobbiamo inserire né codice né dati in quello spazio. E come facciamo? Innanzitutto, iniziamo una sezione all’indirizzo $100, dopodiché riserviamo un po’ di spazio.
Lasciare spazio
jp EntryPoint
ds $150 - @, 0 ; Make room for the header
La riga 7 afferma di “fare spazio per l’header”, di cui ho parlato brevemente poco sopra.
Per ora, concentriamoci su ciò che ds
fa effettivamente.
ds
è usato per allocare staticamente della memoria.
Riserva un certo numero di byte, che sono impostati ad un certo valore dato.
Il primo argomento di ds
, in questo caso $150 - @
, è quanti byte riservare.
Il secondo argomento (che è opzionale), in questo caso 0
, è il valore a cui impostare ogni byte riservato5.
Vedremo perché questi byte devono essere riservati in un paio di lezioni.
È importante notare che il primo argomento è un’espressione.
RGBDS (fortunatamente!) supporta l’inserimento di espressioni arbitrarie pressoché ovunque.
Quest’espressione è una semplice sottrazione: $150 meno @
, che è un simbolo speciale che sta per “l’indirizzo in memoria attuale”.
Un simbolo è essenzialmente “un nome associato a un valore”, di solito un numero. Nel corso dell’esercitazione esploreremo i diversi tipi di simboli, a partire dalle etichette nella prossima sezione.
Un simbolo numerico utilizzato in un’espressione viene sostituito dal suo valore, che dev’essere noto al momento della compilazione della ROM; in particolare, non può dipendere dal contenuto di alcun registro.
Ora vi starete chiedendo cosa siano questi “indirizzi di memoria” di cui continuo a parlare. Andiamo subito a scoprirlo!
Technically, instructions in RGBASM are implemented as directives, basically writing their encoded form to the ROM; but the distinction between the instructions in the source code and those in the final ROM is not worth bringing up right now.
The curious reader may ask where the value is copied from. The answer is simply that the “immediate” byte ($00 in this example) is stored in ROM just after the instruction’s opcode byte, and it’s what gets copied to a
.
We will come back to this when we talk about how instructions are encoded later on.
hardware.inc
itself contains more directives, in particular to define a lot of symbols.
They will be touched upon much later, so we won’t look into hardware.inc
yet.
Section names actually only need to be unique for “plain” sections, and function differently with “unionized” and “fragment” sections, which we will discuss much later.
Actually, since RGBASM 0.5.0, ds
can accept a list of bytes, and will repeat the pattern for as many bytes as specified.
It just complicates the explanation slightly, so I omitted it for now.
Also, if the argument is omitted, it defaults to what is specified using the -p
option to RGBASM.