L’architettura ARM ha subito consistenti cambiamenti nel corso della sua storia, come abbiamo cercato di evidenziare nel precedente articolo .
Uno dei più grossi, ma poco noti e/o apprezzati, è rappresentato dall’introduzione di nuove modalità di esecuzione del microprocessore, che corrispondono effettivamente a delle nuove ISA implementate e, quindi, a decoder appositamente dedicati per le istruzioni codificate in questi nuovi formati.
Non era certamente un passo obbligato per ARM Ltd, essendo la sua un’architettura ben delineata, ma, essendo un’azienda particolarmente attenta alle esigenze del mercato, s’è trovata davanti non a un capriccio, quanto alla necessità tutt’altro che remota di migliorare la densità del codice, a cui ha dato seguito integrando nella quarta versione della sua famiglia quella che ha poi chiamato modalità Thumb.
Come abbiamo già appurato, da diverso tempo quest’azienda opera principalmente nel settore dei dispositivi embedded, arrivando ad abbracciare anche smartphone e PDA (e, di recente, anche i netbook), per i quali il software viene distribuito sotto forma di firmware (eventualmente aggiornabile).
Il firmware è, in genere, una memoria non volatile (EEPROM o Flash) che può variare da pochi MB fino a un centinaio (almeno per ora), e che racchiude tutto ciò che è necessario per l’avvio (reset o reboot) e il caricamento del sistema operativo, visto che ormai da tempo questi gioielli tecnologici sono diventati estremamente complessi e non è più sufficiente un sistema “minimale” (giusto per far partire l’applicazione che si occupava di tutto).
Per essere precisi, un po’ di anni fa non serviva un s.o. con questi aggeggi. Erano talmente semplici che nel firmware era presente soltanto una routine che serviva al setup del sistema all’avvio, che passava poi il controllo a un codice “factotum”, il quale si occupava di gestire qualunque cosa: agenda degli appuntamenti, blocco note, mini foglio di calcolo, sveglia, ecc.
Oggi PDA o smartphone offrono parecchie funzionalità, hanno delle interfacce grafiche sciccose e richiedono pertanto software estremamente complessi per gestire il tutto (anche se non digerirò mai i tempi biblici di accensione dei Nokia). Ovviamente ci sono sistemi embedded che continuano più o meno come prima, perché hanno esigenze ridotte.
In ogni caso la presenza di un firmware su una memoria non volatile comporta un certo costo per i produttori, per cui trovare il modo di ridurre lo spazio occupato è sempre una cosa “buona e giusta”. Coi RISC, però, la storia è sempre la stessa da quando sono nati: avendo degli opcode a dimensione fissa, la dimensione del codice rispetto ai CISC (che usano opcode a dimensione variabile) è mediamente maggiore.
ARM, tanto per cambiare, non fa eccezione. Pur essendo dotata di un’architettura molto flessibile, con esecuzione condizionale del codice, ben 16 registri general purpose, istruzioni con 3 registri, e la possibilità di utilizzare il barrel shifter interno in buona parte delle operazioni di calcolo (come ho spiegato nel primo articolo a esso dedicato), utilizza opcode di 32 bit. Quindi anche l’operazione più semplice (ad esempio una NOP, che non fa nulla) presenta una costo fisso di ben 4 byte.
Per confronto, un microprocessore CISC che utilizza opcode di dimensione variabile minima di 8 bit (come un x86) per le operazioni più semplici richiede soltanto un byte. Un CISC della famiglia 68000, che ha opcode di dimensione minima di 16 bit, ne impiegherà 2, ma è comunque un netto risparmio rispetto ai 4 minimi (e massimi) di un RISC come l’ARM (e tanti altri).
Confronti di questo tipo, però, non vanno fatti sulla sola base della dimensione minima degli opcode (non è questo l’obiettivo dell’articolo), ma servono soltanto per introdurre il concetto. Quella della maggior densità di codice è stato, ed è ancora, uno dei buoni motivi per cui chi lavora in settori embedded preferisce un CISC a un RISC, e a cui aggiungiamo anche la generale maggior facilità di scrittura di codice a basso livello (in assembly), sempre coi primi.
Tutto ciò ha portato ARM, come dicevo, a introdurre la modalità Thumb, con la quale il processore si trova a dover leggere dalla memoria opcode di dimensione fissa a 16 anziché a 32 bit. Conti alla mano, la dimensione minima è scesa a 2 soli byte, risparmiando fino al 35% di spazio per il codice rispetto all’equivalente a 32 bit.
Chiaramente la “cura dimagrante” non ha portato via dei bit “inutili”, per cui l’utilizzo di opcode di dimensione ridotta ha comportato notevoli modifiche e restrizioni all’utilizzo delle caratteristiche presenti nel core, primo fra tutti il numero di registri direttamente manipolabili che è passato da 16 a 8 (i primi, da R0 a R7).
In realtà gli altri non sono stati “fatti fuori”, ma è possibile accedervi con apposite istruzioni (si può copiare il registro R8 in R3, tanto per fare un esempio). Inoltre altri registri sono stati “rimappati” opportunamente e vengono utilizzati più o meno implicitamente, a seconda del contesto.
Il PC (Program Counter, R15) viene comunque sfruttato per recuperare gli opcode; SP (Stack Pointer, R13) si usa per simulare lo stack, e infine LR (Link Register, R14) conserva l’indirizzo di ritorno dalla chiamata a subroutine. A conti fatti, soltanto 5 registri (da R8 a R12) rimangono fuori e richiedono apposite istruzioni per accedervi. 11 registri su 16 rappresenta comunque un buon risultato.
Sul fronte delle istruzioni, manco a dirlo, si trovano i cambiamenti più profondi:
- le istruzioni che possono utilizzare tre registri sono soltanto quelle di somma e sottrazione
- sempre somma e sottrazione sono le uniche che possono utilizzare due registri e un valore immediato (limitato da 1 a 8)
- sono state introdotte apposite istruzioni di shift (prima del tutto assenti: si utilizzava il barrel shifter direttamente nelle istruzioni, se serviva)
- diverse istruzioni consentono di utilizzare un valore immediato a 8 bit (somma, sottrazione, confronto, assegnamento, somma a PC o SP, e infine invocazione a interrupt software)
- sono presenti numerose istruzioni di load/store, che in pratica codificano soltanto alcune (le più utili o necessarie) delle modalità d’indirizzamento utilizzabili con gli opcode a 32 bit
- fanno il loro ingresso i famigerati salti condizionati (con offset limitato a 8 bit)
- i salti incondizionati sono limitati a offset a 10 bit
- i salti a subroutine richiedono l’uso di due istruzioni che, “concatenate” (eseguite una di seguito all’altra), forniscono un offset a 20 bit
A questo punto per chi ha avuto modo di lavorarci o studiarla, la domanda che sorge spontanea è: che fine ha fatto l’elegante architettura ARM? Non c’è, è sparita: questa non ne rappresenta nemmeno l’ombra. E infatti, checché ne dica ARM parlando di Thumb come di una “estensione” della tradizionale ARM, questa rappresenta, a tutti gli effetti, un’altra ISA, e pure molto diversa.
Agli occhi di qualcuno potrà sembrare ingiustificato uno stravolgimento di questa portata, col solo scopo di migliorare la densità del codice, ma l’architettura ARM è comunque utilizzabile (almeno per ora), perché è possibile passare da ARM a Thumb e viceversa in maniera molto veloce, grazie a delle apposite istruzioni.
Inoltre il minor spazio occupato dal codice presenta benefici effetti anche sulla banda di memoria, che risulta ridotta in misura almeno equivalente allo spazio occupato, con risparmi ancora maggiori in presenza di cicli (per i quali viene speso la maggior parte del tempo).
Si riducono anche gli accessi alla memoria, poiché spesso vengono caricate da essa word a 32 bit quando si tratta di trasferire all’interno del core gli opcode da eseguire. Infatti in una word si possono memorizzare due opcode a 16 bit, per cui il secondo non necessita di alcun fetch: si trova già a disposizione.
I puristi continueranno a storcere il naso, in quanto l’architettura risulta del tutto snaturata, ma bisogna essere pragmatici: con Thumb, ARM ha un’ottima carta da giocare nei confronti della concorrenza che da anni offre codice più compatto, consentendo ai produttori di dispositivi basati su questa CPU di risparmiare tanti soldi.
Turiamoci il naso (per chi proprio non ce la fa) e andiamo avanti…