Assembler Z80 e Spectrum

Il processore Z80 (originariamente prodotto dalla Zilog) è un componente molto interessante da diversi punti di vista.
Nonostante la sua età è ancora molto utilizzato, ed è prodotto ancora, sotto varie forme, destinate a svariate applicazioni.
Ma l'interesse per questo componente risiede nel fatto che essendo abbastanza semplice da programmare e da utilizzare viene spesso utilizzato come supporto didattico nell'insegnamento della programmazione in assembler o dell'elettronica dei sistemi digitali. Non bisogna dimenticare inoltre che moltissime macchine del passato utilizzavano questo processore, questo significa che poterlo emulare su un computer attuale permette di fare girare gli stessi programmi che venivano usati sulle macchine originali (mi riferisco ad esempio a molti vecchi videogame)
Questa pagina è dedicata a questi ultimi aspetti.

Per iniziale a programmare il processore sono necessarie due cose, la prima è la documentazione sul set di istruzioni del processore. Un utile riferimento per iniziare è il seguente documento:

Set di istruzioni dello Z80

La seconda cosa che serve è un simulatore del processore, o di una macchina che lo utilizza.
Una soluzione interessante (solo per gli intrepidi) è utilizzare delle routine per emulare il processore e costruirvi un'applicazione attorno, magari cercando di simulare l'hardware di qualche macchina particolare. Questa soluzione è stata utilizzata dall'emulatore di videogames da sala MAME. La libreria utilizzata dal progetto MAME è la seguente:

Z80em Portable Z80 Emulator

Una soluzione più comoda è la seguente: tra le tante macchine dotate di Z80 c'è n'è una molto famosa in passato e molto utile al nostro scopo: lo ZX Spectrum! Esistono moltissimi simulatori per questa macchina, tra i migliori:
-
Z80 e WinZ80 di Gerton Lunter, che nella versione 4.0 per Windows contiene anche un debugger integrato
- Warajevo di Zeljko Juric e Samir Ribic, che è dotato di debugger e "compilatore"
E' facile trovarne una versione aggiornata su Internet...

Per iniziare a scrivere programmi in assembler è necessario anche un assemblatore che sia in grado di generare il codice macchina Z80 a partire da un sorgente scritto in assembler.
Uno tra i migliori assembler per Z80 (funziona anche per altri processori) è il
TASM, anche di questo programma è facile trovare una versione aggiornata.
Ovviamente prima da mettersi a scrivere un programma per una determinata macchina occorre conoscerne le caratteristiche dell'hardware e delle routine di sistema. Su questo punto ritornerò in seguito...

A questo punto sorge un problema: una volta che ho creato il codice macchina del mio programma utilizzando l'assemblatore come facciamo ad utilizzarlo nel simulatore?!? Sarebbe necessario copiare i byte del mio codice nella RAM simulata della macchina... Questo non è un problema da poco!
Esistono due soluzioni: una è utilizzare l'emulatore Z80 per DOS, che contiene una funzione apposita, però non dispone di un debugger interno.
Un'altra soluzione è utilizzare un mio programmino che aggira il problema in modo un pò diverso.
Gli emulatori infatti permettono di caricare e salvare delle "immagini" di memoria dello Spectrum su file (i formati più diffusi sono il .Z80 e il .SNA). Il mio programma non fa altro che generare un'immagine di memoria .SNA in cui è stato copiato il vostro programma. Caricando l'immagine dall'emulatore saprete che il vostro codice si trova alla locazione che avete specificato!

Scarica SpecLD1.zip

Esiste una terza possibilità abbastanza diversa: utilizzare un assemblatore che gira direttamente su Spectrum come il Champ, e usare quello come ambiente di sviluppo...

In definitiva, per fare funzionare i vostri programmi è necessario scriverli e salvarli come file di testo (ad esempio col Notepad), assemblarli con il TASM o altri assemblatori, ottenendo il codice oggetto, usare SpecLD per ottenere l'immagine di memoria (file .SNA) che contiene il vostro programma, specificando l'indirizzo di partenza (di solito 32768 [cioè $8000 esadecimale], SpecLD accetta solo indirizzi decimali...).
A questo punti basta lanciare il simulatore, caricare l'immagine .SNA ottenuta prima e digitare da BASIC dello Spectrum:
PRINT USR 32768
(in WinZ80 premere "P", Ctrl+Shift, "L" e digitare 32768)

 

