Architettura di esempio
                                                 Ing. Costantino Grana
                           ...
Struttura
Internamente il processore è dotato di un registro accumulatore da 32 bit (ACC), di un Memory
Data Register da 3...
I segnali di Carry sono connessi in cascata tra di loro, i segnali di zero (Z) e negato (N) vengono
generati rispettivamen...
un esempio) o utilizzare indirizzi negativi (come spostamento per indirizzamenti indiretti). Quindi
la semplicità viene po...
Ciclo delle istruzioni
Ogni operazione, come avviene tradizionalmente, è composta da due fasi: Fetch e Execute. La fase
di...
MAR IR(addr)
       MDR ACC
       M[MAR] MDR

Add (ADD)
La somma esegue l’operazione concettuale ACC = ACC+M[addr]:
     ...
chiarezza sul significato dei T, quindi continueremo a indicare T5 la sesta microistruzione
indipendentemente dall’istruzi...
SET:
T3,SET:        MAR      IR(addr)              MARIE, IROE
T4,SET:        ACC      MAR                   ACCIE, MAROE
...
6    MARIE = T0 + T3·(SET + LD + ST + ADD + SUB)
7    MAROE = T4·SET
8    IROE = T3·(SET + LD + ST + ADD + SUB)
9    IRIE ...
delle istruzioni già viste. Infatti SET, LD, ST, ADD e SUB sfruttano l’indirizzo presente nell’IR per
leggere dalla memori...
I segnali di controllo da generare diventano allora:

