sabato 14 luglio 2018

Dal Linguaggio Macchina All'Assembly: Istruzioni Principali

Per rendere più facile la vita al programmatore, ad ogni istruzione linguaggio macchina (ovvero una lunga sequenza di 0 ed 1) è stato dato un nome simbolico mnemonico.
L'insieme dei codici mnemonici prende il nome di linguaggio "assembly".
Il programma in assembly può essere scritto con un qualsiasi editor di testo (come il blocco note di Windows).
Tramite un programma assemblatore (assembler) poi si convertono le istruzioni assembly in codice macchina direttamente eseguibile.
Per ogni istruzione assembly esiste una sola sequenza binaria in codice macchina.
In generale un programma scritto in linguaggio macchina usa poca memoria, è veloce nell'esecuzione perchè può accedere completamente alle risorse fornite dal micro.
Ovviamente l'assembly ha diversi svantaggi, che sono di solito quelli che spingono ad utilizzare linguaggi di alto livello come il C, Cobol, Fortran o il Basic: i programmi sono più difficili da scrivere, interpretare e correggere, richiedono molte istruzioni anche per effettuare operazioni semplici, ed è molto difficoltoso effettuare calcoli.
In questo articolo (assolutamente non esaustivo) vedremo le principali istruzioni assembly.


HARDWARE
Essendo l'assembly strettamente legato all'hardware, le sue istruzioni risentono dei limiti (o delle potenzialità) offerte dall'hardware stesso.
Una minima conoscenza dell'hardware è quindi indispensabile.
Ad esempio micro con architettura a 8 bit possono lavorare direttamente solo con numeri rappresentabili in 8 bit (valori compresi tra 0 e 255).
Hanno inoltre una memoria per le istruzioni del programma separata dalla memoria dati (RAM).
La prima è di tipo flash, riprogrammabile elettricamente.
La RAM invece contiene tutte le informazioni di lavoro necessarie durante l'esecuzione del programma, ma a differenza della prima si cancella ogni volta che il computer viene spento.
In questi micro ogni cella della RAM può essere pensata come un registro a 8 bit in cui salvare e leggere i nostri dati.
Le locazioni di memoria, sia programma che dati, hanno un indirizzo crescente che parte da 0.
In particolare le prime locazioni della RAM prendono il nome di SFR (special function registers), e servono per controllare il funzionamento dell'hardware.
Attraverso i registri della sezione SFR si possono anche attivare ed usare le diverse periferiche interne, come i timers, il convertitore analogico/digitale (ADC), la memoria EEPROM, etc
Le aree di memoria su cui si può agire da programma sono i registri della memoria dati e il registro accumulatore W, che non fa parte dell'area dati ma è un ulteriore registro hardware specializzato, usato nelle operazioni aritmetico logiche.
La RAM è inoltre suddivisa in due o più banchi quindi durante l'esecuzione di un programma è importante sapere sempre quale banco si sta utilizzando.


LINGUAGGIO MACCHINA E ASSEMBLER
Il linguaggio macchina è quanto di più vicino ci sia all'hardware.
Dal punto di vista fisico ogni istruzione è una sequenza binaria di livelli elettrici in grado di attivare in modi differenti i circuiti interni.
Le istruzioni assembler sono solo una forma mnemonica comoda per descrivere  le sequenze binarie che danno luogo alle funzioni logiche che vogliamo far eseguire al micro, tra tutte quelle che è fisicamente in grado di eseguire.
Le istruzioni sono le operazioni elementari che è possibile usare per manipolare i bit delle sue informazioni in modo da arrivare al risultato voluto.
In particolare però le istruzioni aritmetiche considerano effettivamente i registri come byte (8 bit)  che contengono un valore numerico da 0 a 255 codificato in forma binaria.


SISTEMA DECIMALE, BINARIO, ESADECIMALE
Riguardo al sistema usato, al posto del canonico sistema decimale con potenze di 10 (1, 10, 100, etc) abbiamo potenze di 2 (1,2,4 etc) e le cifre invece di poter assumere valori da 0 a 9 possono essere solo 0 e 1 (da qui il nome bit: binary digit).
La cifra meno significativa è chiamata D0 o bit 0  (LSB) e ha peso 1, quella più significativa è chiamata  D7 o bit 7 (MSB) ed ha peso 128.
Si può calcolare che se tutti i bit fossero a 1 la somma darebbe esattamente 255, cioè il massimo valore codificabile con 8 bit.
Abbiamo una terza rappresentazione quella esadecimale che è molto usata perché una cifra (digit) esadecimale rappresenta esattamente 4 bit binari (un nibble).
Con due cifre esa da 00 a FF si rappresenta l'intero range di valori codificabili con 8 bit (1 byte).
Le cifre possono assumere tutti i valori compresi tra 0 e 15, e i valori tra 10 e 15 vengono indicati con le lettere dalla A alla F.
L'esadecimale è molto usato per rappresentare in modo compatto i valori binari contenuti in memoria e il valore degli indirizzi di memoria, e anche perché permette di passare rapidamente alla notazione binaria.
In questo caso si tratta solo di convenzioni (come lo sono anche quelle dei linguaggi di alto livello, come il C, C++, Cobol, Java, Javascript, Fortran, etc) create per facilitarci la lettura dei valori e la scrittura del programma, i circuiti del micro a livello fisico lavorano solo e sempre con livelli binari, cioè assenza o presenza di tensione.