Descrizione del sistema Spectrum 48K

Iniziamo dalla descrizione della mappa di memoria del sistema:

0000-3FFF:	ROM (16K)
4000-57FF:	Memoria grafica (display file)
5800-5AFF:	Mappa colori (attributes)
5B00-5BFF:	Buffer di stampa
5C00-5CB5:	Variabili di sistema
...
8000-????:	RAM libera
FF58-FFFF:	Caratteri grafici utente

Nei primi 16K si trovano le routine di sistema, che possono essere chiamate per svolgere particolari funzioni (senza bisogno di scriverle). Gli indirizzi delle varie funzioni sono date in seguito.
Il sistema mette a disposizione un’unica modalità grafica a bassa risoluzione (256x192 pixel) che è accessibile direttamente dall’area di memoria compresa tra gli indirizzi 4000h e 57FFh (detta "display file") in cui risiede la bitmap ad un solo bit che viene direttamente letta dall’adattatore grafico (il chip "ULA") per generare l’immagine video.
Traduzione in italiano: se scrivete un byte in quest'area di memoria sullo schermo vedrete una linietta orizzontale di 8 pixel consecutivi che assumono il colore dello sfondo o della "penna" a secondo che nel byte il bit corrispondente era 1 o 0.
Per fare questo basta il semplice codice in assembler:

LD	HL,$4000	; carica l'indirizzo del display file
LD	(HL),170	; 170 in binario vale %10101010
RET			; ritorna da dove aveva lasciato

Questo piccolo programmino disegna una linietta formata da pixel alternati (10101010) in alto a sinistra sullo schermo.
Si può memorizzare il programma a partire dall'indirizzo $8000 ad esempio.
Una nota importante: le aree dello schermo non sono mappate consecutivamente memoria! Lo schermo è diviso in "righe", "linee", "colonne" e "terzi di schermo" a cui corrispondono i seguenti bit dell'indirizzo di memoria:

010 00 000 000 00000	s	inizio
010 00 000 000 11111	s+31	fine riga 1 / linea 1
010 00 001 000 00000	s+256	inizio riga 1 / linea 2
010 00 000 001 00000	s+32	inizio riga 2 / linea 9
010 01 000 000 00000	s+2048	inizio riga 9 / linea 8*8+1 (secondo terzo)
|   |   |   |   |
fix |   |   |   |
  tezo  |   |   |
      scan  |   |
          riga  |
             colonna

Esempio di conversione da coordinate pixel:
(150, 137)

150/8 = 18 -> 10010 (colonna)
150mod8 = 6 (posizione)
(137/8)mod8 = 4 -> 001 (riga) 
137mod8 = 1 -> 001 (scanline)
137/64 = 2 -> 10 (terzo)	10001001
Mettendo assieme i valori:
010 10 001 001 10010
ossia i due byte dell'indirizzo: 01010001 00110010

Non è molto chiaro?!? Infatti... Per fortuna che ci sono delle funzioni di sistema per fare la conversione...

L’area di memoria successiva (da 5800h a 5AFFh) contiene invece gli "attributi di colore" che hanno la funzione di assegnare un colore ad ognuno dei due livelli della bitmap. Ogni byte determina il colore di un blocco di 8x8 pixel del display file. Quindi lo schermo risulta diviso in 32x24 blocchi adiacenti che possono assumere colori differenti.
I byte della memoria colori hanno il seguente formato:
· bit da 0 a 2: tre bit per il colore "inchiostro" (penna)
· bit da 3 a 5: tre bit per il colore "carta" (sfondo)
· bit 6: alta luminosità (0=off, 1=on)
· bit 7: lampeggiamento (0=off, 1=on)

I codici dei colori sono i seguenti:
000 (0): nero
001 (1):
blu
010 (2):
rosso
011 (3):
magenta
100 (4):
verde
101 (5):
cyano
110 (6):
giallo
111 (7): grigio/bianco

