L’ISA (Istruction Set Architecture) dell’8086

// obiettivi di apprendimento
Conoscere la classificazione dell’ISA 8086 e le principali categorie di istruzioni
Usare correttamente le istruzioni aritmetiche MOV, ADD, SUB, MUL, DIV e comprendere il complemento a 2
Comprendere le istruzioni logiche, di shift/rotazione e la loro applicazione a maschere di bit
Implementare strutture di controllo (if/else, cicli) usando CMP, salti condizionati e LOOP
Capire il meccanismo degli interrupt e come la CPU gestisce l’I/O tramite INT
📄
Slides
ISA 8086
🔬
Lab
Assembly 8086 — Cheatsheet Completo
GitHub →

L’ISA — Instruction Set Architecture

L’ISA (Instruction Set Architecture) è l’insieme completo di istruzioni che un processore è in grado di eseguire — è il “vocabolario” della CPU. L’ISA dell’8086 definisce le istruzioni, i formati, i modi di indirizzamento e il comportamento dei flag: è il contratto tra il software e l’hardware.

📦
TRASFERIMENTO
MOV PUSH POP XCHG LEA
🔢
ARITMETICA
ADD SUB MUL DIV INC DEC CMP NEG
⚙️
LOGICA / SHIFT
AND OR XOR NOT SHL SHR ROL ROR
🔀
CONTROLLO FLUSSO
JMP Jcc LOOP CALL RET INT

I flag — il registro di stato FLAGS

Molte istruzioni non restituiscono il risultato solo in un registro — modificano anche i flag nel registro FLAGS. I flag sono bit singoli che memorizzano informazioni sul risultato dell’ultima operazione e guidano i salti condizionati.

FlagNomeSi attiva quando…Usato da
ZFZero FlagIl risultato è zero (o i due operandi di CMP sono uguali)JE, JNE, LOOPE, LOOPNE
SFSign FlagIl bit più significativo del risultato è 1 (risultato negativo in complemento a 2)JG, JL, JGE, JLE
CFCarry FlagC’è riporto/prestito fuori dal bit più significativo (overflow senza segno)JA, JB, ADC, SBB
OFOverflow FlagIl risultato non entra nel tipo con segno (overflow con segno)JO, JNO
PFParity FlagIl numero di bit a 1 nel risultato è pariControllo parità nelle comunicazioni seriali

Istruzioni di trasferimento dati

MOV — copia di dati

MOV è l’istruzione più usata dell’ISA — copia il valore del secondo operando nel primo. Non è uno spostamento: la sorgente rimane invariata. Non modifica i flag.

; MOV destinazione, sorgente
MOV AL, 34d      ; AL ← 34 decimale (0x22) — immediato
MOV AX, BX       ; AX ← BX — registro a registro
MOV AX, [6Ah]    ; AX ← Mem[DS:6Ah] — da memoria
MOV [0200h], BX  ; Mem[DS:0200h] ← BX — in memoria
// vincolo fondamentale
Non è mai possibile avere entrambi gli operandi in memoria nella stessa istruzione MOV. Per copiare da una locazione di memoria a un’altra servono due istruzioni: prima si carica in un registro, poi si scrive in memoria.

PUSH e POP — gestione dello stack

PUSH inserisce un valore a 16 bit in cima allo stack decrementando SP di 2, POP preleva il valore dalla cima incrementando SP di 2. Lo stack cresce verso il basso in memoria.

PUSH AX      ; SP ← SP - 2 · Mem[SS:SP] ← AX
PUSH BX      ; salva BX sopra AX
POP  BX      ; BX ← Mem[SS:SP] · SP ← SP + 2  (LIFO!)
POP  AX      ; AX ripristinato

XCHG e LEA

XCHG AX, BX         ; scambia AX e BX senza registro temporaneo
LEA  SI, [array]    ; SI ← indirizzo di 'array', NON il suo valore
LEA  DI, 04h[BX]    ; DI ← BX + 4 (indirizzo effettivo calcolato)
// LEA vs MOV — differenza fondamentale
MOV SI, [array] carica il dato contenuto in array. LEA SI, [array] carica l’indirizzo di array — equivale a &array in C. Indispensabile per passare puntatori a stringhe o buffer alle routine di I/O.

Istruzioni aritmetiche

ADD e SUB — addizione e sottrazione

