               L'AMIGA E L'ASSEMBLER 68000
                     Seconda Puntata

  
                I Modi di Indirizzamento

   L'indirizzamento  degli  operandi nei processori  e'  uno
degli argomenti che rivestono la maggiore importanza ai fini
della  programmazione;  l'architettura  del  68000  permette
quattordici  diversi  tipi  d'indirizzamento  che, combinati
piu'  o  meno liberamente  con  le  cinquantasei  istruzioni
fondamentali, generano un set di istruzioni quanto mai vasto
e tuttavia facile da gestire.

   "Indirizzare un operando", sia in memoria che all'interno
del processore stesso (per esempio  un  registro), significa
calcolare l'indirizzo effettivo dell'operando per permettere
l'espletamento   dell'istruzione.  Alcune  istruzioni  hanno
l'operando espresso implicitamente; per esempio, RTS (Return
from  Subroutine),  equivalente  all'istruzione  omonima del
6502  ed al RET dello  Z80,  preleva  automaticamente  dallo
stack un  indirizzo (detto "di ritorno") che va a costituire
il nuovo valore  di  PC,  senza  che  si  debba  ogni  volta
specificare l'operando.  L'indirizzamento  implicito  e'  un
modo  d'indirizzamento un po' sui generis, riservato a poche
e ben  definite  istruzioni  (LINK/UNLK, CHK, DIVS/DIVU...);
non lo tratteremo quindi in  questa puntata, riservandoci di
tornare sull'argomento quando incontreremo le istruzioni che
lo prevedono.

   Vi sono anche due istruzioni  che  non  hanno operandi di
sorta:   NOP   (nessuna   operazione)  e  RESET,  istruzione
privilegiata  riservata  ai programmi che funzionano in modo
supervisore, che invia  alle  periferiche collegate al 68000
un comando di reinizializzazione, senza  alterare  lo  stato
del processore stesso.








   A parte queste due classi  d'istruzioni,  tutte  le altre
necessitano di uno o piu' operandi espressi tramite  un modo
d'indirizzamento,  tenendo chiaramente in conto le eventuali
limitazioni che alcune istruzioni pongono in questo campo. 

   L'istruzione piu' usata dal MC68000 e' l'istruzione

        MOVE <ea1>,<ea2>

dove  per  <ea>  consideriamo l'indirizzo effettivo a cui si
trova   l'operando   scelto.   Con   MOVE   si   trasferisce
semplicemente   un   operando  sorgente  <ea1>  all'operando
destinazione   <ea2>.   I   due   operandi   possono  essere
indirizzati  in  maniera  autonoma;  alcune limitazioni (che
vedremo) sono pero' imposte all'indirizzamento di <ea2>.







------------------------------------------------------------
                  INDIRIZZAMENTO ASSOLUTO
------------------------------------------------------------

   Utilizzando  l'indirizzamento  assoluto,   assegnamo   un
valore numerico agli operandi; con
       
       MOVE.L #$21BB,$FFC0

si trasferisce, a partire dall'indirizzo  $FFC0  in  poi, il
dato $21BB. In questo caso,  l'operando  indirizzato in modo
assoluto e' <ea2>.

In memoria otteniamo dunque:

      Indirizzo     Contenuto
        $FFC0          $00
        $FFC1          $00
        $FFC2          $21
        $FFC3          $BB


   Se avessimo tralasciato il carattere #, che identifica un
dato numerico immediato (indirizzato in  modo immediato), e,
con questa situazione di partenza

      Indirizzo    Contenuto
        $21BB         $AA
        $21BC         $BB
        $21BD         $CC
        $21BE         $DD

        $FFC0         $00
        $FFC1         $00
        $FFC2         $00
        $FFC3         $00

avessimo scritto

        MOVE.L $21BB,$FFC0

avremmo ottenuto il seguente risultato:


      Indirizzo    Contenuto
        $21BB         $AA
        $21BC         $BB
        $21BD         $CC
        $21BE         $DD
        
        $FFC0         $AA
        $FFC1         $BB
        $FFC2         $CC
        $FFC3         $DD

   Naturalmente  in  questo caso ambedue gli  operandi  sono