ASSEGNAZIONE DI VALORI
La prima importante categoria di istruzioni è composta perciò da quelle istruzioni che permettono di assegnare valori ben precisi ai registri, e di spostare questi valori da un registro all'altro.
Per esempio l'istruzione

       MOVLW    174

fa assumere all'accumulatore W il valore 174 (binario 10101110).
Siccome l'assemblatore accetta anche numeri scritti direttamente in binario o in esadecimale è possibile scrivere anche:

       MOVLW    10101110B
       MOVLW    0xAE
       MOVLW    0AEh

Il PIC 16F628 nel banco RAM 0 dispone di un'area dati liberamente usabile per memorizzare i propri valori, quest'area parte dall'indirizzo  32 (20h). Quindi se supponiamo che sia attivo il banco 0 e vogliamo trasferire il contenuto dell'accumulatore nella cella (registro) 32 dobbiamo scrivere:

       MOVWF    32

Sarebbe molto scomodo però doversi ricordare a memoria gli indirizzi di tutte le celle che ci interessa usare, per facilitare il compito l'assemblatore accetta la definizione di un nome simbolico per i valori e gli indirizzi usati nel programma tramite la "direttiva di compilazione" EQU:

     DARKSPACE  EQU      32

     MOVWF    DARKSPACE

In questo modo è possibile assegnare un nome comodo da ricordare (DARKSPACE) ad ogni cella.
Come si può vedere non esiste alcun modo per assegnare in un colpo solo un valore ad un registro, ma occorre sempre passare per l'accumulatore usando quindi due istruzioni.



ESEMPIO PROGRAMMA
La prima parte del programma è detta header e contiene informazioni specifiche per l'assemblatore.
Il programma vero e proprio è composto solo dalle 6 righe centrali racchiuse tra la testata e l'end finale. Nella testata si indica all'assemblatore il tipo di micro usato, la base di default in cui vanno considerati scritti i numeri, si include un file di definizioni EQU che permette di assegnare automaticamente un nome a tutti i registri di uso comune (come per esempio TRISB e STATUS), si definisce la configurazione hardware per il funzionamento del micro (in questo caso per esempio si predispone il funzionamento con clock interno a 4MHz, 16 pin di I/O e WDT disattivato).
Org 0 indica l'indirizzo di partenza a cui andranno caricate le istruzioni nella memoria programma (il micro all'accensione inizia ad eseguire le istruzioni partendo dall'indirizzo 0), e l' END finale indica all'assemblatore la fine del programma.

    PROCESSOR 16F628
    RADIX  DEC
    INCLUDE "P16F628.INC"
    __CONFIG  11110100010000B
    ORG  0

    BSF STATUS,RP0; Attiva banco 1
    CLRF TRISB; Rende PORTB un'uscita
    BCF STATUS,RP0; Ritorna al banco 0
    MOVLW 10101110B; Carica 174 nell'accumulatore
    MOVWF PORTB ; Mandalo sui pin di uscita
    SLEEP; Stop programma

    END

Se il programma funziona, appena si fornisce alimentazione i LED devono accendersi coerentemente al valore binario impostato in W, rammentando che la cifra meno significativa (LSB) si trova sul pin RB0, mentre quella più significativa (MSB) su RB7.
Un gruppo di 4 bit si chiama nibble.


ISTRUZIONI
L'istruzione SWAPF scambia tra di loro i 4 bit meno significativi di un registro con quelli più significativi.
Anche in questo caso il risultato può essere rimesso nel registro di partenza oppure in W a seconda del valore che si da al parametro d.
Le istruzioni MOVLW e MOVWF non alterano il flag Z.
L'istruzione MOVF invece modifica il flag Z, che risulta settato (s=set=1) se il valore caricato è 0, e resettato (c=clear=0) negli altri casi.
Una MOVF può trasferire il valore nella stessa locazione da cui viene letto, il suo valore perciò non cambia, ma, visto che il flag Z viene modificato, è un modo rapido per verificare se contiene zero.
L' istruzione SWAPF scambia i nibbles (i 4 bit superiori e i 4 bit inferiori) di un registro, e deposita il risultato nell'accumulatore o nella locazione stessa da cui è stao prelevato.
Le istruzioni CLRF e CLRW permettono un caricamento diretto del valore 0 in una locazione dati o nell'accumulatore.

ADDLW  n  C Z  W = W + n
ADDWF  reg,d   C Z d = W + (reg)
SUBLW  n  C Z W = n - W
SUBWF  reg,d  C Z d = (reg) - W
INCF   reg,d    Z d = (reg) + 1
DECF   reg,d    Z  d = (reg) - 1

