Il termine homebrew si riferisce a qualcosa che è stato creato, realizzato o prodotto in modo amatoriale, non ufficiale o indipendente. Il termine è utilizzato in diversi contesti per indicare cose che sono state fatte da appassionati od hobbisti, anziché da professionisti o da aziende. In una bella guida dal titolo “16-bit Serial Homebrew CPU“, Jiri Stepanovsky spiega come creare un processore seriale a 16 bit perfettamente funzionante.
Sulla carta, realizzare da zero una CPU ricorrendo a componenti elettronici facili da reperire, può richiedere l’utilizzo un gran numero di chip logici. È valutazione comune che l’implementazione di registri, Program Counter, ALU e altri componenti in logica TTL o CMOS richieda una discreta quantità di chip. Ma quanti esattamente? Prova a rispondere alla domanda proprio Stepanovsky che in realtà dimostra come sia possibile mettere a punto un processore seriale a 16 bit con soli 8 circuiti integrati.
Registri, Program Counter e ALU nelle CPU
Nell’articolo dedicato a come funziona un processore, abbiamo messo in evidenza quali sono i principali componenti di una moderna CPU. In un altro approfondimento, ci siamo soffermati sul significato di CPU e sulle operazioni che svolge. I registri sono piccole unità di memoria interne alla CPU utilizzate per archiviare dati temporaneamente durante le operazioni di elaborazione. I registri sono estremamente veloci se confrontati con la memoria principale.
Il Program Counter è un registro che tiene traccia dell’indirizzo di memoria corrispondente all’istruzione successiva da eseguire. Il contatore è incrementato al completamento di ciascun istruzione e passa a puntare all’indirizzo della successiva istruzione da eseguire. Il Program Counter è essenziale per controllare il flusso di esecuzione delle istruzioni all’interno del programma.
L’ALU (Arithmetic Logic Unit) è la parte della CPU che si occupa dell’esecuzione delle operazioni aritmetiche (come l’addizione e la sottrazione) e logiche (come AND, OR, NOT) su dati binari. L’ALU prende in input dati dai registri, esegue l’operazione richiesta e produce l’output che si procede quindi ad archiviare nuovamente nei registri.
Creare un processore seriale: cos’è
Nella sua guida, Stepanovsky descrive la creazione di un processore seriale ovvero di una CPU che elabora le istruzioni in modo sequenziale, ovvero una dopo l’altra, anziché in parallelo. Questo significa che il processore completa un’istruzione prima di passare alla successiva. Contrariamente a un’architettura in cui più istruzioni risultano elaborate contemporaneamente (architettura parallela), in un’architettura seriale ogni istruzione deve essere completata prima che l’istruzione successiva sia presa in considerazione.
Il fatto che si tratti di una CPU a 16 bit significa invece che tale è la lunghezza delle parole di istruzioni e dati che il processore può elaborare in una singola operazione. In un processore a 16 bit, le parole sono composte da 16 bit. In un altro articolo abbiamo parlato della corsa 8, 16, 32, 64 bit nel caso di processori e sistemi operativi.
Il processore presentato da Stepanovsky non può ovviamente competere con le CPU moderne: non c’è storia nell’esecuzione di operazioni complesse che richiedono l’elaborazione di un gran numero di istruzioni. Anzi, è possibile svolgere operazioni tutto sommato relativamente semplici, ma volete mettere la soddisfazione di realizzare un processore da soli?
La struttura del processore seriale a 16 bit realizzabile con 8 circuiti integrati
Stepanovsky mostra come creare un processore con 128kB di SRAM, 768kB di memoria flash e fino a 10 MHz di clock. Come si vede in questo video, il sistema utilizza una soluzione che di fatto può essere paragonata a un’unica ALU a 1 bit, ma la maggior parte delle sue 52 istruzioni lavorano su valori a 16 bit.
Utilizzato al massimo del suo potenziale, il processore può eseguire circa 12.000 istruzioni al secondo (0,012 MIPS) e, tra le altre cose, è in grado di visualizzare delle informazioni su un display LCD basato su PCD8544 (Nokia 5110) a circa 10 frame per secondo.
L’architettura si ispira ad altri progetti che si servono di una EEPROM, EPROM o ROM per generare segnali di controllo e trasferirli ai componenti della CPU. Nel suo esempio, Stepanovsky ha utilizzato una EPROM puntando poi a ridurre il più possibile, come anticipato in apertura, il numero di componenti complessivamente utilizzati.
Tabella di ricerca al posto della traduzione ALU
L’autore del progetto racconta di aver optato per tutta una serie di semplificazioni, a partire dalla completa sostituzione dell’ALU con una tabella di ricerca (lookup table). Eliminare completamente l’ALU e sostituirla con una lookup table significa trasformare le operazioni di calcolo e logica dell’ALU in un processo di ricerca. Invece di eseguire direttamente le operazioni matematiche e logiche all’interno di un’ALU tradizionale, è possibile utilizzare tabelle di ricerca precalcolate per ottenere i risultati desiderati.
In una CPU tradizionale, come abbiamo già raccontato, l’ALU è responsabile di eseguire le operazioni matematiche e logiche come addizione, sottrazione, AND, OR, NOT e altre. Rimuovere completamente l’ALU significa che queste operazioni sono affrontate in modo diverso. L’approccio della lookup table coinvolge la creazione di tabelle di ricerca che contengono tutti i possibili risultati per diverse combinazioni di input. In altre parole, le operazioni sono calcolate in anticipo e i risultati memorizzati in queste tabelle. Quando è necessario eseguire un’operazione, la CPU consulta la tabella corrispondente per ottenere il risultato corrispondente all’input fornito.
Utilizzo di memoria SRAM e flash
L’uscita dall’ALU a 1 bit è gestita con una SRAM seriale che elimina la necessità di registri, poiché tutte le operazioni dell’ALU possono essere eseguite direttamente sui dati in SRAM. Selezionando il numero di cicli di clock della SRAM da impegnare, l’autore del processore seriale ha scelto appunto 16 bit (16 cicli di clock SRAM per 1 operazione ALU) come ottimo compromesso tra funzionalità e prestazioni.
Per le operazioni ALU con 2 operandi (come ADD/AND/XOR…), sono necessari 2 ingressi serializzati. Aggiungere una terza SRAM potrebbe sicuramente essere un’opzione (2 per gli input dell’ALU, 1 per il risultato), ma c’è una soluzione migliore. Se viene utilizzata una memoria flash seriale al posto di una SRAM, gli stessi vantaggi rimangono (dati già serializzati, indirizzo serializzato), ma la flash può essere utilizzata anche per memorizzare le istruzioni/il programma oltre a fornire l’input dell’ALU.
Stepanovsky chiarisce inoltre che non è necessario aggiungere hardware per il Program Counter, poiché c’è già abbondanza di spazio all’interno delle SRAM, dove il suo valore può essere tranquillamente annotato.
Il circuito è costruito intorno a una EPROM M27C1001-15 da 128 KB operativa a 5V; le due SRAM seriali 23LCV512 sono a 64 KB; la flash seriale W25Q80 è da 1 MB.
Dialogare con la CPU utilizzando codice Assembly
Per colloquiare con la sua CPU, Stepanovsky ha utilizzato lo strumento open source customasm per generare file binari dal codice sorgente Assembly. I file binari possono quindi essere caricati utilizzando una piccola applicazione Python sul microcontroller di programmazione ATtiny13: permette di scrivere i dati binari nella memoria flash.
L’autore del progetto ha quindi presentato diversi esempi pratici: una semplice routine che restituisce il risultato a 32 bit della moltiplicazione di due valori a 16 bit, un’altra che mostra sul display LCD una stringa ASCII memorizzata all’interno della flash, un motore di proiezione di oggetti wireframe 3D minimalista che fa uso dell’aritmetica a virgola fissa a 16 bit.