MOV AL, 34d   ; AL = 0x22
ADD AL, 5d    ; AL = AL + 5 = 39d = 0x27
SUB AL, 10d   ; AL = 39 - 10 = 29d = 0x1D
// risultato negativo → complemento a 2
Se il risultato di SUB è negativo, la CPU lo rappresenta in complemento a 2. Il bit più significativo diventa 1 (SF = 1) e il valore binario corrisponde a 256 - |risultato| per operandi a 8 bit.
SUB AL, 50d   ; AL era 29d → 29 - 50 = -21d
; -21 in complemento a 2 su 8 bit:
; 21d = 0001 0101
; NOT  = 1110 1010  (complemento a 1)
; + 1  = 1110 1011  (complemento a 2) = EBh
; AL conterrà EBh — SF=1 (bit7=1 → numero negativo)

ADC e SBB — con riporto e prestito

Per somme e sottrazioni su operandi più grandi del registro (es. 32 bit su una CPU a 16 bit), si usano ADC e SBB che includono il valore del CF dell’operazione precedente.

; somma a 32 bit: (DX:AX) + (CX:BX) → risultato in (DX:AX)
ADD AX, BX    ; somma i 16 bit bassi — può generare carry
ADC DX, CX    ; somma i 16 bit alti + eventuale carry dal passo precedente

MUL — moltiplicazione

MUL moltiplica sempre usando AL o AX come operando implicito. Il risultato è sempre il doppio della dimensione degli operandi — per evitare overflow.

// operando a 8 bit
MOV AL, 50d   ; AL = moltiplicando
MOV BL, 10d   ; BL = moltiplicatore
MUL BL        ; AX ← AL × BL
; AX = 500d = 0x01F4
AL × operando → AX (16 bit)
// operando a 16 bit
MOV AX, 1000d
MOV BX, 500d
MUL BX        ; DX:AX ← AX × BX
; DX:AX = 500000d = 0x7A120
; DX=0007h · AX=A120h
AX × operando → DX:AX (32 bit)

DIV — divisione

DIV funziona in modo speculare a MUL — il dividendo è sempre il doppio della dimensione del divisore, e il risultato viene suddiviso tra quoziente e resto.

// divisore a 8 bit
MOV AX, 100d  ; dividendo in AX
MOV BL, 30d   ; divisore in BL
DIV BL
; AL = quoziente = 3d (0x03)
; AH = resto     = 10d (0x0A)
AX ÷ op8 → AL (quoziente) · AH (resto)
// divisore a 16 bit (100000 ÷ 100)
MOV DX, 1h    ; parte alta dividendo
MOV AX, 86A0h ; parte bassa (DX:AX=100000d)
MOV BX, 100d
DIV BX
; AX = 1000d = 0x03E8
; DX = 0 (resto)
DX:AX ÷ op16 → AX (quoziente) · DX (resto)

INC, DEC, NEG, CMP

INC AL     ; AL = AL + 1 (non modifica CF — utile nei cicli)
DEC CX     ; CX = CX - 1
NEG AL     ; AL = -AL (complemento a 2 di AL)

; CMP sottrae senza salvare il risultato — aggiorna solo i flag
MOV AL, 50d
MOV BL, 10d
CMP BL, AL  ; BL - AL = 10 - 50 = -40 → CF=1, SF=1, ZF=0
// CMP — come funziona internamente
CMP op1, op2 esegue internamente op1 - op2 ma scarta il risultato — aggiorna solo ZF, SF, CF e OF. Se op1 = op2: ZF=1. Se op1 < op2: CF=1, SF=1. Se op1 > op2: tutti a 0. Sempre seguita da un’istruzione di salto condizionato.

Istruzioni logiche e di shift

Operazioni bitwise — AND, OR, XOR, NOT

Le istruzioni logiche operano bit per bit tra i due operandi. Sono fondamentali per gestire flag di stato, maschere di bit e operazioni su singoli bit di un registro.

// AND — azzerare bit specifici (masking)
MOV AL, 0b11001010  ; AL = 0xCA
AND AL, 0b00001111  ; maschera nibble basso
; AL = 0b00001010 = 0x0A
; I bit alti sono stati azzerati
// OR — impostare bit specifici
MOV AL, 0b10110000  ; AL = 0xB0
OR  AL, 0b00000111  ; forza i 3 bit bassi a 1
; AL = 0b10110111 = 0xB7
// XOR — invertire bit / azzerare registro
XOR AX, AX   ; AX = 0 (idioma classico, più veloce di MOV AX,0)
XOR AL, 0FFh ; inverte tutti i bit di AL (complemento a 1)
// NOT — complemento a 1
MOV AL, 0b10110001
NOT AL
; AL = 0b01001110 (ogni bit invertito)

SHL, SHR, ROL, ROR — shift e rotazione

