Il processore SGI/MIPS R8000 (R8K)

Questa pagina è dedicata al microprocessore RISC MIPS R8000, o R8K che dir si voglia (nome in codice TFP). Presentato il 7 Giugno 1994 con toni un po’ sopra le righe del tipo “MIPS ha creato, e continuerà a creare, i più veloci processori al mondo”, è stato il primo membro superscalare della famiglia MIPS, capace cioè di eseguire contemporaneamente più istruzioni (in particolare due operazioni aritmetiche o logiche e due istruzioni riguardanti l’accesso alla memoria). L’R8000 era in effetti un processore realizzato con più chip: un’unità centrale (R8000 propriamente detto), una FPU particolarmente potente sviluppata in collaborazione con Weitek, dotata di due pipeline a precisione doppia (R8010), tre tag RAM per la cache secondaria (due per l’accesso a quest’ultima, che poteva essere grande fino a 16 MB, uno con funzione di bus snooping) ed un controller per la cache stessa, tutti realizzati come circuiti specifici (ASIC) full custom e prodotti da Toshiba su disegno MIPS. Il tutto era realizzato con processo CMOS a 0,8 μm e tre livelli di metallizzazione, funzionante a 3,3 V; l’R8000 e l’R8010 contenevano complessivamente ~3,4 milioni di transistor ed i rispettivi die avevano più o meno le stesse dimensioni, 17,2 × 17,3 mm. Si trattava senza dubbio di chip grandi – contenuti in package PGA a 595 piedini – dal momento che all’epoca il PowerPC 603 integrava un maggior numero di transistor in un die decisamente più piccolo. Le cache interne erano di 16 KB ciascuna, una per i dati e gl’indirizzi di memoria ed una per le istruzioni; v’era inoltre una terza piccola cache da un solo KB che fungeva da branch target buffer (l’R8000 fu una delle prime CPU ad impiegare una tecnica di predizione statistica della destinazione dei salti, o branch target, del programma – tecnica che rientra nell’ambito della c.d. “esecuzione speculativa” ed è oggi adottata da tutti i processori moderni). La cache esterna richiedeva l’impiego di veloci ma costosi circuiti SSRAM sincroni, i quali contribuivano a rendere l’R8000 il processore dal prezzo più impegnativo prodotto fino ad allora. Una descrizione molto dettagliata dell’architettura dell’R8K ed un raffronto tra questa CPU e due suoi concorrenti dell’epoca (1996), l’Alpha 21164 di DEC ed il POWER2 di IBM, si può trovare in formato PDF nel sito francese dell'INRIA col titolo di Etude des architectures des Microprocesseurs DEC 21164, IBM POWER2 et MIPS R8000, pubblicata da A. Seznec ed Y. Mével tra i Rapports de Récherche, Giugno 1995, numero 2553.

Articolo della rivista Byte che parla del MIPS R8000:
http://www.byte.com/art/9409/sec6/art1.htm
Descrizione concisa del MIPS R8000: 
http://www.linux-mips.org/wiki/R8000
Belle immagini al microscopio ad alta risoluzione di CPU MIPS, incluso l’R8000:
http://micro.magnet.fsu.edu/chipshots/mips/index.html
Pagina dedicata al MIPS R8000 nel sito Gecko’s CPU Library:
http://gecko54000.free.fr/index.php?documentations=1994_MIPS_R8000
Pagina di SGI Stuff con descrizioni architetturali ed applicazioni SGI delle CPU MIPS, tra cui l’ R8000:
http://sgistuff.g-lenerz.de/hardware/processors.php
Pagina dedicata alla SGI Power Challenge, con informazioni anche sulla CPU R8000:
http://www.uoks.uj.edu.pl/resources/flugor/POWER/chap3.html
Pagine dedicate alle macchine SGI, che contengono molte informazioni sulle CPU MIPS:
http://vintagecomputers.info/i2.html
Uno dei tanti mirror di Great Microprocessors of the Past and Present, che parla di CPU MIPS:
http://www.cpushack.net/CPU/cpu4.html
Tabella con i dati di molti microprocessori e le immagini dei rispettivi die:
http://www-vlsi.stanford.edu/group/chips_micropro_body.html
Descrizione funzionale della CPU R8000 (attenzione, formato PPT):
www.academic.marist.edu/~jzbv/architecture/ Projects/projects2004/SuperscalarArchitectures.ppt 