La locazione di memoria corrispondente un blocco di coordinate x,y (x max. 31, y max. 23) può essere calcolata con la seguente formula: $5800+x+32*y.
Per colorare la linietta di prima di rosso su sfondo blu basta aggiungere al programma:

LD	HL,$4000	; carica l'indirizzo del display file
LD	(HL),170	; 170 in binario vale %10101010
LD	HL,$5800	; carica l'indirizzo del display file
LD	(HL),10	 	; 10 in binario: %00 001 010
RET			; ritorna da dove aveva lasciato

Le aree di memoria successive contengono spazi vuoti destinati al buffer di stampa o memoria libera per i programmi, e alle locazioni 5C00-5CB5 sono contenute le variabili di sistema che sono utilizzate da sistema operativo per diversi scopi, e che possono essere molto utili quando si scrivono dei programmi (si veda il manuale dello Spectrum sotto).

 

Routine di sistema.

Nella ROM sono contenute molte routine utili, che possono essere chiamate dai programmi e che facilitano molto determinati compiti (come ad esempio scrivere sullo schermo...).
Ecco una lista di alcune delle funzioni più utili:

KEY-SCAN (call 02BFh)
Esegue una lettura della tastiera e imposta le variabili di sistema KSTATE e LASTK in funzione dei tasti premuti.
in uscita:
A = CODE tasti premuti = variabile KSTATE
BC - DE - HL modificati
IX non modificato
OPEN (call 1601)
Apre un canale di output. Bisogna eseguirla prima di una operazione di output.
in ingresso:
A = codice del canale
canale "K": codici 0, 1, 253. Corrisponde all'area inferiore dello schermo
canale "S": codici 2, 254. Corrisponde allo schermo superiore.
canale "P": codice 3. Corrisponde alla stampante.
in uscita:
modificati tutti i registri tranne AX
PRINT (rst 10h)
Permette di stampare un carattere sullo schermo qualsiasi (anche definito dall'utente) nella posizione corrente del cursore. E' un istruzione molto utilizzata ed infatti viene chiamata con un istruzione RST di un solo byte.

in ingresso:
A = CODE del carattere da stampare
in uscita:
A modificato, altri registri non modificati.
PPOS-SET (call 0DD9h)
Imposta la posizione del cursore sullo schermo riferito al canale precedentemernte aperto (S=scermo superiore, K=schermo inferiore, P=stampante).

in ingresso:
B = coord. riga = 24-num. riga = da 24 a 3 per "S", da 24 a 23 per "K", ignorato per "P"
C = coord colonna = 33-num. colonna = da 33 a 2
in uscita:
HL = indirizzo del corrispondnte row 0 nel display file
A e DE = num colonna risultante (0-31)
BC e IX non modificati
CLS (call 0DAFh)
Cancella lo schermo resettando anche la posizione del cursore.
in uscita:
A - BC - HL - DE modificati
IX non modificato
PRINT-STRING (call 203Ch)
Stampa una stringa di caratteri sullo schermo.
in ingresso:
DE = indirizzo di partenza della stringa
BC = lunghezza della stringa
in uscita:
DE = infirizzo di filne stringa + 1
A - BC modificati
IX - HL non modifiati

 

Per maggiori informazioni consultare il manuale dello Spectrum e il disassembly delle ROM:

Il manuale originale! (in inglese...)

Il disassembly commentato delle ROM (Se lo riuscite a leggere imparerete molto...)

Per concludere vi propongo il listato completo e la spiegazione di un programma per il gioco del Tetris che ho scritto con la collaborazione di alcuni miei colleghi (che tra l'altro saluto se passasero da quà...). Credo che sia molto utile studiarlo per imparare sia le tecniche di programmazione dello Z80, sia dello Spectrum.

Tetris per Spectrum 48K!

 

 

Per concludere ecco un link molto interessante:
http://www.gaby.de/z80/
E' possibile trovare moltissimo materiale su Z80, periferiche e software, tra cui un manuale dell Z80 in formato PDF.

 

 


Torna a casa!
Hosted by www.Geocities.ws

1