Jumps
Una volta terminata questa lezione, saremo in grado di capire tutto di CopyTiles
!
Finora tutto il codice che abbiamo visto era lineare: veniva eseguito dall’alto verso il basso. Ma questo non è sufficiente: a volte è necessario eseguire alcune azioni in base al risultato di altre (“se le crêpes iniziano ad attaccarsi, ungi di nuovo la padella”), e a volte è necessario eseguire azioni ripetutamente (“se è rimasta della pastella, ripeti dal passo 5”).
Entrambe le cose implicano una lettura non lineare della ricetta. In assembly, questo si ottiene con i salti.
La CPU dispone di un registro speciale chiamato “PC”, che sta per Program Counter. Esso contiene l’indirizzo dell’istruzione in corso di esecuzione1, come se si tenesse a mente il numero del passo della ricetta che si sta eseguendo. Il PC aumenta automaticamente quando la CPU legge le istruzioni, quindi “per impostazione predefinita” vengono lette in sequenza; tuttavia, le istruzioni di salto consentono di scrivere un valore diverso nel PC, saltando di fatto a un’altra parte del programma. Da qui il nome.
Ok, allora parliamo di queste istruzioni di salto, che ne dite? Ce ne sono quattro:
Instruction | Mnemonic | Effect |
---|---|---|
Jump | jp | Jump execution to a location |
Jump Relative | jr | Jump to a location close by |
Call | call | Call a subroutine |
Return | ret | Return from a subroutine |
Per ora ci concentreremo su jp
.
jp
, come quello della riga 5, imposta semplicemente PC al suo argomento, saltando l’esecuzione a quel punto.
In altre parole, dopo l’esecuzione di jp EntryPoint
(riga 5), l’istruzione successiva eseguita è quella sotto EntryPoint
(riga 16).
🤔
Ci si potrebbe chiedere a cosa serva questo specifico jp
.
Non preoccupatevi, vedremo più avanti perché è necessario.
Salti condizionati
Passiamo ora alla parte davvero interessante. Esaminiamo il ciclo responsabile della copia delle tile:
; Copy the tile data
ld de, Tiles
ld hl, $9000
ld bc, TilesEnd - Tiles
CopyTiles:
ld a, [de]
ld [hli], a
inc de
dec bc
ld a, b
or a, c
jp nz, CopyTiles
Non preoccupatevi se non capite bene quanto segue, perché lo vedremo in azione nella prossima lezione. Se avete problemi, provate ad andare alla prossima lezione e guardate il codice eseguito passo per passo; poi, tornando a questo punto, dovrebbe avere tutto più senso.
Per prima cosa, copiamo Tiles
, l’indirizzo del primo byte dei dati delle tile, in de
.
Poi, impostiamo hl
a $9000, che è l’indirizzo in cui inizieremo a copiare i dati delle tile.
ld bc, TilesEnd - Tiles
imposta bc
sulla lunghezza dei dati delle tile: TilesEnd
è l’indirizzo del primo byte dopo i dati delle tile, quindi sottraendogli Tiles
si ottiene la lunghezza.
Quindi, in pratica:
de
contains the address where data will be copied from;hl
contains the address where data will be copied to;bc
contains how many bytes we have to copy.
Arriviamo quindi al ciclo principale.
Leggiamo un byte dalla sorgente (riga 29) e lo scriviamo nella destinazione (riga 30).
Incrementiamo i puntatori alla destinazione (tramite l’implicito inc hl
fatto da ld [hli], a
) e alla sorgente (riga 31), in modo che la successiva iterazione del ciclo elabori il byte successivo.
Ecco la parte interessante: poiché abbiamo appena copiato un byte, significa che ne abbiamo uno in meno, quindi dobbiamo fare dec bc
.
(Abbiamo già visto dec
due lezioni fa; per rinfrescare la memoria, si tratta semplicemente di diminuire di uno il valore memorizzato in bc
).
Poiché bc
contiene la quantità di byte che devono ancora essere copiati, è facile capire che dobbiamo semplicemente ripetere l’operazione se bc
!= 0.
😓
dec
di solito aggiorna i flag, ma sfortunatamente dec bc
non lo fa, quindi dobbiamo controllare manualmente se bc
ha raggiunto 0.
ld a, b
e or a, c
applicano “bitwise OR” a b
e c
insieme; per ora è sufficiente sapere che lascia 0 in a
se e solo se bc
== 0.
E or
aggiorna il flag Z!
Quindi, dopo la riga 34, il flag Z è impostato se e solo se bc
== 0, cioè se dobbiamo uscire dal ciclo.
Ed è qui che entrano in gioco i salti condizionati! È possibile “prendere” condizionatamente un salto a seconda dello stato dei flag.
Le “condizioni” sono quattro:
Name | Mnemonic | Description |
---|---|---|
Zero | z | Z is set (last operation had a result of 0) |
Non-zero | nz | Z is not set (last operation had a non-zero result) |
Carry | c | C is set (last operation overflowed) |
No carry | nc | C is not set (last operation did not overflow) |
Quindi, jp nz, CopyTiles
può essere letto come “se il flag Z non è impostato, allora salta a CopyTiles
”.
Poiché stiamo saltando all’indietro, ripeteremo di nuovo le istruzioni: abbiamo appena creato un ciclo!
Ok, abbiamo parlato molto del codice e lo abbiamo visto girare, ma non abbiamo visto come gira. Guardiamo la magia che si svolge al rallentatore nella prossima lezione!
Not exactly; instructions may be several bytes long, and PC increments after reading each byte. Notably, this means that when an instruction finishes executing, PC is pointing to the following instruction. Still, it’s pretty much “where the CPU is currently reading from”, but it’s better to keep it simple and avoid mentioning instruction encoding for now.