Scheda con CPU R8000 a 75 MHz per workstation SGI Indigo2. Sotto il grosso dissipatore di calore di alluminio chiaro trovano posto la CPU R8000 e la FPU R8010; a destra si vede la cache di secondo livello, a sinistra i controller della memoria, mentre i tre componenti nella parte alta della scheda sono altrettanti buffer di sistema.

Il processore R8K è costituito da due componenti separati: l'unità integer, a sinistra, e quella floating point. Quello mostrato qui è, come molte altre CPU MIPS, fabbricato da Toshiba.

L’R8000 equipaggiò dapprima i costosi server SGI Power Challenge ed in seguito le workstation Power Indigo2; “Power” (potenza) fu il prefisso distintivo delle macchine che lo montavano. Non ebbe altre applicazioni al di fuori di questo ambito. Progettato in modo specifico per il calcolo scientifico, nel quale eccelleva grazie alla potenza della FPU tanto da “mettere la potenza dei supercomputer nelle mani di molti” come diceva la pubblicità di Silicon Graphics, soffriva le proprie limitate prestazioni negli altri settori (non più di 108 SPECint92 a 75 MHz) e la difficoltà di programmazione dovuta alle particolarità architetturali: ciò, unitamente all’elevato costo, ne causò in breve il declino e la scomparsa (rimase sul mercato poco più di un anno). Questo processore è nondimeno importante perché costituisce il punto d’ingresso di soluzioni tipiche dei supercomputer nel mondo dei microprocessori: al momento della sua presentazione era la CPU dalle prestazioni floating point più elevate (310 SPECfp92), e poteva essere utilizzato con una certa facilità in configurazioni multiprocessore massicce (16 e più CPU), consentendo la realizzazione di sistemi in grado di competere con i grandi calcolatori (big iron) dell’epoca, in specie con i Cray. In questo senso, un singolo processore R8000 offriva le medesime prestazioni floating point di un Cray Y–MP. L’introduzione dell’R8000 dimostrò dunque, smentendo il generale scetticismo esistente all’epoca, che i microprocessori erano oramai maturi per entrare a pieno titolo nel mondo del supercomputing, erroneamente ritenuto ad essi precluso; inoltre, rese evidente come fosse necessario per i costruttori di CPU RISC differenziare i proprî prodotti in due linee separate, la “fascia alta” e quella “media”, in modo da poter più efficacemente venire incontro ad esigenze fra loro molto diverse.

Scheda di server SGI Onyx con doppia CPU R8K a 90 MHz.

