Salta ai contenuti

UDA 2 — Il controllo del flusso

3 ore (1 di teoria + 2 di laboratorio) · Settimana 4

Al termine di questa unità:

  • usi i salti incondizionati (JP) e condizionati (JR/JP + condizione) leggendo i flag;
  • sai scegliere la condizione giusta dopo un CP (minore / uguale / maggiore);
  • costruisci cicli con DJNZ e con contatori a 16 bit (BC);
  • traduci if/else, while e for dal pseudo-codice all’assembly;
  • hai completato l’Esercizio 2 e sai spiegare la pausa dell’esempio 03-led-contatore.

Finora i programmi eseguivano le istruzioni in ordine. Il registro PC punta alla prossima istruzione e normalmente avanza da solo… ma noi possiamo cambiarlo:

Tipo di saltoIstruzioneEffetto
incondizionatojp etichetta / jr etichettasalta sempre
condizionatojr z, etichetta / jp nc, etichetta…salta solo se la condizione è vera

I salti leggono i flag (UDA 1) e non li modificano: questa è la chiave per concatenare confronti e decisioni.


jp ciclo ; salta a 16 bit: arriva ovunque (3 byte)
jr ciclo ; salto relativo: ±127 byte (2 byte, più compatto)
  • JP contiene un indirizzo completo: 3 byte, nessun limite di distanza.
  • JR contiene uno spiazzamento (offset) con segno: 2 byte, ma il bersaglio deve stare entro −128…+127 byte. Comodissimo per i salti vicini (cicli, rami if/else).
  • Se un JR è troppo lontano, l’assembler lo segnala: “salto relativo fuori range” — in quel caso si usa JP o si riorganizza il codice.
IstruzioneByteNota
jp etichettaC3 lo hiindirizzo a 16 bit
jr etichetta18 offsetoffset con segno
djnz etichetta10 offsetrelativo, come jr

3. Salti condizionati: otto domande da fare alla CPU

Sezione intitolata “3. Salti condizionati: otto domande da fare alla CPU”

Ogni condizione corrisponde a un flag (o a una coppia di flag):

MnemonicoSalta se…Da usare dopo…
Zzero: Z = 1CP (uguale), logica (AND/OR/XOR)
NZnon zero: Z = 0come sopra
Ccarry: C = 1CP (primo minore, unsigned), addizioni
NCnon carry: C = 0CP (primo ≥, unsigned)
Msegno: S = 1numeri con segno negativi
Ppositivi: S = 0numeri con segno non negativi
PEparità pari: V = 1dopo operazioni logiche (parità)
POparità dispari: V = 0come sopra

Attenzione: JR esiste solo per Z, NZ, C, NC (le più usate); per le altre serve JP.

JRByteJPByte
jr z28jp zCA lo hi
jr nz20jp nzC2 lo hi
jr c38jp cDA lo hi
jr nc30jp ncD2 lo hi
——jp pe / jp poEA / E2
——jp m / jp pFA / F2

CP n esegue la sottrazione A − n senza salvare il risultato, solo per aggiornare i flag. Con i numeri senza segno (0…255) il flag C dice tutto:

Vuoi…ScriviPerché
salta se A == ncp n + jr z, …differenza zero
salta se A < ncp n + jr c, …carry = “ho dovuto prendere in prestito”
salta se A ≥ ncp n + jr nc, …nessun prestito
salta se A == 0or a + jr z, …OR di A con se stesso: 1 byte, comodo!

E il classico if/else:

ld a, (0x0010)
cp 0x20
jr nc, grande ; A >= 0x20 ?
ld a, 0x01 ; ramo "piccolo"
jr fine
grande: ld a, 0x02 ; ramo "grande"
fine: halt

Numeri con segno: CP confronta “come se” i numeri fossero senza segno; per i numeri con segno servono le condizioni M/P con l’overflow (PE/PO). In questo corso useremo valori 0…255, dove C/NC bastano e avanzano.


DJNZ etichetta fa tre cose: decrementa B, e se B ≠ 0 salta; poi (non tocca i flag!).

ld b, 8 ; contatore
ciclo:
out (0x20), a
inc a
djnz ciclo ; ripeti 8 volte

Trappola: se B vale 0, il decremento lo porta a 255… e il ciclo gira 256 volte! È un classico da tenere a mente (e da testare).

DJNZ conta fino a 255. Per contare di più si usa una coppia (BC, DE, HL) e si testa “è zero?” con un OR. È esattamente quello che fa la pausa dell’esempio 03-led-contatore:

attesa: ; pausa: consuma cicli
push af ; (preserva A e BC del programma principale)
push bc
ld bc, 0x0800 ; 2048 giri
att1:
dec bc
ld a, b ; A = byte alto
or c ; ...test insieme al byte basso
jp nz, att1 ; ripeti finché BC != 0
pop bc
pop af
ret

Perché ld a, b + or c? Non esiste un’istruzione “confronta la coppia BC con zero”: si mette il byte alto in A e si fa l’OR con quello basso — il risultato è zero solo se entrambi sono zero.