stati indirizzati in modo assoluto.









   Con   l'indirizzamento  assoluto  si  possono  utilizzare
indirizzi  a  16 bit (che vengono estesi a 32 bit mantenendo
il bit di  segno)  o  a  32  bit. Per questa ragione, alcuni
programmatori distinguono un indirizzamento assoluto "corto"
da  un  indirizzamento  assoluto "lungo"; questa distinzione
non e' generata comunque  dal  suffisso dell'istruzione .W o
.L,  che, ricordiamocelo, riguarda solamente  il  campo  del
dato da trasferire; si puo' dunque dare il caso di un MOVE.W
con <ea2>  a  piu'  di  sedici  bit, mentre di <ea1> vengono
considerati i sedici bit meno significativi.












------------------------------------------------------------
          INDIRIZZAMENTO DIRETTO A REGISTRO DATI
------------------------------------------------------------

   In questo modo l'operando e'  contenuto all'interno di un
registro dati Dn:

        MOVE.W D0,D1

trasferisce  i sedici bit meno significativi  (suffisso  .W)
contenuti nel registro D0 nei bit corrispondenti di D1. 
   In  questo  esempio  abbiamo  indirizzato  direttamente a
registro dati sia <ea1> che <ea2>.

   Ricordate che, tranne nel caso  di  un MOVE.L (32 bit), i
bit piu' significativi che rimangono al di  fuori  del campo
specificato non vengono alterati.





    Consideriamo per esempio:   D0=$AABBCCDD
                                D1=$11223344

    Dopo MOVE.B D0,D1 otteniamo D0=$AABBCCDD
                                D1=$112233DD

    Dopo MOVE.W D0,D1 otteniamo D0=$AABBCCDD
                                D1=$1122CCDD

    Dopo MOVE.L D0,D1 otteniamo D0=$AABBCCDD
                                D1=$AABBCCDD

    L'istruzione MOVE agisce sui Codici Condizione contenuti