Dal punto di vista architetturale l’R8000, processore superscalare, rappresenta uno storico punto di rottura nei confronti della tradizione MIPS che aveva fino ad allora puntato più sulla profondità delle pipeline che non sulla capacità delle CPU di eseguire più istruzioni contemporaneamente. Una siffatta “rivoluzione”, estesasi in seguito ai membri della famiglia MIPS successivi all’R8000, originò dalla constatazione che le prestazioni in virgola mobile cui intendevano arrivare i progettisti avrebbero potuto essere raggiunte o creando un processore vettoriale oppure mediante – appunto – un’architettura superscalare; delle due ipotesi quest’ultima era senza dubbio quella più adatta ad un impiego generico della CPU e venne dunque adottata. Delle quattro distinte unità d’esecuzione contenute nella parte “intera” (integer) dell’R8K, tre sono del tipo fully pipelined e possono eseguire ciascuna istruzione in un singolo ciclo di clock mentre la quarta, dedicata a moltiplicazioni e divisioni, non lo è e richiede 6 cicli di clock per la moltiplicazione e da 21 a 73 per la divisione. L’R8000 contiene in totale quattro pipeline, tutte a cinque stadi (fetch, decode, address generate, execute, writeback): due alimentano le unità integer, mentre le rimanenti sono dedicate alle operazioni dette di load/store, sicché la CPU possa eseguire contemporaneamente operazioni di ALU ed accessi alla memoria. La complessità del progetto dell’R8000 generò all’epoca non pochi problemi architetturali, connessi alla notevole difficoltà riscontrata nell’utilizzare in modo efficiente un processore di queste capacità ed ai limiti intrinseci delle CPU superscalari (ad es. il cosiddetto load shadow: il ciclo immediatamente successivo quello durante il quale è stata eseguita un’operazione di load non può utilizzare il risultato di quest’ultima). Per evitare almeno una parte di tali questioni – generandone nel contempo altre, giudicate però meno importanti – MIPS scelse di disporre l’unità di esecuzione, in gergo l’ALU, un passo più avanti nella pipeline rispetto alla sua posizione canonica, che sarebbe immediatamente dopo lo stadio di decodifica (decode) dell’istruzione. Questa soluzione, che in certi casi induceva un’attesa di un singolo ciclo di clock nella successione delle operazioni, pur evitando il suddetto fenomeno di load shadow, era praticamente unica all’inizio degli anni Novanta, ma venne spesso ripresa in processori più recenti. L’unità integer dell’R8000  incorporava svariate altre migliorie rispetto alle architetture dei precedenti processori MIPS; per esempio era “tradizione” che in queste ultime l’esecuzione di tutte le istruzioni di salto, o branch, fosse seguita da un ciclo di clock di attesa (delay slot): se tale usanza dovette essere mantenuta per ragioni di compatibilità col passato, l’R8K eseguiva nondimeno in contemporanea il salto e l’attesa, sicché quest’ultima risultava – ai fini pratici – annullata, pur esistendo “teoricamente”, incrementando con ciò la velocità operativa della CPU. Infine, la cache dei dati era del tipo a doppia porta (dual ported), e ciò consentiva due contemporanei accessi ad essa in un dato istante; in più, uno speciale meccanismo di bypass consentiva di eseguire in parallelo un’operazione di store seguita da una di load direttamente dipendente dalla prima.

L’R8010, FPU (unità in virgola mobile) dell’R8K, era una curiosa via di mezzo tra un coprocessore a tutti gli effetti ed una parte architetturale della CPU realizzata su un distinto chip; il suo accoppiamento con l’unità integer avveniva per il tramite d’una coda (queue) di dati che poteva essere scritta e letta da entrambe le unità, le quali erano così liberate dall’incombenza di dover attendere che il passaggio dei dati stessi dall’una all’altra fosse completato prima di poter proseguire con l’elaborazione delle istruzioni successive. Questo tipo di soluzione, che pure aveva i suoi lati positivi, poteva indurre talvolta una sorta di “imprecisione” nei risultati prodotti dalla FPU, nel senso che qualora fosse intervenuta la necessità di modificare i dati passati ad essa nel mentre che questi si trovavano nella “coda”, sarebbe stato necessario attendere la loro “uscita” da essa, cioè a dire l’elaborazione, e quindi attendere ancora che i dati corretti venissero elaborati – a meno che non si volesse svuotare l’intera queue oppure far attendere le pipeline integer finché la FPU non avesse completato il suo lavoro. Ciononostante la potenza di calcolo in virgola mobile dell’R8000, in larga parte derivante dalla presenza di due identiche e parallele pipeline a precisione doppia nell’R8010, ottimizzate per di più per l’esecuzione di operazioni tipiche della grafica e del calcolo scientifico, era a quei tempi impressionante: mai s’era visto, infatti, un processore capace di “sfornare” ben 310 MFLOPS a soli 75 MHz! Al raggiungimento di tale valore contribuiva fra l’altro la circostanza che l’esecuzione delle moltiplicazioni da parte della FPU era fully pipelined e poteva essere eseguita in un solo ciclo di clock, mentre divisioni ed estrazioni di radici, non pipelined, richiedevano tutt’al più 20 cicli.

