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:

InstructionMnemonicEffect
JumpjpJump execution to a location
Jump RelativejrJump to a location close by
CallcallCall a subroutine
ReturnretReturn 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:

NameMnemonicDescription
ZerozZ is set (last operation had a result of 0)
Non-zeronzZ is not set (last operation had a non-zero result)
CarrycC is set (last operation overflowed)
No carryncC 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!


1

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.