Introduzione al linguaggio macchina
Linguaggi di programmazione add e sub e uso dei registri. Confronto tra registri e memoria. Organizzazione e indirizzamento della memoria. Quando si parla di linguaggi di programmazione sono tra loro simili quindi imparare a usare un linguaggio di programmazione risulta molto semplice una volta che abbiamo imparato uno perché la somiglianza tra i linguaggi nasce dal fatto che tutti i calcolatori.
Ambiente di collaudo integrato
PC Spim è un simulatore totalmente software che non è corredato del processore MIPS ma tale programma replica come si comporta il processore MIPS esistesse e se a questo processore fossero sottoposte per l'esecuzione le istruzioni dei programmi. Simulare significa cercare di capire se il processore fa quello che abbiamo ipotizzato nel progetto.
Nella parte alta dello schermo si ha la fotografia dello stato della CPU: c'è PC, EPC, registri, per ogni registro si trova il numero d'ordine progressivo e anche nome attribuito ma non sono tutti presenti. Compaiono i valori che i registri contengono quando si eseguono programmi, ci dice quale è lo stato corrente della CPU quindi ogni registro al suo interno che valore ha assunto a fronte delle esecuzioni.
La seconda porzione dello schermo è dedicata all'inserimento dei programmi, quando si inseriscono i programmi tale programma sarà sottoposto a traduzione se non sono commessi errori formali il caricamento andrà a buon fine. La parte bassa è la parte di SPIM che comunica con l'utente: 31: la $s0, dati - la prima porzione di programma è stata collocata alla 31esima riga del file sorgente.
Jal check - nella parte centrale si vede scritta l'istruzione con qualche differenza si nota la prima rielaborazione di quanto si ha proposto, l'ambiente di collaudo indica che jal a check corrisponde a voler saltare all'indirizzo in esadecimale 0x00400214 che consultando la tavola dei simboli è l'indirizzo di destinazione del richiamo al sottoprogramma.
Andando verso sinistra tale numero in esadecimale contiene la rappresentazione in esadecimale quindi i 32 bit che rappresentano le istruzioni che vogliamo eseguire; per ogni istruzione vedremo comparire qualcosa che è rispettoso del formato R, I, J. Potremo spacchettare tali bit per identificare op, identificativi dei registri, costanti. A sinistra riportato tra parentesi quadre c'è un indirizzo in esadecimale che rappresenta l'indirizzo di memoria in cui tale istruzione è stata collocata, quindi ho foto completa della memoria che contiene in codice.
- Finestra 1: Stato CPU
- Finestra 2: Programma che abbiamo scritto
- Finestra 3: Area che contiene i dati
Le prime 2 righe: Perché la seconda riga sembra vuota per quanto riguarda il codice sorgente: lo è perché tale comando è una load address: stiamo richiedendo di caricare un intero indirizzo all'interno di un registro ma un indirizzo non starebbe nella nostra istruzione infatti è una pseudoistruzione e infatti ciò che vediamo al centro è una lui e ori che ci dicono che tale indirizzo sarà caricato preliminarmente in due parti: la parte alta, noto $1 che è registro $at, ho primo caricamento in cui carichiamo la parte alta e si azzera la parte bassa e seguirà tale comando la ori che prende il valore predisposto contenuto in $a1 e farà con il dato immediato che è 85 che in autonomia ha calcolato l'assemblatore e caricherà il risultato dell'or nel registro $16 che è il valore di $s0 che l'istruzione specificava al suo interno.
3 finestra -> area dati = resto della memoria ossia memoria che contiene costanti che il programma deve usare, messaggi che abbiamo preconfezionato che vorremo far apparire sul display -> tutto ciò prepedeutico al buon funzionamento. Nell'area dati ritroveremo anche risultati parziali delle elaborazioni. Quando si faranno calcoli e i risultato non sarà possibile tenerli nella CPU nei registri si faranno SW, SB. Sono una serie di costanti predefinite dal programmatore usate durante l'esecuzione del programma.
Alla sinistra abbiamo indirizzo di partenza. A destra troveremo 4 word che richiedono per essere rappresentate 16 byte (in esadecimale 10) e si nota che gli indirizzi vanno da 10 esadecimale in 10. Quindi le word sono caricate a indirizzi multipli di 4 dunque in esadecimale: 0x206f7265. Alla posizione 12 finale che in esadecimale è C, quindi alla posizione C finale ho 65, alla D 72, alla E 6F, alla posizione 0F 20.
Esecuzione dei programmi
Come possiamo capire cosa accade durante l'esecuzione dei programmi? L'ultima porzione di schermo è la porzione di colloquio con l'utente dove compariranno alcuni messaggi che sono del tipo: è stata eseguita tale istruzione oppure anche segnalazione errore ad esempio se si è avuto un overflow. L'ultima finestra è quella di colloquio. È una porzione da non coprire perché qui appaiono le problematiche.
Il programma: (da scrivere con blocco note)
add $t1, $zero, $zero #inizializzare registri è fondamentale perché se leggo qualcosa che non add $t2, $t1, $t1 #ho mai scritto nell'ambiente di collaudo trovo 0 ma nel sistema reale # i bistabili e flip flop si portano in una condizione imprecisata lui $t2, 0x1000 #inserimento in t2 del valore 10000000 in esadecimale ciclo: addi $t1, $t1, 0x100 ->100 in esa è 256 in decimale -> 000100000000 slt $t3, $t2, $t1 beq $t3, $zero, ciclo sub $t1, $t1, $t2 addi $t1, $t1, 1 j ciclo #il valore che prevediamo che la condizione di salto non sia più soddisfatta è 0x10000100 perché tali cifre dicono che abbiamo superato il traguardo. #una volta uscito dal ciclo si farà l'operazione di sottrazione che è : t1=> 0x10000100 – 0x10000000 = 0x100 Al passo seguente : # 0x101, si ripeterà il ciclo e il valore diventerà 201, 301, 401 fino a quando non otterremo il primo valore che supera il parametro di riferimento che vale: 0x10000001 – 0x10000000 = 0x1 E il ciclo si ripete all'infinito. (salvare con nome.asm)
Caricandolo su PC Spim: Come metterlo in esecuzione? È ciclo infinito e quindi non vedo nulla. Quindi bisogna eseguire una istruzione per volta. Posso controllare la corretta esecuzione per una iterazione per 2 iterazioni e poi dopo di ciò capiremo che sto ciclo fa quello che deve.
Per poter partire dall'istruzione corretta dato che vedo che il program counter parte da 00000000 devo fare: simulator->set value e voglio fare: mettere nel PC il valore che rappresenta l'indirizzo della prima istruzione quindi: Eseguire un'istruzione alla volta: single step (lo capisco dalla finestra di colloquio con l'utente)
Massimo numero rappresentabile in complemento a 2
Il massimo numero rappresentabile in complemento a 2 è 7FFFFFFF. Multiple step permette di definire quanti passi si vogliono eseguire. Go -> Vai alla massima velocità senza fermarti mai. Break point -> punto di arresto, lo uso mettendo un punto di arresto, dico voglio che il programma proceda con la massima rapidità che andrà avanti alla massima rapidità fino a quando non incontra il punto di arresto.
Breakpoint -> inserisco indirizzo di memoria corrispondente all'istruzione raggiunta la quale voglio che il controllo torni per vedere se il programma ha fatto quello che doveva. Colloco l'indirizzo dell'istruzione. Quando il programma ripartirà se raggiungerà tale posizione il controllo verrà restituito all'utente che potrà verificare ciò che accade. Si possono inserire anche break point multipli.
Programma contenente errori
Programma per il test del funzionamento di un'area di memoria RAM: l'area è costituita da 64k word a partire dall'indirizzo 0x10000000. La memoria viene completamente scritta e riletta effettuando un confronto fra i dati scritto e letto. In ogni cella si memorizza un dato uguale all'indirizzo della cella.
Nota bene: versione con errori voluti! Questa è il file da non modificare. La versione su cui lavorare per correggere gli errori è TST-RICERCA. $t0 usato come puntatore alla memoria e come dato da memorizzare/controllare. $t1 usato come indice di fine check. $t2 usato per il dato riletto.
li $t0, 0x10000000 # N.B. è una pseudoistruzione lui $t1, 0x1001 write: sw $t0, 0($t0) addi $t0, $t0, 1 beq $t1, $t0, write # fine scrittura lui $t0, 0x1000 read: lw $t2, 0($t0) sub $t3, $t0, $t2 bne $t3, $zero, error addi $t0, $t0, 1 beq $t1, $t0, read # lettura dati conclusa in modo OK loop: j loop # lettura dati KO: trovato un errore error: j error
All'accensione del calcolatore si avviano una serie di programmi bootstrap che permettono di attivare disco rigido, vengono avviati programmi di test che permettono di verificare se ad esempio la tastiera è collegata ma anche il funzionamento della memoria.
Verifica del funzionamento della memoria
Come fare a capire che la memoria che il sistema usa è correttamente funzionante per evitare di avere sorprese durante l'attività operativa del sistema? L'idea è di scrivere e di leggere (trattiamo della memoria RAM) dati noti all'interno della memoria. Conviene scrivere un dato e rileggerlo o scrivere un'intera zona di memoria e poi rileggerla nell'interezza verificando se quello che abbiamo scritto è congruente con le aspettative?
Scrivere e leggere non è una buona idea perché potrebbe succedere che ci sia un malfunzionamento nell'indirizzamento. Viene proposta la scrittura dell'intera zona d'interesse e poi fare la rilettura casella per casella per ogni dato che dalla memoria si riceve.
Problemi di indirizzamento e dati
Altro problema: tipo dato da scrivere, ossia scrivere lo stesso valore in ogni cella di memoria ad esempio tutti zeri: non è buona idea perché se c'è un problema degli indirizzamenti non si evidenzia e se c'è un problema anche sul trasferimento dati non si evidenzia perché avendo tutti i bit uguali se avessi due bit di dati scambiati un filo in cortocircuito verso la massa questo non è riconoscibile perché riceveremo 0 ma questo deriva dal fatto che la linea è bloccata alla massa viceversa il problema anche per quanto riguarda la scrittura di tutti 1 perché ci potrebbe essere un cortocircuito verso l'alimentazione positiva.
Non è buona soluzione nemmeno alternare 0 e 1. Qualunque soluzione si adotti non si ha la certezza unica che vengano evidenziati i problemi. Nell'esempio proposto si saranno usati 64 k word a partire da 0x10000000. 64k -> 10010000. In ogni word si scrive un dato differente.
Blocchi del flow chart
- Inizializzazione memoria, permette di capire quale è la posizione da cui parte il testo.
- Andare a scrivere in memoria un dato coincidente con l'indirizzo della casella, aggiorniamo il puntatore alla memoria e si verifica se si hanno completato le operazioni di scrittura dalla memoria di interesse se non si è completata la scrittura il ciclo si ripete effettuando una nuova scrittura, le scritture in memoria con SW e l'aggiornamento di indirizzo con ADDI.
- Quando la scrittura è completamente effettuata dunque 64k word sono riempiti si parte con operazione di lettura controllo confronto 5 permette di reinizializzare il puntatore all'area di memoria di interesse.
- Effettuare la rilettura del dato e dato che il dato che troviamo nella casella ha un valore coincidente con indirizzo da cui stiamo leggendo possiamo capire se quanto abbiamo letto è congruente con le nostre aspettative.
Effettuato il test si potrà decidere di andare a una routine che segnala che si è presentata una problematica oppure nel caso in cui l'operazione è andata a buon fine si farà l'operazione duale a quella di prima ossia aggiornare puntatore alla memoria per poterci predisporre a effettuare una nuova lettura e quindi il test finale ci dice: siamo arrivati alla fine? Abbiamo concluso l'esame di ogni nostra cella di memoria d'interesse? Se no si ritorna alla lettura del dato ma avendo modificato il puntatore nella casella successiva, se si si conclude che la memoria funziona correttamente e si passa il controllo del programma ai programmi operativi veri e propri.
li $t0, 0x10000000# N.B. è una pseudoistruzione lui $t1, 0x1001#sono operazioni che eseguono la stessa cosa write: sw $t0, 0($t0) addi $t0, $t0, 1 #ci si sposta alla posizione successiva beq $t1, $t0, write# fine scrittura lui $t0, 0x1000 # reimposta t0 al valore di partenza read: lw $t2, 0($t0) # spostato di 0 posizioni sub $t3, $t0, $t2 # di norma dovremo avere in t0 e t2 cose uguali # e t0-t2 dovrebbe fare 0 quindi: bne $t3, $zero, error addi $t0, $t0, 1 beq $t1, $t0, read# lettura dati conclusa in modo OK loop: j loop #ciclo infinito # lettura dati KO: trovato un errore error: j error # ciclo infinito
Quando si incontra l'istruzione di BEQ non si fa un salto al write come vorremmo e dobbiamo mettere BNE $t1, $t0, write apposto la istruzione semplicemente con: perché si vuole saltare al write fino a quando l'indirizzo della casella che viavia si incrementano non diventa uguale alla prima casella al di fuori dell'area di interesse (e da notare che anche il BEQ del read è BNE quindi: BEQ $t1, $t0, read -> BNE $t1, $t0, read) BNE $t1, $t0, write -> mi fa ritornare al write ma quando si arriva a SW non mi fa fare ADDI ed esce segnalazione:
Ci dice che abbiamo una conferma sulla store SW $t0, 0($t0) e che l'indirizzo usato nella store di scrittura pari a 0x10000001 è un indirizzo non allineato ossia non rispetta il vincolo di allineamento perché quando si producono word gli indirizzi devono essere multipli di 4 quindi si è prodotto un indirizzo che non può essere usato per accedere a una word. Questo errore perché si è presentato? Perché nella ADDI abbiamo messo 1 e non 4 quindi: ADDI $t0, $t0, 4 siamo sicuri che scriviamo fino all'ultimo valore atteso? Lo possiamo capire ripetendo il ciclo per il numero di volte necessario, ci conviene lavorare con break point in corrispondenza della istruzione successiva a quello del sottoprogramma. E definisco il punto in cui ci vogliamo fermare con breakpoint. Così si troverà in una situazione di loop infinito nella condizione di ok.
Ma così abbiamo visto che è andato a buon fine l'operazione di scrittura, di rilettura di ogni word scritta in memoria ma non abbiamo la certezza che sulla corretta identificazione dell'errore perché ciò che abbiamo fatto è stata la rilettura di ogni dato e ogni dato è stato congruente con le aspettative; la domanda che bisogna porsi è: abbiamo controllato ogni possibile situazione? NO, non abbiamo controllato il caso di riconoscimento dell'errore.
Come possiamo controllare anche il riconoscimento di un errore? Mi basta usare set value che si può fare sia caricando registri ma si può fare anche in memoria quindi dico: Metto un valore diverso rispetto all'indirizzo della casella, metto valore sbagliato che non è congruente rispetto a quello che dobbiamo trovare in fase di lettura ma la fase di lettura si è già conclusa. Vedo che la seconda casella è sbagliata. In t2 noto l'12345678 che avevo inserito, quindi il programma ci ha permesso di eliminare alcuni errori formali, correggere l'errore nato dalla generazione di un indirizzo non allineato, capire che la scrittura va a buon fine anche rilettura e introducendo un errore esso è stato identificato. Con buona probabilità quindi il programma ha fatto il suo dovere e ha raggiunto gli obiettivi proposti (questi non sono i tempi che impiega il simulatore ma quelli che impiega il programma di simulazione appoggiandosi sul processore del personal computer).
Ulteriore piccolo errore: si è scritto 64 k word ma non è vero perché sono 64k byte perché la memoria del processore è organizzata a byte e quindi il numero di caselle scritte è l'intero spazio di indirizzamento collaudato/4 perché ogni word richiede 4 byte.
Identificatori dei salti
Gli identificatori dei salti possono essere nomi qualsiasi, non devono cominciare con un numero ma possono essere conclusi con un numero, non sono resi possibili l'utilizzo di etichette coincidente con la sintassi delle istruzioni. Quando scriviamo dati numerici essi sono di default in decimale possono essere indicati come esa se anteposti da 0x ma non si possono scrivere in binario per motivi di lunghezza.
Possiamo definire stringhe che sono delimitate da "", se abbiamo bisogno di usare caratteri speciali come new line, tabulatori o le virgolette, dobbiamo anteporre tali simboli con /, in particolare:
- /n -> New line
- /t -> Tabulatore
- /” -> Virgolette
Comandi per l'assemblatore
Quando si scrivono i programmi l'assemblatore ci permette di impostare o ci viene in aiuto quando dobbiamo definire stringhe, costanti. Comandi per l'assemblatore -> DIRETTIVE, precedute dal punto. Quando l'assemblatore vede il punto capisce che è una direttiva che deve essere presa in considerazione durante la traduzione del codice. .ascii "stringa", .asciiz "stringa": non di rado nella scrittura dei programmi dobbiamo proporre delle stringhe su un display ipotetico e quindi proporre insieme di caratteri.
.ascii "stringa" -> serve per caricare in memoria le codifiche esadecimali associate a ognuno dei simboli della codifica ascii e quindi collocare in celle consecutive i caratteri rappresentati la nostra stringa. È compito dell'assemblatore fare la traduzione. asciiz "stringa" -> stringa conclusa con
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
-
Calcolatori elettronici - Appunti
-
Appunti lezione Calcolatori elettronici
-
Appunti lezione Calcolatori elettronici
-
Appunti Calcolatori elettronici - parziale