Uno tra gli obiettivi primari del progetto dell’R8000 fu l’ottenere una CPU superscalare che non fosse sensibile in modo critico all’allineamento delle istruzioni. Ora, svariate architetture RISC possono eseguire effettivamente in parallelo più istruzioni solamente se queste ultime rispettano certi vincoli di allineamento (ad es. su 64 o 128 bit); in caso contrario, se dunque questo allineamento non c’è, il tempo impiegato dalla CPU per ottenerlo consuma gran parte del vantaggio ottenuto dalla superscalarità (come accade ad es. nell’Alpha 21064). Al momento della presentazione dell’R8000 nel mondo RISC era compito del compilatore, quando non del programmatore, provvedere caso per caso a tale allineamento mediante l’inserimento nel codice, ove necessario, di operazioni NOP (“nulle”) per riuscire a raggiungerlo. Il punto è che in questo modo si diminuisce la densità del codice, mentre si aumenta il tasso di cache miss, il che com’è noto peggiora le prestazioni della CPU. La soluzione proposta da MIPS Technology consisteva nell’introduzione di un apposito buffer per le istruzioni al principio della pipeline, il quale provvedeva alla lettura delle istruzioni stessa dalla cache, all’allineamento sui 128 bit ed al loro successivo invio (dispatch) alle unità di esecuzione.

Inviare nella pipeline quattro istruzioni per ciclo di clock significava caricarne (fetch) altrettante dalla memoria nello stesso tempo, cosa che avrebbe reso le prestazioni dell’R8000 estremamente dipendenti dal numero di salti (branch) presenti nel programma, al cui crescere esse sarebbero rapidamente peggiorate. Ecco perché l’R8000 divenne una delle prime CPU RISC ad utilizzare la “previsione dei salti” (branch prediction), una tecnica parte di ciò che oggi è comunemente noto col nome di “esecuzione speculativa” e che consiste nella previsione, appunto, della destinazione delle istruzioni di salto ancora da eseguire, allo scopo di migliorare le prestazioni globali del processore. La sua ragion d’essere si trova nella constatazione che, all’interno d’un qualunque generico programma, vi sono sempre delle sequenze di istruzioni elaborate ripetutamente secondo uno schema regolare sicché, analizzando statisticamente le destinazioni (target) d’un congruo numero di salti eseguiti recentemente, è possibile prevedere con buona accuratezza la destinazione del successivo ed anticipare dunque il caricamento dalla memoria dell’istruzione da esso “puntata”. In questo modo, allorché il flusso del programma (code path) giungerà in quel preciso punto, non sarà necessario attendere l’operazione di lettura della memoria e l’esecuzione potrà procedere più speditamente. Per attuare efficacemente la branch prediction è necessario provvedere la CPU d’una piccola memoria dove registrare le destinazioni dei salti recenti (detta branch target buffer); nel caso dell’R8000 essa è ampia 1K, dimensione sufficiente ad assicurare risultati accurati pur utilizzando uno schema di predizione semplificato.

Tutti i moderni processori attuano la previsione dei salti; nella famiglia x86 essa è stata adottata con l’avvento dell’architettura P6 (dunque con il Pentium Pro).  Un limite del tipo di branch prediction usato dall’R8000 consiste nel fatto di assumere a priori che tutte le destinazioni dei salti previste puntino ad istruzioni contenute nella cache, e corrispondano dunque a c.d. cache hit (si ha un cache hit quando la CPU richiede il caricamento di un’istruzione dalla cache ed essa effettivamente si trova colà; in caso contrario, si avrebbe un cache miss e dunque la necessità di leggere dalla memoria anziché dalla cache stessa, con una penalità in termini di tempo d’accesso), ciò che accresce la penalità in caso di cache miss. Altro difetto è la scarsa efficienza del metodo nei casi in cui all’interno d’uno stesso blocco di quattro istruzioni vi siano due istruzioni di salto, limitazione che genera la necessità di compilare il programma in maniera tale che simili situazioni siano evitate (specifiche istruzioni dell’R8000 consentivano nondimeno di minimizzarne l’occorrenza).