Lo shift sposta i bit verso sinistra o destra — i bit che escono da un lato vengono persi (o vanno nel CF). La rotazione sposta i bit ma quelli che “escono” da un lato rientrano dall’altro.

// SHL — shift left (moltiplica per 2ⁿ)
MOV AL, 5d     ; AL = 0000 0101
SHL AL, 1      ; AL = 0000 1010 = 10d  (×2)
SHL AL, 2      ; AL = 0010 1000 = 40d  (×4)
; SHL di N = moltiplicazione per 2ⁿ
// SHR — shift right (divide per 2ⁿ)
MOV AL, 40d    ; AL = 0010 1000
SHR AL, 1      ; AL = 0001 0100 = 20d  (÷2)
SHR AL, 2      ; AL = 0000 0101 = 5d   (÷4)
; SHR di N = divisione intera per 2ⁿ
; ROL — rotazione sinistra: il bit uscente a sinistra rientra a destra
MOV AL, 10110001b
ROL AL, 1   ; AL = 01100011b  (il bit7=1 è rientrato come bit0)

; ROR — rotazione destra: il bit uscente a destra rientra a sinistra
MOV AL, 10110001b
ROR AL, 1   ; AL = 11011000b  (il bit0=1 è rientrato come bit7)
// shift vs moltiplicazione — perché usarlo
SHL AL, 3 è equivalente a AL × 8 ma richiede 1 ciclo di clock invece dei molti cicli di MUL. Nei loop ad alte prestazioni, moltiplicazioni e divisioni per potenze di 2 si esprimono sempre con shift.

Controllo del flusso — salti e cicli

JMP — salto incondizionato

JMP carica una nuova label nell’instruction pointer — tutte le istruzioni tra JMP e la label vengono saltate e non eseguite.

MOV AL, 100d
MOV BL, 30d
JMP fine         ; salta direttamente a 'fine'
MOV AL, 35d      ; NON eseguita
MOV BL, 40d      ; NON eseguita
fine:
  MOV AL, 10d    ; esecuzione riprende da qui

CMP + salti condizionati — struttura if/else

La coppia CMP + istruzione di salto condizionato è il mattone base di ogni struttura di selezione in Assembly. CMP imposta i flag, il salto li legge.

// if (AL > BL) → ramo_vero else → ramo_falso
  MOV AL, 50d
  MOV BL, 30d
  CMP AL, BL       ; AL - BL → imposta flag
  JG  ramo_vero    ; salta se AL > BL (con segno)
  ; --- ramo_falso ---
  MOV CX, 0d       ; AL ≤ BL
  JMP fine_if
ramo_vero:
  MOV CX, 1d       ; AL > BL
fine_if:

Tabella dei salti condizionati

IstruzioneCondizioneFlag controllatiInterpretazione
JE / JZop1 = op2ZF = 1Uguale / Zero
JNE / JNZop1 ≠ op2ZF = 0Diverso / Non zero
JGop1 > op2ZF=0 e SF=OFMaggiore — con segno
JAop1 > op2CF=0 e ZF=0Maggiore — senza segno
JLop1 < op2SF ≠ OFMinore — con segno
JBop1 < op2CF = 1Minore — senza segno
JGE / JAEop1 ≥ op2SF=OF / CF=0Maggiore o uguale (con/senza segno)
JLE / JBEop1 ≤ op2ZF=1 o SF≠OF / CF=1 o ZF=1Minore o uguale (con/senza segno)
JNG / JNL ecc.NegazioneNegazione del corrispondentePrefisso JN nega l’istruzione base
// con segno vs senza segno — perché importa
MOV AL, 10d    ; AL = 0x0A
MOV BL, -30d   ; BL = 0xE2 (226 senza segno / -30 con segno)
CMP AL, BL

; Con segno   (JG/JL): AL=10 > BL=-30  → JG salta  ✓
; Senza segno (JA/JB): AL=10 < BL=226  → JA NON salta
Stessi bit, interpretazione diversa: la CPU non sa se i dati sono “con segno” o no — sei tu a scegliere l’istruzione di salto corretta in base al contesto del programma.

LOOP — cicli con contatore

LOOP è l’istruzione dedicata ai cicli a contatore. Usa CX come contatore implicito: decrementa CX e salta alla label se CX ≠ 0. Equivale a un ciclo for con CX iterazioni.

; somma 1+2+3+4+5 usando LOOP
  MOV CX, 5d    ; CX = contatore — eseguirò 5 iterazioni
  MOV AX, 0d    ; AX = accumulatore