nello Status Register (SR):
- il flag X (Extend) rimane inalterato;
- il flag N (Negative) riflette lo stato del bit piu' signi-
ficativo  dell'operando  oggetto  del  MOVE; se  tale bit e'
settato (cioe' posto a  1)  ed  il  valore   e'  considerato
insieme al suo segno, N indica che tale valore e' negativo.



  Se il valore del segno non e' per voi  importante,  potete
considerare  il  test  di  N   come   comodo   sostituto  di
un'istruzione di rotazione sinistra e  successivo  controllo
del  flag  di  Carry; 
- il flag Z (Zero)  viene  settato se l'operando oggetto del
MOVE e' stato uno zero; altrimenti viene resettato; 
- i flag V (Overflow) e C (Carry) vengono resettati.

   Molte  sono  le  istruzioni  tipo  MOVE  implementate nel
MC68000 (e che vedremo in seguito: MOVEA,  MOVEM  ecc.);  di
queste solo MOVE e MOVEQ  hanno riflessi sui flag, mentre le
altre li lasciano inalterati.










------------------------------------------------------------
        INDIRIZZAMENTO DIRETTO A REGISTRO INDIRIZZI
------------------------------------------------------------

   Questo modo  e' molto simile al precedente; la differenza
e' che gli  operandi  vengono  espressi  tramite  i registri
indirizzi An. 

        MOVE.L A0,D1

fa assumere a D1 il  valore corrente di A0. L'indirizzamento
diretto  a  registro indirizzi e' qui  applicato  ad  <ea1>,
mentre  <ea2>  e'   indirizzato   direttamente   a  registro
dati (precisazione forse un po'  ridondante  ma sempre utile
in un corso...).

   Volendo  usare  questo  nuovo  modo  d'indirizzamento per
<ea2>,  dobbiamo  abbandonare  l'istruzione MOVE per passare
all'omologa   MOVEA  (Move  Address);  la  sua  sintassi  ci
permette di usare qualsiasi modo d'indirizzamento per <ea1>,
e  solo  il  diretto a registro indirizzi per  <ea2>;  senza
inoltre modificare i contenuti dello Status Register.
   Cosi':

        MOVEA.L D1,A0

e' il contrario dell'esempio prima riportato; ma anche

        MOVEA.W A0,A1

e' un'istruzione perfettamente eseguibile.

   Non e' possibile comunque usare  MOVEA  in  formato  byte
(estensione .B). 

   A proposito di MOVEA e'  interessante  aprire  una  breve
parentesi. Alcuni programmatori ritengono non sia il caso di
differenziare   MOVE   da  MOVEA,  riconoscendo  la  seconda
istruzione quando <ea2> e' indirizzato mediante An. 
   Fin  qui,  nulla di male:  ognuno,  nell'intimita'  delle
proprie memorie, e'  libero  di  fare  cio' che crede: anche
andare contro gli standard e le sintassi riconosciute.


   I problemi sorgono quando questi  programmatori  decidono
di   distribuire  i  loro  testi  sorgente  o,  ancora  piu'
irresponsabilmente,  decidono  di  realizzare  un  programma
Assembler  e  commercializzarlo  (Kuma  Seka  Assembler, per
esempio),  obbligando cosi' anche altri a  seguire  sintassi
completamente errate.  Fate  sempre  un  check  del  set  di
istruzioni  riconosciute  dal vostro Assembler, la cui lista
dovrebbe  essere  sempre   inclusa   nel   manuale   utente,
preferendo,  quando potete,  programmi  che  rispettano  gli
standard e  le  sintassi,  fondamento  irrinunciabile  della
portatilita' dei sorgenti.











------------------------------------------------------------
                 INDIRIZZAMENTO IMMEDIATO
------------------------------------------------------------

   Abbiamo gia'  intravisto  questo indirizzamento nel primo
esempio fatto per l'indirizzamento  assoluto;  l'operando e'
un valore preceduto dal simbolo #

        MOVE.W #$FFDD,D1

inizializza la parola bassa, o  word meno significativa, del
registro D1 con il valore esadecimale $FFDD. 
   A costo di sembrare pedanti  o  paranoici,  specifichiamo
che in questo esempio e' <ea1> ad essere indirizzato in modo
immediato.   Non   e'   possibile,  con  l'istruzione  MOVE,
indirizzare allo stesso modo anche <ea2>.
   Prestate  attenzione  all'estensione  di segno  del  dato
numerico, che avviene automaticamente  nel caso in cui <ea2>
sia un registro dati; "estendere  di  segno"  un byte od una
word  vuol  dire  replicare  il contenuto del suo  bit  piu'
significativo (il bit 7 ed  il  bit  15  rispettivamente) in
tutti   i  bit  del  registro  piu'  a  sinistra  del  campo
specificato.  Esempio:  se un byte %10010000 viene messo nel
registro D1, la  sua  estensione  di  segno  portera'  D1  a
contenere  il  valore  %11111111 11111111 11111111 10010000;
quindi l'istruzione MOVE vista  sopra  avra', nella pratica,
l'effetto  di  inizializzare  D1 con $FFFFFFDD.  Notate  che 
questo non e' altro che un complemento a due, necessario per
poter rappresentare un intero negativo in base binaria. 

   Un'ultimo consiglio per un corretto uso  di  questo  modo
d'indirizzamento:   quando   dovete   usare  delle  costanti
all'interno  dei vostri programmi, e' preferibile che queste
abbiano un nome esplicativo della loro funzione:
 
      CheckSum EQU $326F0411
               .
               .
               MOVE.L #CheckSum,D3
               .

risulta molto piu' leggibile del corrispondente

    MOVE.L #$326F0411,D3
e permette, in caso di successive correzioni, di intervenire
una sola volta sull'EQUate senza  dover  spuntare  tutto  il
sorgente per aggiornare il valore.


------------------------------------------------------------
            INDIRIZZAMENTO IMMEDIATO RAPIDO
------------------------------------------------------------

   Sostanzialmente  uguale  al  modo  appena  visto,  questo
indirizzamento se  ne  distingue  per  l'uso riservato a tre
sole istruzioni "quick": MOVEQ, ADDQ e SUBQ. 
  
   La prima permette di caricare un  registro  dati  con  un
valore  in  formato byte; tale byte e' considerato con segno
(bit 7: 0  positivo,  1  negativo), e viene esteso a tutti i
rimanenti 24 bit.





   ADDQ e SUBQ operano  somme  e  sottrazioni  su registri e
locazioni di memoria di valori compresi tra 1 e 8 (%00000001
e %00001000); sostituiscono, nel set d'istruzioni del 68000,
le istruzioni di incremento e decremento INC e  DEC presenti
sullo Z80 e sul 6502.


------------------------------------------------------------
       INDIRIZZAMENTO INDIRETTO A REGISTRO INDIRIZZI
------------------------------------------------------------

   Dopo aver visto  i  modi  di  indirizzamento  diretti del
68000,  entriamo   ora   nel  piu'  vasto  campo  di  quelli
indiretti.  In  questa puntata considereremo comunque solo i
modi  indiretti  piu'  semplici,  riservandoci  di  esaurire
l'argomento con la prossima puntata del corso.

   Iniziamo con il modo indiretto a registro indirizzi, modo
che trova corrispondenza anche nei processori ad 8 bit:

        MOVE.L (A7),D0

trasferisce in  D0 tutti i trentadue bit che, in memoria, si
trovano a partire  dall'indirizzo  espresso  da A7; si dice,
cioe',  che  A7  e' un puntatore all'indirizzo dell'operando
effettivo. Esempio:

Valore di A7=$FF00; Contenuto indirizzo $FF00: $40
                                        $FF01: $12
                                        $FF02: $CC
                                        $FF03: $3E

il  valore di D0 dopo l'istruzione considerata e' $4012CC3E.

   Prestate attenzione al fatto che, nonostante si sia usato
A7,  registro  dedicato  normalmente  a  funzioni  di  stack
pointer,   l'istruzione  sopra  riportata  NON  e'  un  modo
corretto di prelevare un valore  dalla  cima dello stack. Il
registro  A7,  infatti,  mantiene  costante  il  suo  valore
(nell'esempio  $FF00)  anche  DOPO  l'esecuzione dell'istru-
zione; chi proviene dal mondo  degli  8 bit ricordera' che i
famosi PUSH e POP (o PHA e PLA) aggiornavano invece anche lo
stack pointer, con decrementi od incrementi appropriati.

   Si  usera' comunque MOVE.L  (A7),D0  per  leggere un dato
presente  sulla  cima  dello stack nel caso  serva  lasciare
intatto A7; allo stesso  modo,  per  forzare un valore sullo
stack (per esempio per cambiare un indirizzo di ritorno), si
potra'  far  ricorso  a  MOVE.L  D0,(A7): a questo trucco e'
meglio pero' che facciano ricorso solo  programmatori di una
certa esperienza.

   Per  gestire lo stack pointer  in  maniera  corretta,  si
ricorre invece ai due modi d'indirizzamento che seguono.












------------------------------------------------------------
       INDIRIZZAMENTO INDIRETTO A REGISTRO INDIRIZZI 
                     CON PREDECREMENTO
------------------------------------------------------------

   Se  pensate  che sia barbaro usare siffatti nomi  per  un
modo d'indirizzamento... bene, avete ragione! 
   Guardiamo   comunque   alla   sostanza   delle   cose,  e
ringraziamo  i  progettisti  del 68000 per averci fornito un
modo potente ed efficace  per  la gestione di tabelle dati e
di liste.
   La  lista  piu' importante del processore  e'  lo  stack,
normalmente  puntato  dal  registro  A7;  ovviamente  potete
costruire piu' stack  all'interno  dello  stesso  programma,
usando    diversi   registri   indirizzi;   oppure,   potete
tralasciare (ma fino ad un  certo punto, come vedremo oltre)
A7, ed impiegare un diverso  registro  indirizzi  come stack
pointer:  la  liberta'   in  questo senso e' ampia, anche se
non assoluta.



   Allo stesso  modo  di  uno  stack,  potete  poi formare e
gestire le vostre tabelle dati, naturalmente usando registri
indirizzi  liberi;  negli  esempi  che  seguono parleremo di
stack, ma ricordate che gli stessi  si possono applicare per
altre strutture di dati.

   Rispetto   all'indirizzamento   indiretto   semplice,  il
predecremento   sottrae   dal registro indirizzi  un  valore
legato al campo  dell'operazione (pari al numero di bytes di
tale campo: 1 per .B, 2 per .W e 4 per .L), considerando poi
questo  registro  decrementato come  puntatore  all'operando
effettivo.

        MOVE.W D0,-(A7)

decrementa di due  bytes  il  valore  corrente  dello  stack
pointer,  ponendo  quindi  il  registro  dati  D0  al  nuovo
indirizzo di memoria puntato da A7.




   Vediamo un dump della memoria con A7=$DFFE e D0=$8012:

      Indirizzo Contenuto
        $DFF9      $FF
        $DFFA      $FF
        $DFFB      $FF
        $DFFC      $FF
        $DFFD      $FF
   A7-> $DFFE      $FF

dopo MOVE.W D0,-(A7) la situazione e' la seguente:

      Indirizzo Contenuto
        $DFF9      $FF
        $DFFA      $FF
        $DFFB      $12
   A7-> $DFFC      $80
        $DFFD      $FF
        $DFFE      $FF
con A7=$DFFC e D0 invariato; quello che abbiamo ottenuto non
e'  altro  che  un push di D0, ovvero un "salvataggio" di D0
sullo stack.
   Un altro uso interessante di questo modo d'indirizzamento
e' legato al trasferimento ed alla copia di stack e tabelle;
per riportare n words  di stack in un altro stack puntato da
A6, possiamo far eseguire al processore

        MOVE.W -(A7), -(A6) 

all'interno di un loop di n cicli.

   Quando  progettate  uno  stack  all'interno  di un vostro
programma, non dimenticate che nessun controllo verra' fatto
dal  sistema di Amiga sulle dimensioni  dello  stesso;  puo'
quindi essere utile, in certi casi, effettuare dei check sul
valore dello  stack  pointer  per  evitare spiacevoli crolli
della memoria; inoltre non allocate le vostre strutture dati
all'interno dello stack principale del programma: utilizzate
piuttosto altri registri indirizzi per formare  altri  stack
"privati".




------------------------------------------------------------
       INDIRIZZAMENTO INDIRETTO A REGISTRO INDIRIZZI
                    CON POSTINCREMENTO
------------------------------------------------------------

   Il prelevamento, o lettura, di  un  valore  dalla cima di
uno  stack  si  effettua  con questo modo di indirizzamento,
reciproco del precedente: 

        MOVE.L (A7)+,D0

pone in D0 il contenuto  della  long  word  puntata  da  A7,
incrementando quindi A7 di quattro bytes. Cosi', con D0=$0 e
A7=$DFFA ed un dump

      Indirizzo Contenuto
A7->    $DFFA      $5D   
        $DFFB      $E6   
        $DFFC      $00   
        $DFFD      $42
        $DFFE      $6A
        $DFFF      $12
dopo l'istruzione vista, otteniamo

D0=$4200E65D e A7=$DFFE, mentre il dump della memoria rimane
invariato.

   Anche  per  questo  modo di indirizzamento vale la regola
dell'incremento proporzionato al campo  dell'operazione:  in
questo  caso,  con  un  campo .L, sono stati sommati quattro
bytes: si puo' comunque ricorrere  anche  ai  campi .W e .B,
tranne in un solo, importante caso.

   Per i progettisti del 68000, il registro A7  viene sempre
considerato  il puntatore allo stack di sistema; ed anche se
per  il nostro programma preferiamo  usare  ad  esempio  A4,
tutte le  istruzioni del processore che interessano lo stack
(istruzioni di salto o ritorno da subroutine) fanno continuo
riferimento ad A7.  Per  questo motivo, quando oggetto di un
predecremento o di un postincremento  e'  il registro A7, ed
il campo dell'istruzione e' un singolo byte,  la sottrazione
o  la  somma  vengono  automaticamente  portate ad una word:
questo  per  impedire  che lo stack  possa  trovarsi  ad  un
indirizzo dispari.
   Questo non significa che il  campo minimo debba essere di
sedici bit: e' solo l'aggiornamento dello  stack pointer che
viene portato ad una word. Consideriamo A7=$DFFA e D0=$0000,
e manteniamo il dump precedente; dopo

        MOVE.B (A7)+,D0

A7  sara'  uguale  a  $DFFC   (indirizzo  pari),  mentre  D0
conterra' $005D (e non $E65D).  Il  campo  specificato di un
byte  e'  stato dunque soddisfatto, dato che l'operazione ha
interessato   solo   gli   otto   bit   meno   significativi
dell'operando D0.

   Allo stesso modo si comporta un'istruzione

        MOVE.B (A7)+,(A4)+

A4 viene incrementato  di  un byte, mentre A7, essendo stack
di sistema, viene incrementato di una word.



   Tenete ben presente la particolarita'  di  A7, nonche' la
sua  irrinunciabilita' ad essere  un  puntatore  allo  stack
usato dal  MC68000  per  scrivere e leggere gli indirizzi di
ritorno, quando pianificate l'uso dei  registri per scrivere
il vostro software.  Quando  il processore si trova in stato
supervisore, A7 non punta piu'  comunque al normale stack di
sistema,  bensi'  allo  stack supervisore (SSP);  per  poter
accedere allo stack utente   si puo' ad ogni modo utilizzare
l'istruzione privilegiata MOVE USP; vedremo nel dettaglio il
comportamento dei vari tipi di  stack  quando tratteremo del
modo supervisore del 68000.

   Un'altra  particolarita',  anche  se  di  minor  rilievo,
interessa  la velocita' di esecuzione delle istruzioni MOVE:
il tempo richiesto da

        MOVE.L Dn,(An)   MOVE.L An,(An)
        MOVE.L Dn,(An)+  MOVE.L An,(An)+
        MOVE.L Dn,-(An)  MOVE.L An,-(An)

e'   sempre   identico   nel   MC68000    e   nel   MC68008:
rispettivamente  12  e  24  cicli   di  clock  per  eseguire
l'istruzione   considerata.   Nel   MC68010,  impiegato  con
facilita'  su  Amiga  come  "passo avanti" rispetto alla CPU
standard, questo tempo e' di  12  cicli di clock, tranne nel
caso in cui l'operando destinazione sia indirizzato  in modo
indiretto   con   predecremento:  in  questo  caso  i  cicli
richiesti salgono a 14.

   Infine, combinando il predecremento con il postincremento
si possono trasferire comodamente liste di diverso tipo, per
esempio per copiare dati da  una  lista  di tipo LIFO ad una
gestita  come FIFO senza alterare l'ordine di estrazione dei
dati:

        MOVE.L -(A7),(A6)+

pone il primo valore in cima allo stack alla base dello heap
puntato da A6: questo stesso valore sara' il primo ad essere
letto, sia nella struttura stack  (LIFO) che nella struttura
heap (FIFO).



   Concludiamo qui la seconda parte del corso  per  evitarvi
pesanti  quanto   inutili   indigestioni   di   "elle-emme";
ripassatevi con  calma  i  concetti  piu'  importanti  degli
argomenti  affrontati,  senza  tuttavia preoccuparvi se fate
confusione tra i diversi modi: una vera  confidenza  con  il
processore si ottiene solo dalla pratica, e presto, esaurita
questa  parte teorica, passeremo a lavorare direttamente sul
68000 e sul S.O. di Amiga.

   

                                     Steed Kulka