Il principale problema che i progettisti dell’R8K dovettero affrontare era l’ottenimento di un memory bandwidth (“ampiezza di banda di memoria”, in sostanza la capacità e la velocità del canale di trasmissione dei dati dalla CPU alla memoria stessa) sufficiente per le esigenze d’un processore così veloce. La soluzione risiedeva in opportune struttura e gerarchia delle cache. L’R8000 contiene due cache primarie da 16 Kbyte ciascuna, una per i dati ed una per le istruzioni, aventi entrambe una dimensione di riga (line width) di 32 byte, alimentate da una cache secondaria esterna al processore, unificata, mediante l’invio due blocchi alla volta (lunghi 16 byte). Entrambe le cache interne sono del tipo direct mapped, ma differiscono sotto altri aspetti la cui trattazione esula dallo scopo di questo Catalogo; è invece interessante notare come la FPU (R8010) acceda direttamente alla cache di secondo livello (set associative a 4 vie), la quale funge anche da memoria locale nei sistemi multiprocessore. Per soddisfare le esigenze della FPU medesima, nonché del processore nel suo complesso, occorre una banda di memoria ampia almeno 1 Gbps – valore oggigiorno modesto, ma elevatissimo all’epoca in cui fu progettato l’R8000. Per raggiungerla fu necessario l’impiego di un insolito e costoso tipo di memoria, chiamata SSRAM, contenente appositi buffer che consentivano operazioni di trasferimento dati molto più veloci di quelle possibili con le cache “normali”. La cache di secondo livello dell’R8000 è dunque organizzata in due banchi di SSRAM, ciascuno con una lunghezza di linea di 64 byte e tempo d’accesso di 10 ~ 12 ns, e garantisce una banda di circa 1,2 Gbps. Onde evitare che quest’ultima fosse penalizzata dalla struttura della cache stessa (associativa a 4 vie), i progettisti realizzarono degli specifici chip di tag RAM a loro volta strutturati in questo modo, all’interno dei quali avveniva la “formazione” dell’effettivo indirizzo di memoria cache dal quale leggere o nel quale scrivere; la comunicazione tra CPU e cache segue nell’R8000 un percorso corrispondente ad una pipeline a 5 stadî, con penalità di 7 cicli per un miss riguardante i dati e di 11 per uno relativo alla cache delle istruzioni. Una delle ragioni che indussero i progettisti a disaccoppiare l’R8000 dalla FPU R8010 fu proprio il miglioramento delle prestazioni della cache, unitamente all’esigenza di evitare che gli accessi della FPU a quest’ultima limitassero – in certe condizioni, difficilmente prevedibili e dunque con altrettanta difficoltà evitabili in fase di compilazione – le prestazioni dell’unità integer.  

L’R8000 utilizzava un meccanismo piuttosto semplice per il mantenimento della corrispondenza tra le cache interne e quella esterna globale (ampia da 1 a 16 MB, con un sector size compreso tra 32 e 128 byte), basato innanzitutto sul fatto che le prime erano del tipo write through rispetto alla seconda. Un tanto semplificava di molto sia la logica di gestione delle cache medesime, sia quella necessaria al supporto di configurazioni multiprocessore – dal momento che non era necessario far fronte ai problemi tipici della soluzione write back. Benché ciò fosse potenzialmente in grado di creare problemi di “consistenza” tra dati integer e dati floating point, nonché di eccessiva occupazione della banda di memoria da parte dell’unità integer medesima, questa specifica gerarchia della cache fu scelta dal momento che nella gran parte dei casi offriva ottime prestazioni, specialmente in quelle applicazioni basate essenzialmente su floating point cui l’R8000 era di fatto dedicato. Come giustamente messo in evidenza in un articolo apparso sulla rivista Byte nel Settembre 1994, l’R8000 ha dimostrato – come già l’HP PA–RISC 7200 – che all’inizio degli anni Novanta i microprocessori erano oramai pronti per rivestire un ruolo di primo piano in tutti i settori dell’informatica, compresi quelli tradizionalmente riservati ai cosiddetti “supercalcolatori”.