Operazioni e flag
Va bene, sappiamo come passare i valori, ma copiare semplicemente i numeri non è divertente; vogliamo modificarli!
La CPU GB non fornisce tutte le operazioni sotto il sole (per esempio, non esiste un’istruzione di moltiplicazione), ma possiamo programmarle da soli con ciò che abbiamo. Parliamo di alcune delle operazioni di cui dispone; per ora ne ometterò alcune non utilizzate in Hello World.
Aritmetica
Le istruzioni aritmetiche più semplici supportate dalla CPU sono inc
e dec
, che rispettivamente INCrementano e DECrementano il loro operando.
(Se non siete sicuri, “incrementare” significa “aggiungere 1” e “decrementare” significa “sottrarre 1”).
Quindi, per esempio, il dec bc
alla riga 32 di hello-world.asm
sottrae semplicemente 1 da bc
.
Ok, bene!
Possiamo però andare un po’ più veloci?
Certo che sì, con add
e sub
!
Questi rispettivamente aggiungono e sottraggono valori arbitrari (una costante o un registro).
Nessuno dei due viene usato nel tutorial, ma c’è un parente di sub
: avete notato il piccolo cp
alla riga 17?
cp
permette di confrontare i valori.
Funziona come sub
, ma scarta il risultato invece di riscriverlo.
“Aspettate, quindi non fa nulla?”, vi chiederete; beh, aggiorna le flag.
Flag
È arrivato il momento di parlare del registro speciale (ve lo ricordate?) f
, per, beh, flag.
Il registro f
contiene 4 bit, chiamati “flag”, che vengono aggiornati a seconda dei risultati di un’operazione.
Questi 4 flag sono:
Name | Description |
---|---|
Z | Zero flag |
N | Addition/subtraction |
H | Half-carry |
C | Carry |
Sì, esistono sia un flag chiamato “C” che un registro chiamato “c” e sono cose diverse e non correlate. Questo rende la sintassi un po’ confusa all’inizio, ma vengono sempre usati in contesti diversi, quindi basta farci caso.
Per ora dimentichiamo N e H; concentriamoci su Z e C. Z è il flag più semplice: viene impostato quando il risultato di un’operazione è 0 e viene azzerato altrimenti. C viene impostato quando un’operazione va in overflow o in underflow.
Che cos’è un overflow?
Prendiamo la semplice istruzione add a, 42
.
Questa istruzione aggiunge semplicemente 42 al contenuto del registro a
e scrive il risultato in a
.
ld a, 200
add a, 42
Alla fine di questo snippet, a
è uguale a 200 + 42 = 242, fantastico!
Ma se invece scrivessi questo?
ld a, 220
add a, 42
Si potrebbe pensare che a
sia uguale a 220 + 42 = 262, ma non sarebbe corretto.
Ricordate che a
è un registro a 8 bit, può memorizzare solo otto bit di informazione!
E se dovessimo scrivere 262 in binario, otterremmo %100000110, che richiede almeno 9 bit…
Quindi cosa succede?
Semplicemente, il nono bit viene perduto e il valore che otteniamo è %00000110 = 6. Questo si chiama overflow.
Questo si chiama overflow: dopo l’aggiunta, otteniamo un valore più piccolo di quello con cui abbiamo iniziato.
We can also do the opposite with sub
, and—for example—subtract 42 from 6; as we know, for all X
and Y
, X + Y - Y = X
, and we just saw that 220 + 42 = 6 (this is called modulo 256 arithmetic, by the way); so, 6 - 42 = (220 + 42) - 42 = 220.
This is called an underflow: after subtracting, we get a value greater than what we started with.
Quando viene eseguita un’operazione, imposta il flag di riporto se si è verificato un overflow o un underflow, altrimenti lo azzera. (Vedremo più avanti che non tutte le operazioni aggiornano il flag di riporto)
Summary
- We can add and subtract numbers.
- The Z flag lets us know if the result was 0.
- However, registers can only store a limited range of integers.
- Going outside this range is called an overflow or underflow, for addition and subtraction respectively.
- The C flag lets us know if either occurred.
Confronto
Parliamo ora di come cp
viene utilizzato per confrontare i numeri.
Ecco un ripasso: cp
sottrae il suo operando da a
e aggiorna i flag di conseguenza, ma non scrive il risultato.
Possiamo usare i flag per controllare le proprietà dei valori confrontati e vedremo nella prossima lezione come usarli.
L’interazione più semplice è quella con il flag Z.
Se è impostato, sappiamo che la sottrazione ha prodotto 0, cioè a - operando == 0
; quindi, a == operando
!
Se non è impostato, allora sappiamo che a != operando
.
Ok, controllare l’uguaglianza è bello, ma potremmo anche voler eseguire dei confronti. Non preoccupatevi, perché il flag di riporto serve proprio a questo! Quando si esegue una sottrazione, il flag di riporto viene impostato quando il risultato scende sotto lo 0, ma è solo un modo elegante per dire “diventa negativo”!
Quindi, quando il flag di riporto viene impostato, sappiamo che a - operando < 0
e di conseguenza che a < operando
…!
E, al contrario, sappiamo che se non è impostato, a >= operando
.
Ottimo!
Riassunto dell’istruzione
Instruction | Mnemonic | Effect |
---|---|---|
Add | add | Adds values to a |
Subtract | sub | Subtracts values from a |
Compare | cp | Compares values with what’s contained in a |