ciclo:
  ADD AX, CX    ; AX = AX + CX (5, poi 4, poi 3, poi 2, poi 1)
  LOOP ciclo    ; CX-- · se CX≠0 → salta a 'ciclo'
; AX = 15d (1+2+3+4+5) · CX = 0
LOOP
Salta se CX ≠ 0 dopo decremento
LOOPE
Salta se CX ≠ 0 e ZF = 1 — preceduta da CMP
LOOPNE
Salta se CX ≠ 0 e ZF = 0 — preceduta da CMP

CALL e RET — subroutine

  CALL mia_funzione   ; salva IP nello stack · salta a mia_funzione
  ; ← esecuzione riprende qui dopo il RET
  MOV BX, AX         ; usa il valore restituito dalla funzione
  ...

mia_funzione:
  MOV AX, 42d        ; convenzione: valore di ritorno in AX
  RET                ; recupera IP dallo stack · torna al chiamante

Interrupt e I/O

I programmi Assembly devono interagire con tastiera, schermo e altri dispositivi. In x86 questo avviene attraverso il meccanismo degli interrupt software.

// cos’è un interrupt
Un interrupt è un evento che interrompe il normale ciclo fetch-decode-execute della CPU. In risposta, la CPU sospende il programma corrente, salva il contesto (IP, FLAGS) nello stack ed esegue l’interrupt handler (ISR). Al termine, il contesto viene ripristinato e il programma riprende esattamente dove era stato interrotto.
01
Il programma esegue INT N — N è il numero dell’interrupt (0–255)
02
La CPU salva FLAGS e IP nello stack, disabilita gli interrupt
03
La CPU consulta la Interrupt Vector Table (IVT) all’indirizzo 0000:0000h — ogni entry occupa 4 byte (2 per IP, 2 per CS) → handler N si trova all’offset N×4
04
La CPU salta all’handler e lo esegue — il servizio richiesto (I/O, sistema, grafica) viene effettuato
05
IRET ripristina IP e FLAGS dallo stack — il programma riprende esattamente dove era stato interrotto

INT 21h — i servizi DOS

L’interrupt più usato nei programmi DOS è INT 21h (il dispatcher dei servizi di sistema). Il servizio specifico da eseguire si seleziona caricando un codice in AH prima di chiamare l’interrupt.

AHServizioInputOutput
01hLeggi carattere da tastiera con echoAL = codice ASCII del tasto
02hScrivi carattere a videoDL = codice ASCII
09hStampa stringa a videoDS:DX = indirizzo stringa terminata da $
0AhLeggi stringa da tastiera con bufferDS:DX = indirizzo bufferBuffer riempito con la stringa
4ChTermina programmaAL = codice di uscita
; esempio completo: leggi un carattere e stampalo a video

  MOV AH, 01h    ; servizio: leggi carattere da tastiera
  INT 21h        ; AL ← codice ASCII del tasto premuto

  MOV DL, AL     ; preparo il carattere da stampare
  MOV AH, 02h    ; servizio: scrivi carattere a video
  INT 21h        ; stampa il carattere in DL

  MOV AH, 4Ch    ; servizio: termina programma
  MOV AL, 0      ; codice di uscita = 0 (OK)
  INT 21h
; stampa una stringa — il messaggio deve terminare con '$'
msg DB 'Ciao, mondo!', '$'

  MOV AH, 09h    ; servizio: stampa stringa
  LEA DX, msg    ; DX ← indirizzo di 'msg'
  INT 21h        ; stampa fino al carattere '$'

Riepilogo — quadro delle istruzioni ISA 8086

CategoriaIstruzioni principaliNota chiave
TrasferimentoMOV PUSH POP XCHG LEAMOV non modifica i flag · LEA carica indirizzi non valori
AritmeticaADD ADC SUB SBB MUL DIV INC DEC NEG CMPMUL: AL×op→AX · AX×op→DX:AX · CMP scarta il risultato
LogicaAND OR XOR NOTXOR reg,reg azzera il registro · AND per maschere
Shift/RotazioneSHL SHR ROL RORSHL/SHR: ×/÷ per potenze di 2 in 1 ciclo
Salto incond.JMP CALL RETCALL salva IP nello stack · RET lo recupera
Salto condiz.JE JNE JG JA JL JB JGE JLEJ* con segno (JG/JL) · J* senza segno (JA/JB)
CicliLOOP LOOPE LOOPNEUsano CX come contatore implicito — CX– poi controlla
InterruptINT N IRETINT 21h + AH = servizio DOS per I/O tastiera/video

Lascia un commento