Pseudo-codiceAssembly
if (A == 0) { … }or a + jr nz, dopo … dopo:
if (A < 10) { … } else { … }cp 10 + jr c, minore … jr fine minore: … fine:
for (i = 8; i > 0; i--) { … }ld b, 8 + etichetta + … + djnz
while (BC != 0) { … ; BC-- }etichetta + … + dec bc + ld a, b + or c + jp nz

Apri 03-led-contatore.asm (il contatore sui LED che nella UDA 0 era “scatola nera”) e rispondi con il Passo sotto gli occhi:

  1. quanti giri fa il ciclo att1 con BC = 0x0800? (2048)
  2. cosa succede al primo giro a BC? e quando si esce dal ciclo?
  3. perché nella subroutine ci sono push af / push bc all’inizio e i pop alla fine?

Consegna: modifica la costante (0x0800 → 0x2000) e descrivi come cambia il conteggio.

Apri esercizi/02-massimo.asm: il ciclo deve leggere i valori e aggiornare il massimo.

  • Verifica: con i valori 3, 9, 5 → A = 9; prova anche 9, 2, 7 → 9 e 1, 2, 3 → 3.
  • I tre valori stanno nelle celle 0x0010–0x0012: scrivili a mano in memoria (come nella UDA 1, attività D) oppure usa questa variante con i dati nel sorgente (richiede il segmento assoluto, come l’esempio del timer):
.seg CODE (abs)
.org 0x0010
valori: .db 3, 9, 5
.org 0x0100
ld a, (0x0010) ; massimo provvisorio
ld hl, 0x0011
ld b, 2
ciclo:
ld c, (hl) ; nuovo valore
cp c ; A >= C ?
jr nc, avanti
ld a, c ; no: nuovo massimo
avanti:
inc hl
djnz ciclo
halt

Cambia i tre valori nel .db e riassembla: il massimo deve seguire i dati.

Realizza un semaforo: prima la fase “verde” (LED 0 acceso) per qualche istante, poi la fase “rossa” (LED 1 acceso) per lo stesso tempo, in ciclo infinito. Usa un flag di fase in un registro (es. C), un ciclo contato e la pausa della Attività A.

Mostra una soluzione
ld c, 0 ; fase corrente: 0 = verde, 1 = rosso
fase:
ld a, c
or a
jr z, verde
; ---- fase rossa ----
ld a, 0x02 ; LED 1 = "rosso"
ld b, 8
rosso1:
out (0x20), a
call attesa
djnz rosso1
ld c, 0
jr fase
verde:
ld a, 0x01 ; LED 0 = "verde"
ld b, 8
verde1:
out (0x20), a
call attesa
djnz verde1
ld c, 1
jr fase
attesa: ; pausa (preserva A e BC)
push af
push bc
ld b, 0x10
rout:
ld c, 0x00
rin:
dec c
jr nz, rin
djnz rout
pop bc
pop af
ret

Regola il numero di ripetizioni (ld b, 8) e la costante della pausa per rendere il semaforo “credibile”.


A. Scegli il salto giusto (completa la condizione):

ObiettivoCodice
salta se A è zeroor a + jr ____ , posto
dopo cp 10, salta se A < 10cp 10 + jr ____ , posto
dopo cp c, salta se A ≥ C (il massimo resta)cp c + jr ____ , posto
djnz salta finché B…ripete finché B ≠ 0 (dopo il decremento)

B. Traccia i cicli — al termine, quanto vale A?

ListatoA finale
ld b, 4 / ld a, 0 / ciclo: inc a / djnz ciclo / halt
ld b, 0 / ld a, 0 / ciclo: inc a / djnz ciclo / halt
Mostra la soluzione

A. 1) jr z · 2) jr c · 3) jr nc · 4) finché B ≠ 0 (attenzione: decrementa prima di decidere, e con B = 0 conta 256).

B. Primo listato: inc a eseguito 4 volte → A = 4. Secondo listato: B = 0 → il primo decremento lo porta a 255, quindi il ciclo gira 256 volte: A incrementa da 0 a 255 e poi torna a 0 (con carry) → A = 0.


  1. Differenza tra JP e JR? Quando non puoi usare JR?
  2. Dopo un CP, quale flag guardi per “primo minore” (senza segno)? E per “uguale”?
  3. Cosa fa esattamente jr nc, avanti dopo un cp c?
  4. Perché DJNZ con B = 0 è insidioso?
  5. Perché è comodo che i salti non modifichino i flag?

📎 I file .asm di esempi ed esercizi si scaricano dalla pagina Materiali.

  • Compito: completa l’Esercizio 2, le due tracce e la tabella “scegli il salto”; porta il semaforo funzionante con una pausa scelta da te.
  • Pre-lettura: manuale, sezione Disassemblaggio (per vedere come JR diventa 18 xx).
  • Materiali: esempio 03-led-contatore.asm; esercizio esercizi/02-massimo.asm (+ soluzione).
  • Prossima unità: memoria, stack e sottoprogrammi (CALL/RET) — UDA 3.