Articolo pubblicato sul n. 28 di
MCmicrocomputer
(Edizioni Technimedia Srl - Roma) nel marzo 1984
EXMA
un assemblatore per VIC-20
(seconda parte)
di Andrea de Prisco
Nel
numero scorso, vi abbiamo presentato un potente assemblatore
per il VIC-20 espanso con 16K. Col listato pubblicato, è
possibile scrivere programmi in linguaggio macchina
sfruttando etichette, notazione decimale, ottale, binaria
nonché altre utility per rendere la vita un po' più facile a
chi si occupa di questo genere di programmazione. Per
semplificare ancora di più le cose, aggiungiamo al nostro
assemblatore le macro istruzioni definibili dall'utente.
È il tema di questa puntata. Le linee Basic presenti su
questo numero sono da aggiungere e/o sostituire al listato 2
del numero scorso. Loro compito è appunto quello di
permettere la creazione di macro nonché, chiaramente, la
loro utilizzazione nei programmi. L'assemblatore, tanto per
cambiare, provvederà a sostituire ogni istruzione definita
dall'utente, con il pacchetto di istruzioni elementari
direttamente eseguibili dal microprocessore.
Parametri attuali e parametri formali
Prima di entrare nel merito di macro istruzioni e affini, è
bene chiarire alcuni concetti riguardanti il passaggio di
parametri. Tanto per restare in intimità, senza quindi
scomodare linguaggi assai più evoluti come l'Algol e il
Pascal, chiamiamo in causa il caro amico Basic,
l'onnipresente.
Oltre alla semplicità d'uso, una delle caratteristiche più
interessanti di questo linguaggio è la possibilità di
definire funzioni tramite l'istruzione DEFFN. Supponiamo di
aver bisogno di una funzione che, preso un qualunque N,
restituisca la somma del suo quadrato e del suo doppio.
La definizione avviene col comando:
10
DEFFNF(N)=N*N+2*N
Anche se qualcuno non se ne sarà mai accorto, la N che
vediamo nella definizione di FNF non è la variabile N, che
dal canto suo può tranquillamente esser usata in qualsiasi
altra parte del programma. È un parametro formale che serve
solo per descrivere la funzione: ciò che si deve fare col
dato in ingresso.
Se alla linea 20 scriviamo:
20
N=150:A=FNF(3)
dopo l'esecuzione, N conterrà ancora 150 e ad A sarà
associato il valore 3*3+2*3 (= 15) e non N*N +2*N come
appare nella definizione. Il 3 di FNF(3) è il parametro
attuale, quello con il quale viene chiamata la funzione FNF(N).
In Algol e Pascal, la cosa si fa ancora più interessante: la
definizione di una funzione può anche essere lunga come un
intero programma, e il numero di parametri "passabili" non è
limitato a l come in Basic. .
Quando si ha la possibilità di definire a piacimento
procedure e funzioni, anche la programmazione cambia
aspetto. Generalmente, risolvere con un programma un
problema in Pascal, si riduce essenzialmente a scomporlo in
sottoproblemi di minore difficoltà, definendosi facilmente
tutte le procedure che interessano e, conseguentemente,
limitandosi a scrivere il programma come semplici chiamate
di quest'ultime.
Le Macroistruzioni
Programmando in assembler 6502, spesso capita di dover
ripetere più volte una stessa sequenza di istruzioni. Tanto
per citare qualche caso, l'incremento di un byte con
relativo riporto nel byte successivo o semplicemente
l'azzeramento di un determinato byte, sono sequenze, seppur
molto brevi, praticamente onnipresenti in programmi in
linguaggio macchina. Ed è un vero peccato che non siano
disponibili al livello hardware del microprocessore.
Per non parlare poi di casi leggermente più raffinati, come
la copia di un byte in un altro o lo scambio dei contenuti
di due celle di memoria o la moltiplicazione 8 x 8 bit, che
se fossero disponibili farebbero del 6502 una vera "bomba".
Ogni volta che ci servono, siamo costretti a scrivere per
intero la sequenza di istruzioni elementari che li
descrivono. Più interessante sarebbe definire una volta per
tutte queste sequenze standard e fare un semplice
riferimento ad esse tutte le volte che sia necessario,
eventualmente specificando i parametri su cui operare. In
altre parole definirsi la macro istruzione che descrive
l'istruzione assente a livello hardware.
Una macro altro non è che una piccola porzione di programma
con in testa un nome e una lista di parametri formali. Ad
esempio, la macro che descrive l'operazione di azzeramento
di un determinato byte è:
MACRO :CLR
M
LDA #0
STA M
Come nel caso del Basic, la M presente nella dichiarazione è
assolutamente formale: l'assemblatore, dopo questa
dichiarazione, è informato dell'esistenza di questa nuova
istruzione che si chiama CLR e che opera su un parametro.
Ogni volta che nel processo di assemblaggio viene incontrato
un CLR di qualche byte di memoria, viene automaticamente
sostituito con la sequenza di due istruzioni LDA #0 e STA
[byte specificato]. Senza alcuna restrizione per il modo di
indirizzamento. In questo caso, sono ammessi tutti i modi di
indirizzamento concessi dall'istruzione STA (che nella
dichiarazione usa il parametro M). Potremmo quindi usare "CLR
$1000", "CLR (44),Y", "CLR (12,X)" etc.
Facciamo un discorso un po' più operativo. Supponiamo di
aver già aggiunto al programma 2 del numero scorso le linee
Basic presentate in quest'articolo. Caricato e fatto
eseguire il programma DATA, diamo il RUN al secondo
programma.
Con SHIFT e ''l'', si va in fase di Input dopo aver ripulito
l'area di lavoro. Per far capire all'assemblatore che si sta
definendo una Macro è obbligatorio scrivere "MACRO" nel
campo Label della prima linea.
Si procede indicando, sempre nella prima linea, nel campo
OPR il nome della Macro e nel campo Address la lista dei
parametri, ognuno separato dal "Punto e virgola ".
Fa seguito la porzioncina di programma che descrive la Macro
da noi definita. Al termine, dopo essere tornati al MENU #1,
bisogna assemblare la Macroistruzione in modo da poterla
usare a nostro piacimento. Al termine di questa operazione,
l'assemblatore, col solito trucchetto del [RETURN] forzato
nel buffer di tastiera, inserisce fra le REM di testa la
definizione cifrata della Macroistruzione.
Nel numero scorso, per non confondervi troppo le idee, oltre
al "giallo della passata zero" (vedi riquadro per la
soluzione), vi sono state nascoste altre due possibilità
dell'EXMA. Una è il salto relativo, e si usa con i Branch
condizionali. Si specifica nel campo Label il numero di
istruzioni da saltare, in avanti col simbolo "> " o indietro
col simbolo "< ". L'istruzione:
BPL <
$03
salta indietro di tre linee se il BPL ha dato esito vero.
Sempre ad esempio:
BNE >$07
salta in avanti di 7 linee se è vero il BNE. L'altra
possibilità è la direttiva vuota. Si
indica con ".CO" (dal fortran-iano CONTINUE) e quando
l'assemblatore l'incontra, l'ignora del tutto e assembla la
linea successiva. Sembra l'arte dei pazzi, ma non lo è.
Specialmente l'ultima, è utile nelle definizioni macro,
quando vi è un'uscita brutale dal corpo della definizione.
Facciamo un esempio: definiamo una macro che pone
nell'accumulatore il massimo tra due oggetti. La definizione
è:
MACRO
:MAX ALFA;BETA
LDA ALFA
CMP BETA
BPL FINE
LDA BETA
FINE :.CO
La direttiva ".CO" è stata necessaria dato che la label FINE
(così come qualsiasi altra label) non può esistere se il
campo OPR non è occupato da qualcosa. La possibilità di
definire i Branch relativi è sfruttata dall'assemblatore
stesso nella fase di Macro Expansion. È questa fase che
precede l'assemblaggio: vengono sostituite a tutte le Macro
usate in un programma, le relative sequenze di istruzioni
elementari (foto 2, 3 e 4).
A titolo di esempio, vediamo ora qualche Macro di uso più o
meno comune:
MACRO :SWP
I;J
LDA I
PHA
LDA J
STA I
PLA
STA J
Scambia (SW AP) il contenuto di due celle di memoria.
È importante notare che nella definizione di una Macro,
possono starci sia istruzioni semplici, sia altre Macro
purché già definite. Supponiamo di definire una istruzione
che ordina in modo crescente due byte. Algoritmicamente ciò
significa che se ALFA e BETA sono due byte e ALFA > = BETA
non bisogna far nulla; se ALFA < BETA, bisogna scambiare i
contenuti di ALFA e di BETA. In termini di Macro
definizione:
MACRO :ORD
ALFA;BETA
LDA ALFA
CMP BETA
BPL EXIT
SWP ALFA;BETA
EXIT :.CO
Facciamo ora un esempio di Macro a tre parametri. Questa
istruzione pone in un determinato byte (RE) il resto della
divisione tra un byte dividendo (DD) e un byte diviso re
(DR):
MACRO :RES DD;DR;RE
LDA DD
LOOP :STA RE
SEC
SBC DR
BPL LOOP
Notare che tanto DD quanto DR possono essere celle di
memoria o numeri. RE deve essere necessariamente una cella
di memoria.
Potremmo ad esempio usare:
RES
$2234; #33; 3
che pone nella cella 3 il resto della divisione tra il
contenuto di $2234 e il numero 33; così come:
RES #50;
$5; $200
pone nel byte $200 il resto tra #50 e la cella di memoria 5.
Nulla ci vieta di essere ancora più contorti:
RES
($41),Y;(54,X); 482,Y
pone in "482,Y" il resto della divisione tra "($41),Y" e
"(54,X)".
In casi come questo, sorge però un piccolo problema: a causa
del limitato numero di colonne del VIC, può capitare che una
determinata chiamata di Macro non entri in una linea di
schermo per i troppi (o troppo contorti) parametri passati.
Niente paura: si può usare il campo Label della linea
seguente mettendo 3 puntini sospensivi nel campo OPR. La
chiamata di Macro sopra descritta, di fatto, va inserita in
memoria sotto forma di due linee; precisamente:
RES
($41), Y; (54,
... X); 482,Y
Ancora:
MACRO :MCD
I;J
PIPPO :ORD I;J
RES I;J;I
LDA I
BNE PIPPO
LDA J
pone nell'accumulatore il Massimo Comun Divisore tra due
celle di memoria. Provare per credere!
Dulcis in fundo:
MACRO :MUL
C;D
LDA #0
STA $0
LDX #8
LOOP :LSR C
BCC SKIP
CLC
ADC D
SKIP : ROR
ROR $0
DEX
BNE LOOP
LDY $0
esegue la moltiplicazione fra due byte, ponendo il risultato
a 16 bit nell'accumulatore (parte alta) e nel registro Y
(parte bassa). In questo caso, essendo presente nella
dichiarazione un LSR C, si impone che il primo parametro
nella chiamata di questa Macro sia una cella di memoria e
non un numero puro.
Avvisi e consigli
Ricordarsi che ogni definizione Macro è stivata in memoria
sotto forma di linea Basic, grazie a un [RETURN] forzato nel
buffer di tastiera. Ciò implica due considerazioni: primo,
se si definisce una nuova Macro, bisogna salvare nuovamente
il programma su nastro o su disco. Con l'inserimento di una
nuova Macro, conterrà una linea in più.
Secondo, non è illimitata la lunghezza di una definizione.
Oltre 16 o 17 linee, potrebbe non entrare, in forma cifrata,
in una linea Basic (lunghezza 88 chr).
I parametri di una Macro devono essere sempre separati da
"punto e virgola". Nei programmi, usare le Macro sempre con
lo stesso numero di parametri usato nella definizione.
È inutile dire che non è lecito un:
LDX 1428,X
o peggio, un gustosissimo
STA $48
In altre parole, controllare che in una chiamata di Macro i
parametri usati non combinino guai come sopra.
Notare che nelle Macro presentate come esempio in
quest'articolo, accumulatore e registri indice vengono
"sporcati". Chi non desidera ciò, salvi nello Stack detti
registri prima di usare una determinata Macro.
Speriamo di aver detto tutto! Arrivederci. Se avete problemi
vi preghiamo di non telefonare, ma scrivere!
Il Miniminiquiz (del numero scorso): la soluzione
Cosa avviene prima dell'assemblaggio? Dando
un'occhiata alla linea 1280, troviamo un DIM A$(170,2),
una SYS 19992 e subito dopo il programma di
assemblaggio vero e proprio che continuamente fa
riferimento al contenuto di A$(I,J), apparentemente
non inizializzato (nessuna istruzione di
assegnamento è presente in queste linee).
In una primitiva versione dell'EXMA, prima di
iniziare la fase di assemblaggio, una routine Basic
trasferiva il contenuto dell'area di lavoro nell'array
A$ (I,J) grazie ad un semplice FOR e a delle
istruzioni di PEEK. Lo svantaggio, tanto per
cambiare, era appunto l'esasperante lentezza: per
assemblare un qualsiasi programma, il 60% del tempo
totale era perso per inizializzare A$
(I,J).
La routine in linguaggio macchina posta
all'indirizzo 19992, pone rimedio all'inconveniente,
risolvendo il tutto in pochi decimi di secondo.
Per capire meglio il funzionamento, vediamo come il VIC organizza all'interno della sua memoria, la
gestione di un array di tipo stringa. L' Array
Header è il "descrittore" dell'array, ed è creato in
memoria all'atto del dimensionamento. Nel caso di
matrici a due indici (la nostra è 171 x 3), è
composto da 9 byte ed è immediatamente
seguito da
tanti Array Elements quanti sono gli elementi dell'array
(fig. 1). Ognuno di questi elementi è composto a sua
volta da tre byte, dei quali il primo indica la
lunghezza e gli altri due l'indirizzo dove la
stringa è "stivata ". Modificando opportunamente
ogni Array Element (all'atto del DIM tutti a zero),
si ha l'effetto, ritornando al Basic, che ogni
stringa non è vuota ma contiene il corrispondente
"atomo" di programma da assemblare. |
Impaginato
originale...
Articolo pubblicato
su
www.digiTANTO.it - per ulteriori informazioni
clicca qui
|