ACCOE = T3·(ST + ADD + SUB + INC + DEC)
ACCIE = T3·SET + T4·(LD + IN...
vediamo che vengono eseguite due operazioni di ALU, ma l’unica che deve essere memorizzata nei
FLAG è la seconda, cioè que...
Dec   Hex    Istruzione   Clock   Con Fetch
0     000    SET          1       4
1     001    LD           2       5
2     ...
32 bit
                                                12 bit 20 bit
                                      00000     001  ...
Alla fine di questo programma, il valore di B è stato “rovinato”, dato che lo si è utilizzato come
contatore per verificar...
ALUOUTOE
                                                                        ALUOUTIE
     Operazione




            ...
Codifica delle istruzioni
Abbiamo sottolineato come nel caso di microprogrammazione le istruzioni debbano essere
codificat...
Nel caso microprogrammato la questione è più complessa, dato che bisogna dotare il sequencer,
ovvero la parte di architett...
0              1         1   0     se N incremento, altrimenti fetch
                  0              1         1   1     ...
Upcoming SlideShare
Loading in …5
×

Architettura dei Calcolatori 07 Architettura Di Esempio 16

462 views
420 views

Published on

Published in: Education, Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
462
On SlideShare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
17
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Architettura dei Calcolatori 07 Architettura Di Esempio 16

  1. 1. Architettura di esempio Ing. Costantino Grana Università degli Studi di Modena e Reggio Emilia (Stampato il 07/04/2008 - architettura di esempio_16.doc) Introduzione ......................................................................................................................................... 1  Struttura................................................................................................................................................ 2  Formato delle istruzioni ....................................................................................................................... 3  Microistruzioni ..................................................................................................................................... 4  Ciclo delle istruzioni ............................................................................................................................ 5  Fetch ................................................................................................................................................. 5  Execute ............................................................................................................................................. 5  Set (SET) ...................................................................................................................................... 5  Load (LD) .................................................................................................................................... 5  Store (ST) ..................................................................................................................................... 5  Add (ADD) .................................................................................................................................. 6  Subtract (SUB) ............................................................................................................................. 6  Increment (INC) e Decrement (DEC) .......................................................................................... 6  Unità di controllo ................................................................................................................................. 6  Implementazione cablata.................................................................................................................. 7  Versione semplificata (solo fetch) ............................................................................................... 7  Versione completa (fetch e execute) ............................................................................................ 7  Parallelizzazione del caricamento del MAR ........................................................................................ 9  SET (000) ................................................................................................................................... 10  LD (001)..................................................................................................................................... 10  ST (002) ..................................................................................................................................... 10  ADD (003) ................................................................................................................................. 10  SUB (004) .................................................................................................................................. 10  INC (005) ................................................................................................................................... 10  DEC (006) .................................................................................................................................. 10  Le istruzioni di salto........................................................................................................................... 11  Codice operativo e tempi di clock...................................................................................................... 12  Esempi di programmi per l’architettura ............................................................................................. 13  Somma di due numeri .................................................................................................................... 13  Prodotto di due numeri................................................................................................................... 14  Implementazione micro programmata dell’unità di controllo ........................................................... 15  Versione senza salti........................................................................................................................ 15  Codifica delle istruzioni ................................................................................................................. 17  Versione completa con i salti ......................................................................................................... 17  Introduzione Il processore di esempio che considereremo ha una struttura interna a singolo bus a 32 bit per lo scambio di informazioni tra i registri. Le connessioni esterne avvengono tramite un bus dati a 32 bit e un bus indirizzi a 20 bit. La scelta di avere un bus indirizzi così piccolo (consente di indirizzare 220 (=1 Mega) parole di memoria) è dovuta alla volontà di evitare istruzioni con lunghezza variabile. Inizialmente considereremo che la memoria abbia un parallelismo di 32 bit, cioè ad ogni indirizzo corrisponde una diversa parola di 4 Byte, mentre solo successivamente vedremo come modificare questa architettura per indirizzare i singoli byte. 1
  2. 2. Struttura Internamente il processore è dotato di un registro accumulatore da 32 bit (ACC), di un Memory Data Register da 32 bit (MDR), di un Memory Address Register da 20 bit (MAR), di un Program Counter da 20 bit (PC), di un Instruction Register da 32 bit (IR), di due registri relativi alla ALU da 32 bit (ALUA e ALUOUT) e da un registro di FLAG che contiene 4 bit di condizione (Zero, Carry, Negativo e Overflow). Le connessioni sono illustrate in figura: ZA 0 0 Cin ACC 1 IR ALUA A OUT MDR B D0-31 MAR ALUOUT A0-19 PC FLAG I registri connessi al bus hanno tutti un segnale di abilitazione in lettura (OE), cioè abilitano i buffer tri-state che li connettono al bus, e uno in scrittura (IE), tranne ALUA che è abilitato sempre in lettura verso la ALU (l’uscita dei flip-flop non passa per un buffer). Inoltre il MDR dispone di due segnali MDRBUSOE e MDRBUSIE che consentono di attivare l’output e l’input dal bus dati. MDRIE e MDRBUSIE non possono essere attivi contemporaneamente. MDRBUSOE viene portato all’esterno come segnale di Write (WR) per la memoria e MDRBUSIE è connesso al Read (RD) della memoria (la memoria è in lettura e pone i dati sul data bus ed MDR è in scrittura e memorizza tali dati). Il segnale ZA comanda il multiplexer posto all’ingresso A della ALU e consente di inviare uno zero alla porta A oppure l’output di ALUA. La porta B è sempre connessa al bus. Il registro FLAG dispone di un segnale di abilitazione FLAGIE per campionare lo stato della ALU. La ALU è molto semplice e consente di eseguire le seguenti operazioni: S1 S0 Operazione 0 0 A and B 0 1 A or B 1 0 A + B + Cin 1 1 A + B’ + Cin La ALU è costituita da 32 blocchi come il seguente: 2
  3. 3. I segnali di Carry sono connessi in cascata tra di loro, i segnali di zero (Z) e negato (N) vengono generati rispettivamente dal negato della somma logica di tutti i bit e dal bit più significativo di ALUOUT. Il Carry della ALU (C) viene generato dal carry out del blocco corrispondente ai bit più significativi (il bit 31), mentre l’overflow è dato dall XOR dei carry out 30 e 31. Concludendo sono necessari i seguenti segnali di controllo: 0 ACCOE 1 ACCIE 2 MDROE 3 MDRIE 4 MDRBUSOE 5 MDRBUSIE 6 MAROE 7 MARIE 8 IROE 9 IRIE 10 PCOE 11 PCIE 12 ALUAIE 13 ALUOUTOE 14 ALUOUTIE 15 FLAGIE 16 ZA 17 Cin 18 S0 19 S1 Formato delle istruzioni Per avere una architettura semplice, vale la pena sacrificare un po’ di praticità dal set di istruzioni, quindi avere meno possibilità, ma ridurre lo sforzo nella realizzazione dell’unità di controllo. Le istruzioni sono tutte composte da un primo campo a 12 bit che identifica il codice operativo e un secondo campo a 20 bit che contiene un indirizzo. La funzione dell’indirizzo dipende dall’istruzione implementata. Questa architettura semplice consente di realizzare numerose funzioni, ma molto difficilmente si potrà lavorare con dati di dimensione inferiore a 32 bit (come dei caratteri per fare 3
  4. 4. un esempio) o utilizzare indirizzi negativi (come spostamento per indirizzamenti indiretti). Quindi la semplicità viene poi pagata in termini di realismo del processore. 31 20 19 0 Codice operativo (opcode) Indirizzo (addr) Microistruzioni Tre sono le possibili tipologie di microistruzioni che si possono realizzare: i trasferimenti tra registri, i trasferimenti con la memoria e le operazioni di ALU. Per i trasferimenti tra registri è necessario abilitare in scrittura il registro destinazione e in lettura il registro sorgente. È necessario verificare che con questo schema il campionamento avvenga al momento giusto, per esempio sul fronte di salita del clock, in modo che il dato venga campionato alla fine del periodo di clock. Ad esempio la microistruzione MAR PC viene realizzata con MARIE=1 e PCOE=1. I dati scambiati con la memoria richiedono un proprio ciclo di clock per il campionamento dei dati da parte della memoria (in scrittura) o del MDR (in lettura). Durante questi trasferimenti il bus locale (interno al processore) non è utilizzato ed è quindi possibile sfruttarlo per altre operazioni. Siccome il MAR è sempre attivo sul bus indirizzi, è sufficiente utilizzare i due segnali di controllo del MDR per eseguire le microoperazioni relative alla memoria: Read MDR M[MAR] MDRBUSIE=1 Write M[MAR] MDR MDRBUSOE=1 Le operazioni di ALU hanno bisogno di tutti e quattro i segnali ZA, Cin, S1 e S0 e quindi la tabella di verità per le operazioni di ALU diventa: S1 S0 ZA Cin Operazione Risultato 0 00 - 0 and B Sempre 0 (può servire per avere uno zero da usare con il decremento) 0 0 1 - A and B AND 0 1 0 - 0 or B B (inutile) 0 1 1 - A or B OR 1 0 0 0 0+B B (inutile) 1 0 0 1 0+B+1 Incremento 1 0 1 0 A+B Somma 1 0 1 1 A+B+1 Somma e incrementa (inutile) 1 1 0 0 0 + B’ NOT 1 1 0 1 0 + B’ + 1 Negativo, cioè –B 1 1 1 0 A + B’ Sottrae e decrementa (può servire per fare un decremento) 1 1 1 1 A + B’ + 1 Sottrazione Ogni operazione di ALU necessita inoltre di un registro abilitato in lettura per avere un dato nel canale B. Le microistruzioni della ALU vengono sempre scritte specificando prima il canale A, cioè ALUA o 0, poi indicando l’operazione (and, or, +), poi il canale B, cioè il registro abilitato in lettura in forma vera o negata e infine, se necessario, il valore del Cin. Il risultato viene sempre portato in ALUOUT. Ad esempio volendo sottrarre il MDR all’accumulatore si indica: ALUA ACC ALUOUT ALUA + MDR’ + 1 4
  5. 5. Ciclo delle istruzioni Ogni operazione, come avviene tradizionalmente, è composta da due fasi: Fetch e Execute. La fase di fetch è seguita sempre da una sottofase che a volte viene indicata come Decode, ma che in questo caso non è altro che l’incremento del program counter, dato che la decodifica non coinvolge alcun registro di stato. Fetch Le microistruzioni di questa fase sono comuni a tutte le istruzioni: MAR PC MDR M[MAR] IR MDR È inoltre necessario, come detto, incrementare il PC: ALUOUT 0 + PC + 1 PC ALUOUT Dal momento che non vi è necessità di tenere separate le due operazioni, possiamo sfruttare il fatto che durante la lettura il bus locale non viene utilizzato e che durante la prima microistruzione PC sta già pilotando il bus e possiamo sfruttarlo per effettuare la somma. Quindi la fase di fetch definitiva diventa: MAR PC, ALUOUT 0 + PC + 1 MDR M[MAR], PC ALUOUT IR MDR Execute Per delineare le possibili tipologie di istruzioni bisogna considerare che l’architettura ad accumulatore lavora sempre con la memoria, quindi saranno certo necessarie due operazioni di lettura e scrittura dell’accumulatore, poi ci saranno alcune operazioni aritmetiche che riguardano il solo accumulatore. Prenderemo in considerazione in seguito le istruzioni di controllo. Set (SET) Volendo impostare in modo immediato il contenuto dell’accumulatore, è possibile realizzare una istruzione molto semplice che prende il campo ADDR dell’istruzione e lo copia in ACC. Ad esempio si potrebbe fare: MAR IR(addr) ACC MAR Il passaggio per il MAR ci permette di “eliminare” l’opcode, e di trasferire in ACC solo i 20 bit meno significativi. Una soluzione più brutale potrebbe essere questa: ACC IR(addr) Purtroppo sappiamo che in questo modo in ACC andrebbe a finire anche l’opcode dell’istruzione SET. La soluzione semplice per questo problema è quindi far sì che questo opcode sia 0! Ovviamente questo impone dei vincoli progettuali, quindi in generale non sembra una buona idea. Load (LD) L’operazione di load carica il contenuto della memoria all’indirizzo specificato nell’accumulatore: MAR IR(addr) MDR M[MAR] ACC MDR Store (ST) L’operazione di store scrive il contenuto dell’accumulatore in memoria all’indirizzo specificato: 5
  6. 6. MAR IR(addr) MDR ACC M[MAR] MDR Add (ADD) La somma esegue l’operazione concettuale ACC = ACC+M[addr]: MAR IR(addr) MDR M[MAR], ALUA ACC ALUOUT ALUA + MDR ACC ALUOUT Vale la pena notare che si può sfruttare il momento di lettura dell’operando per caricare il registro ALUA con il contenuto dell’accumulatore. Subtract (SUB) La differenza non gode della proprietà commutativa, quindi è necessario specificare quale dei due operandi sia il minuendo e quale il sottraendo. SUB esegue l’operazione ACC = ACC-M[addr]: MAR IR(addr) MDR M[MAR], ALUA ACC ALUOUT ALUA + MDR’ + 1 ACC ALUOUT Sarebbe possibile definire anche l’operazione inversa, cioè ACC = M[addr]-ACC. E’ comunque possibile ottenere lo stesso risultato utilizzando questa sottrazione e negando (in senso aritmetico) poi il risultato. Increment (INC) e Decrement (DEC) Solo come ulteriore esempio presentiamo le funzioni di incremento e decremento. Per l’incremento non ci sono particolari problemi, dato che è una classica istruzione di ALU priva di parametri: ALUOUT 0 + ACC + 1 ACC ALUOUT Per quanto riguarda il decremento è possibile sfruttare la sottrazione con decremento, cioè sommare all’accumulatore il negato di zero che in complemento a due è -1. Per fare questo però bisogna caricare il canale B della ALU con uno zero che generiamo usando l’AND con uno zero. ALUA ACC, ALUOUT 0 and ACC ALUOUT ALUA + ALUOUT’ ACC ALUOUT Questo esempio è un po’ più complesso, dato che si utilizza il fatto che non è necessario attendere il corretto trasferimento di ACC in ALUA per preparare ALUOUT con uno 0, dato che il canale B è assolutamente ininfluente e quindi possiamo in parallelo usare ACC per pilotare la porta B della ALU e caricare ALUA. Inoltre si vede come possiamo utilizzare ALUOUT come registro sorgente e come registro destinazione. Unità di controllo Per progettare l’unità di controllo è necessario identificare il valore di ogni segnale di controllo (i 19 che abbiamo visto precedentemente) in ogni “istante”. Gli istanti in realtà sono quantizzati dal segnale di clock che quindi identifica una serie di fasi corrispondenti alle singole microistruzioni. Chiameremo queste fasi con T seguito da un numero (cominciando da T0), per indicare il periodo di clock a cui ci stiamo riferendo. È possibile vedere ogni periodo di clock come un indicatore dello stato presente in un automa a stati finiti che secondo la rappresentazione di Moore associa as ogni stato un valore delle uscite. Lo stato futuro dipenderà allora dallo stato presente e dagli ingressi. Gli ingressi in questo caso sono identificati unicamente dal contenuto del campo OPCODE dell’IR. Per 6
  7. 7. chiarezza sul significato dei T, quindi continueremo a indicare T5 la sesta microistruzione indipendentemente dall’istruzione di cui fa parte. Implementazione cablata Versione semplificata (solo fetch) Riprendiamo in considerazione la fase di fetch e limitiamoci a specificare i segnali interessati a questa fase, pensiamo quindi ad un processore che faccia solo e unicamente il fetch delle istruzioni senza eseguirle. Indichiamo poi accanto ad ogni microistruzioni i segnali che devono essere posti a 1 e il valore dei segnali dell ALU. T0: MAR PC, ALUOUT 0 + PC + 1 MARIE, PCOE, ALUOUTIE, ZA=0, Cin=1, S1S0=10 T1: MDR M[MAR], PC ALUOUT MDRBUSIE, PCIE, ALUOUTOE T2: IR MDR IRIE, MDROE Da questo schema risulta molto semplice associare ad ogni segnale la sua funzione combinatoria dipendente solo da T0,T1 e T2 che per la verità hanno solo tre possibili combinazioni, dato che sono stati mutuamente esclusivi. Lo schema completo dei segnali per questo processore che fa solo fetch, è il seguente: MARIE = T0 PCOE = T0 ALUOUTIE = T0 ZA = T0’ Cin = T0 S1 = T0 S0 = T0’ MDRBUSIE = T1 PCIE = T1 ALUOUTOE = T1 IRIE = T2 MDROE = T2 Bisogna poi specificare la funzione di stato futuro per i T e in questo caso è veramente semplice: T0f = T2 T1f = T0 T2f = T1 Versione completa (fetch e execute) Passiamo invece ora a considerare l’unità di controllo necessaria a implementare un processore con tutte le sei istruzioni viste in precedenza. La fase di fetch è esattamente quella vista in precedenza, quindi andiamo a dettagliare solo le varie possibilità per l’execute. Il campo OPCODE di IR è direttamente connesso (prima dei buffer di OE) ad un decoder nell’unità di controllo che consente per ogni istruzione di generare un segnale corrispondente, ad esempio SET potrebbe essere generato dall’opcode (in esadecimale) 000, LD da 001, ST da 002 e così via. Non ci sono particolari motivi per avere una codifica o un’altra in questo caso. 7
  8. 8. SET: T3,SET: MAR IR(addr) MARIE, IROE T4,SET: ACC MAR ACCIE, MAROE LD: T3,LD: MAR IR(addr) MARIE, IROE T4,LD: MDR M[MAR] MDRBUSIE T5,LD: ACC MDR ACCIE, MDROE ST: T3,ST: MAR IR(addr) MARIE, IROE T4,ST: MDR ACC MDRIE, ACCOE T5,ST: M[MAR] MDR MDRBUSOE ADD: T3,ADD: MAR IR(addr) MARIE, IROE T4,ADD: MDR M[MAR], ALUA ACC MDRBUSIE, ALUAIE, ACCOE T5,ADD: ALUOUT ALUA + MDR ALUOUTIE, MDROE ZA=1, Cin=0, S1S0=10 T6,ADD: ACC ALUOUT ACCIE, ALUOUTOE SUB: T3,SUB: MAR IR(addr) MARIE, IROE T4,SUB: MDR M[MAR], ALUA ACC MDRBUSIE, ALUAIE, ACCOE T5,SUB: ALUOUT ALUA – MDR’ + 1 ALUOUTIE, MDROE ZA=1, Cin=1, S1S0=11 T6,SUB: ACC ALUOUT ACCIE, ALUOUTOE INC: T3,INC: ALUOUT 0 + ACC + 1 ALUOUTIE, ACCOE ZA=0, Cin=1, S1S0=10 T4,INC: ACC ALUOUT ACCIE, ALUOUTOE DEC: T3,DEC: ALUA ACC, ALUOUT 0 and ACC ALUAIE, ACCOE, ALUOUTIE, ZA=0, S1S0=00 T4,DEC: ALUOUT ALUA + ALUOUT’ ALUOUTIE, ALUOUTOE, ZA=1, Cin=0, S1S0=11 T5,DEC: ACC ALUOUT ACCIE, ALUOUTOE Per sintetizzare i segnali di controllo, è sufficiente mettere in AND l’indicatore dell’istante in cui il segnale si deve attivare (deve essere posto a 1) con il segnale relativo all’istruzione corrente. Si possono poi usare le varie leggi dell’algebra di Boole per semplificare l’espressione, risparmiando sul numero di porte logiche necessarie: 0 ACCOE = T3·(INC + DEC) + T4·(ST + ADD + SUB) 1 ACCIE = T4·(SET + INC) + T5·(LD + DEC) + T6·(ADD + SUB) 2 MDROE = T2 + T5·(LD + ADD + SUB) 3 MDRIE = T4·ST 4 MDRBUSOE = T5·ST 5 MDRBUSIE = T1 + T4·(LD + ADD + SUB) 8
  9. 9. 6 MARIE = T0 + T3·(SET + LD + ST + ADD + SUB) 7 MAROE = T4·SET 8 IROE = T3·(SET + LD + ST + ADD + SUB) 9 IRIE = T2 10 PCOE = T0 11 PCIE = T1 12 ALUAIE = T3·DEC + T4·(ADD + SUB) 13 ALUOUTOE = T1 + T4·(INC + DEC) + T5·DEC + T6·(ADD + SUB) 14 ALUOUTIE = T0 + T3·(INC + DEC) + T4·DEC + T5·(ADD+SUB) 15 FLAGIE = 0 16 ZA = T4·DEC + T5·(ADD+SUB) 17 Cin = T0 + T3·INC + T5·SUB 18 S1 = T0 + T3·INC + T5·(ADD + SUB) 19 S0 = T4·DEC + T5·SUB La codifica sopra riportata è certamente non minima, dato che assomiglia fortemente ad una sintesi canonica SP. Non vengono assolutamente utilizzate le indifferenze anche banalmente verificabili. Per esempio S0 può essere messo ad 1 in T4 sempre, dato che nessun’altra istruzione utilizza S0 in T4, quindi si potrebbe risparmiare l’AND con il segnale DEC. Nella realizzazione di una unità di controllo cablata, il numero di segnali cresce enormemente rispetto a questo esempio, quindi riduzioni di questo tipo diventano indispensabili per evitare dimensioni eccessive. Inoltre sostituire i segnali T0-6 con una loro codifica, ridurrebbe notevolmente il numero di segnali da trasportare nell’unità di controllo. La sintesi SP è piuttosto naturale per questo tipo di sistema, dato che abbiamo supposto i segnali attivi ad uno. Dal momento che solo pochi di questi segnali si attivano contemporaneamente, possiamo supporre che per la maggior parte dei casi l’uscita debba essere a 0, riducendo il numero di mintermini da utilizzare. Parlare di mintermini in questo caso non è realmente appropriato, dato che le varie configurazioni sono già decodificate, quindi dire ad esempio T3·INC sottointende chiaramente che T0, T1, T2, T4, T5, T6, LD, ST, ADD, SUB e DEC siano a zero (evitandoci dunque di specificarli nell’espressione). È da notare inoltre che FLAGIE è stato volutamente ignorato, dato che in realtà nessuna istruzione ha bisogno dei flag, anche se certamente dovrebbero impostarli tutte le istruzioni aritmetiche. La sintesi dello stato futuro diventa leggermente più complessa visto che ci sono istruzioni con lunghezza variabile. Quindi ad esempio se sono nello stato T4 e l’istruzione è una INC devo andare allo stato T0, mentre se sono in una DEC devo andare in T5. Proseguendo con la sintesi intuitiva fatta finora, si ottiene questo risultato: T0f = T4·(SET + INC) + T5·(LD + ST + DEC) + T6·(ADD + SUB) T1f = T0 T2f = T1 T3f = T2 T4f = T3 T5f = T4·(LD + ST + ADD + SUB + DEC) T6f = T5·(ADD + SUB) Parallelizzazione del caricamento del MAR L’architettura appena illustrata è ancora incompleta, dato che manca di un aspetto fondamentale, cioè la gestione dei salti che consente di eseguire istruzioni diverse a seconda di risultati o output. Però è comunque utile considerare una otimizzazione a quanto illustrato finora. Abbiamo già visto che se un registro sta pilotando il bus, più di un dispositivo può leggere e memorizzare il contenuto. Questo può essere utilizzato per eliminare la prima microistruzione di 5 9
  10. 10. delle istruzioni già viste. Infatti SET, LD, ST, ADD e SUB sfruttano l’indirizzo presente nell’IR per leggere dalla memoria e devono trasferirlo nel MAR. Si deve altresì notare che il contenuto del MAR è ininfluente sulle altre microperazioni (a parte l’accesso in memoria). Quindi si potrebbe sfruttare la terza istruzione del fetch per preparare già il MAR con il contenuto dell’IR. Nel MAR si dovrebbe trasferire solo il campo Address dell’IR, ma il MAR è collegato ai 20 bit meno significativi del bus, cioè proprio al campo richiesto, quindi qualsiasi cosa ci sia negli altri bit è completamente ininfluente. Facendo l’esempio dell’istruzione LD, il risultato sarebbe il seguente: MAR PC, ALUOUT 0 + PC + 1 MDR M[MAR], PC ALUOUT IR MDR, MAR MDR <<< Qui viene fatta l’aggiunta MAR ← IR(addr) <<< Questa μ-istruzione è eliminata MDR M[MAR] ACC MDR Questa modifica non influenza in alcun modo le altre istruzioni, dato che non aggiunge nulla alle istruzioni che non utilizzano la memoria (INC e DEC). In sostanza le microistruzioni delle varie istruzioni diventano (tra parentesi è indicato il codice operativo che useremo come riferimento): SET (000) ACC MAR LD (001) MDR M[MAR] ACC MDR ST (002) MDR ACC M[MAR] MDR ADD (003) MDR M[MAR], ALUA ACC ALUOUT ALUA + MDR ACC ALUOUT SUB (004) MDR M[MAR], ALUA ACC ALUOUT ALUA + MDR’ + 1 ACC ALUOUT INC (005) ALUOUT 0 + ACC + 1 ACC ALUOUT DEC (006) ALUA ACC, ALUOUT 0 and ACC ALUOUT ALUA + ALUOUT’ ACC ALUOUT 10
  11. 11. I segnali di controllo da generare diventano allora: ACCOE = T3·(ST + ADD + SUB + INC + DEC) ACCIE = T3·SET + T4·(LD + INC) + T5·(ADD + SUB + DEC) MDROE = T2 + T4·(LD + ADD + SUB) MDRIE = T3·ST MDRBUSOE = T4·ST MDRBUSIE = T1 + T3·(LD + ADD + SUB) MARIE = T0 + T2 MAROE = T3·SET IROE = 0 IRIE = T2 PCOE = T0 PCIE = T1 ALUAIE = T3·(ADD + SUB + DEC) ALUOUTOE = T1 + T4·(INC + DEC) + T5·(ADD + SUB + DEC) ALUOUTIE = T0 + T3·(INC + DEC) + T4·(ADD + SUB + DEC) FLAGIE = 0 ZA = T4·(ADD + SUB + DEC) Cin = T0 + T3·INC + T4·SUB S1 = T0 + T3·INC + T4·(ADD + SUB) S0 = T4·(SUB + DEC) T0f = T3·SET + T4·(LD + ST + INC) + T5·(ADD + SUB + DEC) T1f = T0 T2f = T1 T3f = T2 T4f = T3·(LD + ST + ADD + SUB + INC + DEC) T5f = T4·(ADD + SUB + DEC) Le istruzioni di salto Parte integrante della programmazione sono le istruzioni di salto che consentono di modificare il punto di esecuzione del programma. In particolare, fondamentali per implementare funzioni logiche complesse servono istruzioni di salto condizionato, istruzioni che eseguono la modifica al PC solo se si verifica una determinata situazione. Le “situazioni” che tipicamente si prendono in considerazione sono i risultati delle operazioni aritmetiche precedenti. Per tenerne conto abbiamo già accennato alla presenza di un registro FLAG che abbiamo ignorato finora. Questo registro deve essere aggiornato con i segnali provenienti dalla ALU ad ogni operazione richiesta dall’utente, cioè se consideriamo l’istruzione ADD comprensiva di Fetch ed Execute (nella prima versione senza caricamento parallelo di IR e MAR in T2): T0: MAR PC, ALUOUT 4 + PC MARIE, PCOE, ALUOUTIE, ZA=0, QA=1, Cin=0, S1S0=10 T1: MDR M[MAR], PC ALUOUT MDRBUSIE, PCIE, ALUOUTOE T2: IR MDR IRIE, MDROE T3: MAR IR(addr) MARIE, IROE T4,ADD: MDR M[MAR], ALUA ACC MDRBUSIE, ALUAIE, ACCOE T5,ADD: ALUOUT ALUA + MDR ALUOUTIE, MDROE ZA=1, Cin=0, S1S0=10, FLAGIE T6,ADD: ACC ALUOUT ACCIE, ALUOUTOE 11
  12. 12. vediamo che vengono eseguite due operazioni di ALU, ma l’unica che deve essere memorizzata nei FLAG è la seconda, cioè quella relativa ai dati dell’utente, cioè la ADD. I segnali provenienti dai flag arrivano direttamente alla unità di controllo che li utilizza per realizzare le funzioni logiche opportune. Iniziamo considerando la struttura di un salto incondizionato: JMP T3,JMP: PC IR(addr) PCIE, IROE Questo è tutto. Insomma si prende il contenuto del campo ADDR di IR e lo si mette in PC. Al fetch successivo il PC è già stato modificato opportunamente. Che cosa cambia in un salto condizionato? semplicemente il trasferimento in PC avviene solo se si verifica la condizione richiesta, quindi se per esempio vogliamo considerare un salto in caso di risultato Zero dell’operazione precedente: JZ T3,JZ,Z: PC IR(addr) PCIE, IROE Aggiungiamo alla condizione da verificare anche il flag interessato. I salti disponibili in questa architettura saranno (vista la ALU a disposizione): JZ salta se il flag Z vale 1 JNZ salta se il flag Z vale 0 JO salta se il flag O vale 1 JNO salta se il flag O vale 0 JN salta se il flag N vale 1 JNN salta se il flag N vale 0 JC salta se il flag C vale 1 JNC salta se il flag C vale 0 Come si realizza questa condizione nell’unità di controllo? Nel caso cablato la soluzione è particolarmente semplice, infatti il segnale Zero è disponibile già durante T2 (e anche prima) quindi è sufficiente evitare di andare in T3 se l’istruzione è JZ e Z=0. Purtroppo però JZ non è disponibile fino a T3, dato che viene calcolato da IR e quindi aspettiamo T3, non facendo niente nel caso di salto non eseguito (condizione falsa). Consideriamo quindi di aggiungere l’istruzione JZ all’implementazione cablata vista in precedenza, occupandoci solo dei segnali interessati dall’istruzione. Queste sono le modifiche necessarie per una realizzazione letterale, cioè verificando la condizione in T3 (le modifiche sono evidenziate): IROE = T3·(JMP + JZ·Z + JNZ·Z’ + JO·O + JNO·O’ + JN·N + JNN·N’ + JC·C + JNC·C’) PCIE = T1 + T3·(JMP + JZ·Z + JNZ·Z’ + JO·O + JNO·O’ + JN·N + JNN·N’ + JC·C + JNC·C’) T0f = T3·(SET + JMP+JZ+JNZ+JO+JNO+JN+JNN+JC+JNC) + T4·(LD + ST + INC) + T5·(ADD + SUB + DEC) Codice operativo e tempi di clock Nella tabella seguente sono riportati, per ogni istruzione il codice operativo in decimale e esadecimale, il codice mnemonico identificativo e il tempo di clock necessario all’esecuzione, tenendo conto del caricamento anticipato del MAR, con e senza i cicli di clock necessari alla fase di fetch. 12
  13. 13. Dec Hex Istruzione Clock Con Fetch 0 000 SET 1 4 1 001 LD 2 5 2 002 ST 2 5 3 003 ADD 3 6 4 004 SUB 3 6 5 005 INC 2 5 6 006 DEC 3 6 7 007 JMP 1 4 8 008 JZ 1 4 9 009 JNZ 1 4 10 00A JO 1 4 11 00B JNO 1 4 12 00C JN 1 4 13 00D JNN 1 4 14 00E JC 1 4 15 00F JNC 1 4 Esempi di programmi per l’architettura Con la struttura delineata finora è possibile realizzare alcuni programmi molto semplici utilizzando una sintassi nota come linguaggio Assembly. In questo caso non esistono regole sulla sintassi, dato che ogni costruttore ne inventa di sue. Senza volere entrare nei dettagli delle caratteristiche dell’Assembly, basti sapere che su ogni riga si specifica il codice operativo simbolico (detto anche mnemonico) e gli eventuali operandi che possono essere numerici o simbolici, nel senso che non è necessario calcolare l’indirizzo delle variabili in memoria, dato che può farlo un programma per noi. Questo programma si chiama Assemblatore (Assembler). Somma di due numeri Vediamo un semplicissimo programma di esempio che somma due valori numerici a 32 bit A e B impostati in memoria dal caricamento del programma e mette il risultato in una variabile C: ld A add B st C A: 00001234h B: 0056789Ah C: 0 Solitamente i valori esadecimali nei programmi assembly vengono terminati con una h. Inoltre vale la pena notare che si è utilizzata una sintassi specifica per indicare che una linea non contiene un’istruzione, ma un valore numerico che non deve essere interpretato. Supponiamo che questo programma venga convertito in codice macchina e caricato in memoria a partire dall’indirizzo 0. Utilizzando la codifica degli opcode illustrata precedentemente e scrivendo i valori in esadecimale, quello che troveremmo sarebbe: 13
  14. 14. 32 bit 12 bit 20 bit 00000 001 0000C 00004 003 00010 00008 002 00014 0000C 000 01234 00010 005 6789A 00014 000 00000 Caricato il programma in memoria, l’esecuzione potrebbe iniziare a partire dall’indirizzo 00000. Il problema è che terminata la terza operazione il processore eseguirebbe il fetch di una ulteriore istruzione, in particolare di 00001234, che verrebbe interpretata come SET 01234! Insomma, il problema è che non disponiamo di una istruzione per interrompere il fetch, cioè per fermare il processore. Nella realtà questo non è un problema, dato che i processori continuano ad eseguire un programma di base che solitamente è chiamato sistema operativo. Ma per non dilungarci e andare fuori tema, limitiamoci a considerare come sia possibile interrompere il ciclo fetch/execute. Questo effetto si può ottenere con un JMP all’indirizzo dell’istruzione. Il nostro programma a questo punto diventa: ld A add B st C Fine: jmp Fine A: 00001234h B: 0056789Ah C: 0 Si è utilizzato il simbolo Fine per definire un indirizzo, e in particolare l’indirizzo dell’istruzione JMP. Anche negli esempi successivi non specificheremo gli indirizzi, ma sfrutteremo i due punti per definire etichette simboliche da utilizzare come argomenti delle istruzioni. A questo punto possiamo passare ad un esempio più complesso. Prodotto di due numeri Il prodotto di due numeri può essere eseguito in diversi modi, ad esempio con la tecnica che ci è stata insegnata alle elementari che consiste nell’imparare delle moltiplicazioni fondamentali (le tabelline) ed estenderle poi opportunamente. La versione più semplice però è l’utilizzo della definizione, cioè sommare il moltiplicando tante volte quante indicate dal moltiplicatore. È da sottolineare anche che questa tecnica è anche la più lenta e sconsigliabile in applicazioni reali. Ecco come potrebbe apparire un programma che moltiplica A e B scrivendo il risultato in C: Ancora: ld C add A st C ld B dec B jnz Ancora Fine: jmp Fine A: 5 B: 6 C: 0 14
  15. 15. Alla fine di questo programma, il valore di B è stato “rovinato”, dato che lo si è utilizzato come contatore per verificare quando terminare il ciclo. È inoltre importante notare come sia fondamentale avere a disposizione una locazione di memoria inizializzata a zero, dato che questa viene subito utilizzata come raccoglitore iniziale del risultato della moltiplicazione. Implementazione micro programmata dell’unità di controllo Versione senza salti Un approccio alternativo alla realizzazione dell’unità di controllo, consiste nel memorizzare ogni microistruzione come l’insieme di tutti i segnali di controllo con il loro valore. Questo necessita di una struttura di memorizzazione (RAM, EEPROM, ROM) alla quale si potrà accedere con un indirizzo. Al termine del ciclo di clock sarà necessario modificare opportunamente l’indirizzo prelevando i segnali corretti. Tutto questo lo si può realizzare con un micro Program Counter (μPC) che può essere incrementato per passare alla microistruzione successiva, può essere azzerato al termine dell’istruzione, per tornare alla fase di fetch (comune a tutte le istruzioni) e può essere caricato con il contenuto dell’OPCODE, che diventa un indirizzo nella memoria delle microistruzioni. Lo schema seguente dovrebbe chiarire il concetto: 0 0 micro control op code µPC address 1 control signals 2 Incremento microcode memory Secondo questo schema sono necessari in ogni microistruzione anche i segnali di controllo per il μPC che si limitano a selezionare quale dei tre ingressi del multiplexer deve essere utilizzato per il passo successivo. Sono quindi necessari due bit aggiuntivi MC0 e MC1 che selezioneranno l’ingresso: MC1 MC0 Selezione 0 0 0 (μsalto al fetch) 0 1 μsalto all’opcode 1 0 prossima μistruzione 1 1 X (non valido) Ci sono vari modi per evitare si avere un salto specifico al fetch, ma li vedremo in seguito. Passiamo invece alla implementazione della architettura che abbiamo illustrato. Sono necessari 19 bit per i segnali di controllo più due per il microcontrollo. Utilizzando la numerazione della prima tabella dei segnali di controllo possiamo assegnare 19 a MC0 e 20 a MC1 ottenendo uno schema a 21 colonne come il seguente: 15
  16. 16. ALUOUTOE ALUOUTIE Operazione MDRBUSOE MDRBUSIE Indirizzo ALUAIE FLAGIE MDROE ACCOE MARIE MDRIE ACCIE PCOE MC1 MC0 IROE PCIE IRIE Cin ZA S1 S0 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 101 010 1 1 1 fetch 0 10 1 1 1 1 01 1 1 2 10 1 1 LD 3 10 1 4 00 1 1 5 10 1 1 ST 6 10 1 1 7 00 1 8 10 1 1 ADD 9 10 1 1 1 10 101 0 0 1 1 1 11 00 1 1 12 10 1 1 SUB 13 10 1 1 1 14 101 1 1 1 1 1 15 00 1 1 16 101 0 1 0 1 1 INC 17 00 1 1 18 100 0 0 1 1 1 DEC 19 101 1 0 1 1 1 20 00 1 1 21 Gli spazi non riempiti contengono degli zeri. Questa soluzione prevede che il codice operativo sia esattamente l’indirizzo nella memoria di controllo della prima microistruzione corrispondente all’istruzione selezionata. Quindi ad esempio l’istruzione LD non sarebbe più 000 come nell’esempio precedente, ma obbligatoriamente 003. Purtroppo questa implementazione così come è stata presentata ha un bug sulla fase di esecuzione del salto all’opcode. Infatti, nella terza microistruzione si trasferisce il contenuto dell’MDR in IR e contemporaneamente si vorrebbe trasferire questo dato in μPC. Il problema è che i dati vengono campionati sul fronte di salita del clock, quindi se si legge il contenuto di IR (anche solo il campo OPCODE) nell’istante in cui questo campiona, si ricava il contenuto precedente dell’IR, quindi l’indirizzo sbagliato. In realtà vuol dire che si fa il fetch di un’istruzione, ma si esegue la precedente e questo potrebbe causare molti problemi. Sono possibili varie soluzioni, ma la più semplice –valida solo per questo specifico caso– è quella di connettere il bus locale all’ingresso 1 del multiplexer di selezione. In tal modo al termine della fase di fetch, quando viene eseguita la microistruzione (IR MDR), sullo stesso fronte di clock viene caricato il codice istruzione (proveniente da MDR) in IR ed il solo campo OPCODE in uPC. È chiaro che la soluzione alternativa sarebbe inserire un’altra microistruzione dopo il trasferimento all’IR, ad esempio posticipando PC ALUOUT, purtroppo in questo modo si aggiunge un ciclo di clock alla fase di fetch. 16
  17. 17. Codifica delle istruzioni Abbiamo sottolineato come nel caso di microprogrammazione le istruzioni debbano essere codificate in modo specifico per puntare all’indirizzo della microroutine corrispondente. Effettivamente sarebbe più comodo poter utilizzare la stessa codifica utilizzata nel caso di microarchitettura cablata. Una soluzione semplice si può realizzare specificando con l’op-code non l’indirizzo, ma piuttosto il blocco di microistruzioni di interesse. Allineando i blocchi a indirizzi multipli di una potenza di due e riservando loro uno spazio potenza di due, è sufficiente uno shift cablato per ottenere il risultato voluto. Ad esempio, nel caso precedente sarebbe possibile utilizzare blocchi da 4 microistruzioni, ristrutturando la memoria così: Istruzione opcode Celle utilizzate Lunghezza Fetch - 0,1,2 3 LD 001 4,5,6 3 ST 002 8,9,10 3 ADD 003 12,13,14,15 4 SUB 004 16,17,18,19 4 INC 005 20,21 2 DEC 006 24,25,26 3 In questo modo è possibile connettere il bit 2 dell’opcode al bit meno significativo del μPC realizzando quindi uno shift a sinistra di due bit (moltiplicazione per 4, dimensione del blocco). Sostanzialmente è come se si inserissero due zeri (con una codifica binaria) a destra del codice operativo dell’istruzione, producendo così l’indirizzo del μPC. Memoria delle microistruzioni LD 0000|0000|0001 0000|0000|0000 ST 0000|0000|0010 0000|0000|0001 ADD 0000|0000|0011 0000|0000|0010 SUB 0000|0000|0100 0000|0000|0011 INC 0000|0000|0101 0000|0000|0100 DEC 0000|0000|0110 0000|0000|0101 0000|0000|0110 0000|0000|0111 0000|0000|1000 0000|0000|1001 0000|0000|1010 0000|0000|1011 0000|0000|1100 ... Il difetto di questa soluzione è chiaramente lo spreco di memoria, evidente dall’indirizzo dell’ultima microistruzione di DEC che è a 26 rispetto al 21 del caso precedente. È anche possibile pensare ad una rom che si occupi di tradurre gli opcode in microindirizzi, ma ovviamente si complica ulteriormente lo schema architetturale. Versione completa con i salti Il problema più grosso nella gestione dei segnali di controllo lo si ha con l’implementazione microprogrammata che non consente di inviare direttamente in uscita il contenuto di un registro, ad esempio Carry-out. Se alcuni eventi devono essere condizionati al valore dei registri di stato diventa necessario prevedere l’hardware (multiplexer solitamente) per inviare i vari segnali e poi codificare nel microcodice i segnali di controllo per decidere quale valore verrà effettivamente selezionato. 17
  18. 18. Nel caso microprogrammato la questione è più complessa, dato che bisogna dotare il sequencer, ovvero la parte di architettura che seleziona il prossimo valore del μPC, di ingressi dedicati ai Flag e nella memoria delle microistruzioni prevedere dei selettori per questi segnali. Le soluzioni possibili sono molteplici e noi ne vedremo solo una di esempio molto banale e probabilmente non delle migliori, ma sicuramente semplice e di facile comprensione. Questa struttura si basa proprio sul concetto che abbiamo visto sopra, cioè di passare da T2 a T3 in un salto condizionato solo se la condizione specificata è vera. Per questo motivo si possono separare le tre possibilità previste nello schema base (selezionate con MC1 e MC0) in due blocchi in cascata così: MC0 0 0 MC1 1 0 µPC op code 1 Incremento In questo modo abbiamo separato in due livelli concettuali la selezione, ovvero abbiamo un segnale che ci dice se dobbiamo eseguire il decode oppure se dobbiamo usare l’altro multiplexer. Questo multiplexer ci consente di procedere alla microistruzione successiva o di tornare al fetch. Per comandare il multiplexer di incremento o fetch possiamo decidere in maniera secca 0 o 1 fissi oppure possiamo lasciare che siano i FLAG a comandarlo. Nasce quindi l’esigenza di inserire un ulteriore multiplexer (questa volta a 1 bit) per passare i segnali di controllo opportuni: 0 1 C C’ 0 Z Z’ N 0 N’ MC3 1 MC2-0 0 µPC op code 1 Incremento I segnali di controllo MC0, MC1 e MC2 selezionano quale segnale deve decidere se eseguire il Fetch o se incrementare μPC quindi volendo riprodurre il comportamento della microarchitettura precedente è possibile utilizzare i segnali come in tabella: MC3 MC2 MC1 MC0 Funzione 1 x x x μsalto all’opcode 0 0 0 0 0 (μsalto al fetch) 0 0 0 1 prossima μistruzione (incremento) 0 0 1 0 se C incremento, altrimenti fetch 0 0 1 1 se C’ incremento, altrimenti fetch 0 1 0 0 se Z incremento, altrimenti fetch 0 1 0 1 se Z’ incremento, altrimenti fetch 18
  19. 19. 0 1 1 0 se N incremento, altrimenti fetch 0 1 1 1 se N’ incremento, altrimenti fetch A questo punto non è difficile realizzare i salti condizionati e non. Nella prossima tabella vediamo la fase di fetch e la realizzazione dei salti. In questo caso si è utilizzata l’architettura con indirizzamento al byte e il caricamento parallelo di IR e MAR in T2. Resta l’accortezza di ricordare che il μPC carica i dati dal bus quando si legge l’opcode e non dal IR per quanto detto precedentemente: ALUOUTOE ALUOUTIE Operazione MDRBUSOE MDRBUSIE Indirizzo ALUAIE FLAGIE MDROE ACCOE MARIE MDRIE ACCIE PCOE MC3 MC2 MC1 MC0 IROE PCIE IRIE Cin QA ZA S1 S0 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 0 0 0 110010 1 1 1 fetch 0 0 0 0 1 1 1 1 1 1 0 0 0 1 1 1 2 … … 0 0 0 0 1 1 JMP x 0 0 1 0 JC x+1 0 0 0 0 1 1 x+2 0 0 1 1 JNC x+3 0 0 0 0 1 1 x+4 0 1 0 0 JZ x+5 0 0 0 0 1 1 x+6 0 1 0 1 JNZ x+7 0 0 0 0 1 1 x+8 0 1 1 0 JN x+9 0 0 0 0 1 1 x+10 0 1 1 1 JNN x+11 0 0 0 0 1 1 x+12 … Il grosso difetto di questa soluzione è che diventa necessario allungare di un ciclo di clock ogni istruzione di salto. Infatti non è possibile caricare la nuova istruzione e contemporaneamente sapere già quale condizione verificare. Viene quindi introdotta una microistruzione nulla che serve solo per verificare lo stato dei segnali ed eseguire un μsalto al fetch in caso di condizione falsa. Una alternativa a questo schema che non necessiti di una microistruzione vuota, si può realizzare utilizzando il selettore del multiplexer per comandare direttamente PCIE, ritornando quindi allo schema precedente per decidere riguardo all’incremento o al fetch. 19

×