La seconda grande categoria di istruzioni è quella aritmetica, grazie ad esse il micro ha la possibilità di effettuare dei calcoli o di confrontare dei valori.
I PIC delle famiglie 12F e 16F sono in grado di sommare, sottrarre, incrementare e decrementare valori a 8 bit (compresi tra 0 e 255).
La prima, somma semplicemente un valore "n" (compreso tra 0 e 255) all'accumulatore W.
La seconda invece somma l'accumulatore con un registro, e il risultato viene come sempre posto dove specificato con il parametro d.
Le istruzioni di sottrazione sono un pò diverse da quelle di altri tipi di assembly, infatti qui è sempre l'accumulatore ad essere sottratto:

      SUBLW 15           significa: W = 15 - W
      SUBWF DARKSPACE,W      significa: W = DARKSPACE - W
      SUBWF DARKSPACE,F      significa: DARKSPACE = DARKSPACE - W

Le ultime due istruzioni (INCF e DECF) incrementano o decrementano di 1 il valore contenuto nel registro specificato, il risultato viene posto dove specificato con d.
Queste istruzioni settano flag Z se il risultato dell'operazione è 0.

   MOVLW   200       Carica 200 nell'accumulatore
   MOVWF   DARKSPACE    Lo mette nel registro DARKSPACE
   MOVLW   170       Carica 170 nell'accumulatore
   ADDWF   DARKSPACE,W   Lo somma con il valore di DARKSPACE
   MOVWF   PORTB     Lo manda sui pin di uscita

Per quanto riguarda le istruzione logiche:

ANDLW  n  Z  W = W AND n
ANDWF  reg,d   Z  d = W AND (reg)
IORLW  n  Z  W = W OR n
IORWF  reg,d  Z  d = W OR (reg)
XORLW  n  Z  W = W XOR n
XORWF  reg,d  Z d = W XOR (reg)
COMF   reg,d  Z  d = NOT (reg)

Queste istruzioni sono quelle che forse più assomigliano alle funzioni svolte dai comuni circuiti logici, ed in effetti a livello hardware si comportano proprio come delle semplici porte logiche che operano sui bit dei registri o dell'accumulatore.
Tutte le istruzioni a parte l'ultima richiedono due operandi su cui effettuare l'operazione logica.
Gli operandi possono essere l'accumulatore e un valore numerico diretto "n", oppure l'accumulatore e un registro, in questo caso  naturalmente va specificata la destinazione con il parametro d.
Sfruttando le caratteristiche dell'operazione logica XOR è possibile evitare l'uso di registri temporanei e ridurre le istruzioni necessarie solamente a 3:

   XORWF   DARKSPACE,F
   XORWF   DARKSPACE,W
   XORWF   DARKSPACE,F

La prima non altera il valore di W, ma in DARKSPACE si viene a trovare il risultato di DARKSPACE XOR W. La seconda effettua di nuovo uno XOR tra W e DARKSPACE, il risultato è perciò complessivamente DARKSPACE XOR W XOR W, cioè il valore inizialmente contenuto in DARKSPACE, che viene tenuto in W. Infine si effettua un terzo XOR tra DARKSPACE (che contiene sempre l'iniziale DARKSPACE XOR W) e W che contiene il valore iniziale di DARKSPACE, il risultato dell'operazione è complessivamente DARKSPACE XOR W XOR DARKSPACE, che è perciò il valore iniziale di W che viene salvato in DARKSPACE.
Così facendo i due valori hanno così cambiato di posto.
Sfruttando lo stesso principio è possibile anche scambiare tra di loro il valore di due registri (cioè REG1 e REG2) senza usarne altri di appoggio:

   MOVF    REG1,W
   XORWF   REG2,F
   XORWF   REG2,W
   XORWF   REG2,F
   MOVWF   REG1

Infine vediamo le funzioni RLF, RRF, BCF e BSF:
RLF   reg,d   C  d = rlf (reg)
RRF   reg,d  C   d = rrf (reg)
BCF   reg,b Bit b di (reg) = 0
BSF   reg,b Bit b di (reg) = 1

Le istruzioni RLF e RRF ruotano rispettivamente a sinistra o a destra i bit contenuti in un registro.
Il risultato è depositato nell'accumulatore o nel registro stesso, la rotazione avviene sempre attraverso il flag C.
La RRF funziona nello stesso modo, solo che la rotazione avviene nell'altro senso.
Queste istruzioni permettono di risolvere e semplificare numerosi problemi, per esempio legati al controllo di ogni singolo bit di un registro, alla serializzazione dei bit durante una trasmissione o al loro riassemblaggio in ricezione. Inoltre va ricordato che spostare a sinistra o a destra di una posizione i bit di un registro equivale rispettivamente a moltiplicare o dividere per 2 il suo valore numerico.
Le ultime due istruzioni permettono di resettare (BCF) o settare (BSF) un qualsiasi bit di un qualsiasi registro lasciando invariati gli altri. Questo permette per esempio di usare i singoli bit di un registro come 8 semplici memorie a due stati, ottenendo così un grande risparmio nell'utilizzo di registri.

Nessun commento:

Posta un commento