In un precedente articolo ho parlato di “misura dei bit” dell’architettura di un sistema, e ho intenzionalmente tirato fuori una definizione ricorsiva, che faceva uso di altre definizioni per finalizzare se stessa.
Ciò non per sadismo o mancanza di rispetto nei confronti dei lettori (che hanno sollevato ugualmente e giustamente il problema nei commenti), ma perché avevo intenzione di parlare in termini più generali di cosa s’intende per “misura”, delle variabili che possono concorrere al suo calcolo, e soprattutto dell’arbitrarietà della scelta dell’algoritmo utilizzato allo scopo.
Ho voluto, quindi, metter da parte il concetto di misura del singolo componente del sistema, dandolo per scontato / assodato, anche se qualche traccia delle mie idee l’ho lasciata sia nell’articolo che nei commenti, con la promessa di tornare appositamente sull’argomento…
Premetto che le mie considerazioni riguarderanno principalmente le CPU, ma potranno facilmente e intuitivamente essere riportate anche a GPU, APU, PPU, DSP, e in generale a qualunque processore (inteso come elemento che esegue dei calcoli).
Come l’architettura di un sistema è costituita da diversi componenti, allo stesso modo una CPU al suo interno conta diversi elementi. Questi possono in qualche misura essere “misurabili” e, contrariamente a un intero sistema (dove avevo necessariamente forzato l’ipotesi che a ogni componente fosse possibile attribuire una precisa misura), spesso è possibile stabilire in maniera oggettiva su quanti bit lavorano.
Forti di queste maggiori certezze, la tentazione potrebbe essere quella di pensare che, almeno per loro, la decidibilità della misura delle CPU sia concreta. In realtà l’eterogeneità degli elementi interni in generale è tale da presentare esattamente gli stessi problemi di scelta.
Anche qui, quindi, mettiamoci subito l’anima in pace: è molto difficile che si possa definire in maniera oggettiva una misura, in termini di bit, di una CPU.
Passando ai singoli elementi che la costituiscono, vediamo che ne esistono di diversi tipi:
- registri dati
- registri indirizzi
- registri indice
- PC (Program Counter)
- SP (Stack Pointer)
- registro dei flag
- registri speciali
- bus dati interno
- bus dati esterno
- bus indirizzi interno
- bus indirizzi esterno
- collegamenti punto-punto
- AGU
- ALU
- FPU
- SIMD
Ovviamente non sono tutti presenti all’interno di qualunque CPU, ma è possibile che alcuni di essi non ci siano, oppure che siano dotate di elementi addizionali. Ciò non toglie la valità del ragionamento, che è facilmente applicabile anche a casi qui non contemplati.
Una spiegazione per ognuno di essi si rende necessaria, per lo meno per capire di cosa sto parlando.
Un registro dati serve a contenere dei dati sui quali possiamo effettuare la maggior parte delle operazioni (“movimento”, aritmetiche, logiche, campi di bit, stringhe, array, BCD, ecc.). In genere ne è sempre presente almeno uno (chiamato accumulatore) oppure un certo numero (si parla di registri dati general purpose), ma ci sono architetture che non ne hanno (come il TMS 9900 che sfrutta la memoria o i Transputer che usano lo stack).
Un registro indirizzi permette di memorizzare direttamente degli indirizzi di memoria (virtuali o fisici) per poi farvi riferimento tramite le istruzioni di load/store (specificando opportune modalità d’indirizzamento), oppure manipolandolo tramite un ristretto numero di operazioni (“movimento”, e aritmetiche; queste ultime in genere limitate soltanto a somme, sottrazioni e confronti, quindi escludendo moltiplicazioni, divisioni, and, or, ecc.). Il 68000 è un esempio di architettura che ne fa ampio uso.
Un registro indice viene usato per poter indirizzare in maniera indiretta la memoria, semplificando l’uso di array e strutture dati (struct o record). Come per i registri indirizzo, viene specificato nelle modalità d’indirizzamento, e manipolato con un ristretto numero di operazioni. E’ molto comune nelle architetture, e i due concetti (registri indirizzi e indice) si “fondono” se l’ISA non è segmentata, cioé un indirizzo di memoria è contenuto interamente in un registro indice.
Un esempio di architettura per la quale si applica l’uno o l’altro concetto è lo Z8000 , che nella versione più avanzata sfrutta i segmenti (quindi i registri sono classificabili come indice) e in quella più economica no (quindi i registri sono di tipo indirizzo). Ovviamente ci sono anche architetture che non fanno distinzione fra registri dati e/o indice e/o indirizzi, come il PDP-11 .
Il PC è un registro di tipo indirizzo o indice che punta all’attuale o prossima (a seconda delle implementazioni) istruzione da eseguire.
Lo SP è un registro di tipo indirizzo o indice che punta all’attuale o successiva (a seconda delle implementazioni) locazione di memoria di tipo stack (quindi usata per variabili temporanee e/o indirizzi di ritorno da chiamate a subroutine) che risulta disponibile.
Per lo SP i casi sono diversi: ci sono architetture che hanno un apposito registro dedicato (16032), che riservano un registro dati/indirizzo/indice allo scopo (8086), oppure che non hanno un vero e proprio stack, in quanto possono usare qualunque registro (ARM).
Il registro dei flag serve a memorizzare il risultato delle ultime operazioni aritmetiche o logiche, per poi essere utilizzate in istruzioni di salto condizionale. Generalmente c’è un registro dedicato allo scopo (chiamato appunto registro di flag), ma ovviamente esistono delle eccezioni.
Le prime versioni dell’ARM (fino alla 3) integravano i flag nei 6 bit alti del PC (limitando, di fatto, lo spazio d’indirizzamento a 64MB), mentre i MIPS non hanno flag (si usano apposite istruzioni per generare il risultato dei confronti).
Il termine registri speciali è molto generico e in tal modo ho voluto indicare tutto ciò che non rientra nei precedenti, ma che rimane pur sempre un registro, e quindi accessibile come tale direttamente o indirettamente. Di esempi ce ne sarebbero praticamente per qualunque architettura, ma ne faccio giusto qualcuno.
Il TMS 9900 ha un registro indirizzo per puntare alla zona di memoria in cui mappa tutti i registri dati, i MIPS hanno due registri che contengono il risultato della moltiplicazione o divisione, e gli 8086 hanno 4 registri per specificare l’indirizzo base dei 4 segmenti che possono gestire contemporaneamente (codice, dati, stack ed “extra”).
Il bus dati interno serve a trasportare dati dai registri alle unità di calcolo (e viceversa ovviamente), oppure alla memoria. Da parecchi anni ha ormai poco senso parlare di singolo bus dati, perché le esigenze di parallelismo hanno portato ad averne di diversi che operano “in concorrenza”. Inoltre possono essere presenti bus dati di capienza diversa a seconda del tipo di dato trasportato. Per le GPU questa entità è estremamente importante.
Per il bus dati esterno vale all’incirca lo stesso concetto di quello interno, perché esistono architetture che permettono di generare più richieste di lettura o scrittura allo stesso tempo, avendo integrati più memory controller indipendenti. Comunque quelle più diffuse sono dotate di un solo bus dati esterno. Anche qui, per le GPU è molto importante il bus dati esterno.
Il bus indirizzi interno è utilizzato per trasportare i dati relativi agli indirizzi di memoria a cui si vuole accedere. Valgono più o meno le stesse considerazioni del bus dati interno, tranne per il fatto che la dimensione dei dati è sempre la stessa (e avrebbe poco senso altrimenti, a mio avviso, perché parliamo sempre di accedere a locazioni di memoria, la cui codifica è sempre uniforme).
Anche per il bus indirizzi esterno valgono le stesse considerazioni del bus dati esterno.
I collegamenti punto-punto servono a collegare mono o bidirezionalmente esclusivamente due entità, eliminando quindi la necessità di “arbitri” per la gestione della risorsa (le linee su cui viaggiano le informazioni). Al contrario del bus a cui, per definizione, possono attaccarsi una moltitudine di periferiche (anche se quella che ne fa maggior uso generalmente è la CPU) e servono appositi meccanismi di regolazione degli accessi.
In genere si sfruttano per il collegamento a southbridge, ma in sistemi SMP è ormai comune il loro uso per poter interfacciare fra di loro due o più microprocessori, che si scambiano dati per mantenere coerenti le loro cache oppure per permettere a un processore di accedere alla memoria gestita da un altro (in caso di sistemi NUMA). Un processore può avere diversi collegamenti punto-punto (ad esempio gli Opteron della serie 8x ne hanno ben tre).
Le AGU sono unità dedicate alla generazione degli indirizzi (Address Generation Unit), e servono per effettuare tutti i calcoli necessari per ottenere l’indirizzo (virtuale) finale per quanto riguarda una richiesta di accesso alla memoria.
A seconda dell’implementazione della CPU, possono essercene anche più d’uno (ad esempio i Pentium III ne hanno tre, perché possono eseguire fino a tre istruzioni per ciclo di clock, e ognuna di essere può avere un riferimento alla memoria).
Le ALU sono le classiche unità di calcolo che si sobbarcano l’onere di effettuare somme, sottrazioni, moltiplicazioni, divisioni, ma anche and, or, xor, manipolazioni di campi di bit, ecc. a seconda della complessità dell’ISA. Ovviamente per esigenze di parallelismo possono essercene più d’una, e spesso capita che siano specializzate (alcune possono eseguire più o meno operazioni rispetto ad altre). Operano sempre su registri dati/indirizzi/indici.
L’FPU è l’unità di calcolo che in genere si occupa di eseguire le operazioni di calcoli dei valori in virgola mobile (floating point), ma che permette di eseguirne anche con interi, per rendere più facile operazioni di tipo misto (capita non di rado di eseguire, ad esempio, somme fra valori interi e in virgola mobile). Come per le ALU, possono essercene più d’una e anche specializzate, ma in genere hanno appositi registri su cui lavorano, la cui dimensione può essere diversa da quella degli altri registri.
Infine le SIMD sono unità di calcolo vettoriale che hanno preso piede da un po’ di anni perché permettono di velocizzare notevolmente questo tipo di calcoli. Valgono esattamente le stesse considerazioni delle ALU (generalmente hanno appositi registri).
Ho volutamente tralasciato il concetto di spazio d’indirizzamento dell’I/O, perché riguarda architetture abbastanza vecchie (questo concettualmente, perché è vero che è presente nello Z80, ma anche in 8086 e successori), e comunque in genere sfruttano le stesse risorse (bus dati e indirizzo) per questo tipo di accesso.
Dopo questa lunga, ma necessaria, carrellata (che non è, e non pretende di essere esaustiva sulle tipologie di entità esistenti all’interno delle CPU, e in generale dei processori, ma che abbraccia quelle sicuramente più comuni e note), penso sia chiaro per quale motivo per le CPU, come per i sistemi in cui esse sono presenti, non sia possibile stabilire in maniera oggettiva una loro “misura” in bit.
In buona sostanza ci sono troppe variabili di cui tener conto, e non esiste metodo oggettivo che permetta di sceglierne una, un sottoinsieme, una loro media, ecc.. Inoltre ogni entità citata può riservare delle sorprese, perché… non è sempre possibile determinare una loro oggettiva misura in bit, in quanto a loro volta esistono suoi elementi costitutivi a cui è possibile attribuire “misure” diverse.
Torna, quindi, prepotentemente la famigerata ricorsione, ma potete star tranquilli: non ho intenzione di scendere di un ulteriore livello d’astrazione con un nuovo articolo. Mi limiterò a citare, ove applicabile, dove e perché torna in gioco la composizione di elementi diversi (riproponendo, quindi, il problema della misura del “contenitore”).
E’ opportuno, invece, spendere qualche parola facendo alcuni esempi di “misura” di una CPU considerando una sola delle entità precedentemente citate. In questo modo spero di mettere in chiaro non tanto se una CPU sia misurabile in termini di “n bit”, quanto più che altro perché non sarebbe opportuno farlo tenendo conto soltanto di un certo componente.
Tenendo conto dei soli registri dati, lo Z80 sarebbe un CPU a 8 bit, perché tale è la dimensione di questi elementi. Però questo processore è in grado di “accoppiarli” per eseguire anche operazioni a 16 bit.
Prendendo i registri indice, sempre lo Z80 sarebbe una CPU a 16 bit.
Il PC a 16 bit farebbe del 6502 una CPU a 16 bit.
Lo SP a 16 bit renderebbe tale il 6800.
Il registro dei flag del 68000 è a 16 bit (addirittura a 8 bit in modalità utente).
I machine register (registri speciali) introdotti dal Pentium sono a 64 bit.
Per i bus dati interni preferisco citare la GPU della PlayStation 2 (come avevo anche promesso nei commenti del precedente articolo), che ne possiede tre per un totale di ben 2560 bit (1024 per la lettura, 1024 per la scrittura e 512 bit per la lettura e scrittura).
Come bus dati esterno, quello del 68000 era a 16 bit, mentre quello del Pentium a 64 bit.
Il bus indirizzi interno del PentiumPro è a 36 bit.
Il bus indirizzi esterno del 68008 è a 20 bit.
Il collegamento punto-punto HyperTransport introdotto con gli Athlon64 ha una dimensione configurabile dai 2 ai 32 bit.
L’ALU del 68000 è in grado di eseguire operazioni aritmentiche a 8, 16 e 32 bit, ma in quest’ultimo caso richiede più tempo, in quanto internamente i sommatori sono a 16 bit (ed ecco perché ritorna la famigerata “ricorsione”). Ma il 68000 ha anche ALU dedicate per eseguire velocemente la somma a 32 bit per il PC e per la modalità d’indirizzamento con autoincremento di un registro indirizzi.
L’FPU dell’8086 è in grado di eseguire operazioni su interi fino a 64 bit, e in virgola mobile fino a 80 bit.
Infine le due unità SIMD contenute nella CPU della PlayStation 2 operano su registri a 128 bit (ma il core è basato sul RISC 5900 a 64 bit della famiglia MIPS).
Non ho trovato esempi citabili per i registri indirizzi, ma non sarebbe certamente una chimera una CPU dotata di registri dati a 8 bit, e registri indirizzi a 16 bit. Un esempio del tutto simile si potrebbe fare anche per le AGU, per le quali non ho ugualmente fornito un esempio reale.
A questo punto possiamo dire che di certezze ve ne siano veramente poche quando si tratta di applicare una misura in “bit” di un processore. Il che, beninteso, non vuole dire che sia impossibile; banalmente basterebbe realizzarne uno per il quale tutte le entità in gioco lavorino esattamente con dati della medesima dimensione.
Ma spesso la voglia di etichettare è tale per cui se si chiede a un programmatore a quanti bit sia una certa CPU che conosce, stranamente una risposta è in grado di fornirla subito. Avrà utilizzato, a questo punto, un proprio metro di giudizio, ma quale?
Da programmatore espongo l’idea che mi sono fatto. A me interessa lavorare con l’ISA della CPU, quindi coi registri e le istruzioni che su di essi operano. Quindi considero una CPU a “n bit” se i registri dati/indirizzi/indici sono a “n bit”, e se su buona parte delle operazioni posso operare a “n bit”.
Quindi anche se un 68000 ha più istruzioni che operano a 8 bit (ad esempio quelle per i dati BCD), ha i registri a 32 bit e buona parte delle istruzioni operano con queste quantità.
Uno Z80 lo considero a 8 bit perché, pur avendo registri a 16 bit (o combinabili come tali) ne ha pure a 8 bit, e le operazioni a 16 bit sono in numero decisamente più ridotto rispetto a quelle a 8 bit.
Il progettista di una scheda madre, invece, avrà un altro metro di giudizio. Ad esempio, un 68000 ha un bus dati esterno a 16 bit, quindi comunica con la memoria e le periferiche a 16 o 8 bit, per cui per lui sarà una CPU a 16 bit. E un 68008, col suo bus dati esterno a 8 bit, lo tratterà come CPU a 8 bit.
Chi avrà lavorato alle SPE di Cell le considerarà come processori a 128 bit, perché operano per lo più su dati con questa dimensione.
Infine per chi realizza GPU, invece, potrebbe essere più importante il bus dati interno, perché lo scambio di dati internamente è più critico rispetto a quello dei dati esterni.
Si tratta di posizioni perfettamente lecite, perché lo stesso oggetto viene visto in maniera diversa a seconda del contesto in cui si opera.
Ovviamente ci sono anche persone che soffrono di ansia da prestazione, per cui sapere che hanno un oggetto con più bit rispetto a un altro (qualunque sia la metodologia usata per la misura) le fa stare meglio.
Poi ci sono i markettari che, pur di vendere, farebbero carte false per tirare fuori un numero quanto più grande possibile, così da far colpo sull’immaginario collettivo (in particolare sulle persone precedentemente menzionate). Ed ecco spiegato perché la PlayStation 2 è stata venduta come console a 128 bit; anche se forse hanno peccato un po’ d’ingenuità, visto che potevano contare su una GPU a ben 2560 bit…