Struttura processore RISC-V (start point)

In questa serie di paragrafi ci proponiamo di costruire un processore RISC-V che si in grado di eseguire:

  • le istruzioni aritmetico-logiche di tipo R: add, sub, and, or
  • le istruzioni di riferimento alla memoria come ld (load doubleword) e sd (store doubleword)
  • le istruzioni di salto condizionato dal risultato di un test di uguaglianza come beq

Richiamo alla macchina di Von Neumann
...

La CPU si compone di diverse parti: unità di controllo, unità aritmetico logica e registri.
I registri, l'unità aritmetico-logica e alcuni bus che li collegano compongono il data path
Due registri particolarmente importanti sono il PC (program counter) e IR (instruction register).
La memoria principale contiene sia istruzioni, sia dati (sotto forma di sequenze di bit).
Pasted image 20230618163658.png

Richiamo ciclo di fetch-decode-execute
...

La CPU esegue ogni istruzione per mezzo di una serie di passi elementari:

  1. Volendo prendere l'istruzione successiva (avendo appena finito nel ciclo precedente l'istruzione corrente), cambia PC per indicare l'istruzione seguente
  2. Determina il tipo dell'istruzione appena letta
  3. Se l'istruzione usa una word in memoria, determina dove si trova tale word
  4. Se necessario la carica in un registro della CPU
  5. Esegue l'istruzione
  6. Torna al passo 1

Data path semplificato
...

Pasted image 20230618162216.png

Fase di fetch
...

Pasted image 20230618162546.png
Il PC fornisce l'indirizzo della memoria in cui si trova l'istruzione successiva.
Per semplificazione, chiamiamo questa zona della memoria, memoria delle istruzioni e assumiamo che sia solo leggibile e non modificabile.
Assumiamo anche che ci siano due memorie diverse: una per le istruzioni e una per i dati.

Fase di decode
...

La ALU deve capire il tipo di istruzione (quale operazione è richiesta dall'istruzione recuperata dal PC) e quindi per esempio caricare gli operandi passati nell'istruzione. L'unità di controllo (non mostrata nella figura) è colei che effettivamente decodifica le istruzioni.
Pasted image 20230618163002.png

Fase di execute
...

Caricati gli operandi, si può:

  • eseguire un calcolo effettivo (operazioni aritmetico-logiche su interi)
  • elaborare il loro contenuto per determinare un indirizzo di memoria (nel caso di load e store)
  • eseguire un confronto (salto condizionato)
    Pasted image 20230618163314.png
    Pasted image 20230618163329.png
    Pasted image 20230618163359.pngPasted image 20230618163429.png

Nelle seguente figura mostriamo l'unità di controllo che contiene i circuiti che effettivamente decodificano le istruzioni, vedremo nel dettaglio ogni singola parte di questi circuiti, non è necessario comprendere adesso il funzionamento di tale unità. (Nella macchina di Von Neumann è la control unit).
Pasted image 20230618163639.png

Metodologia di temporizzazione
...

La metodologia di temporizzazione usata per determina quando un dato è valido e stabile è la temporizzazione sensibile ai fronti (edge-triggered): una tecnica di temporizzazione nella quale tutti i cambiamenti di stato avvengono su un fronte del segnale di clock. In un sistema digitale sincrono, il segnale di clock (fronti in questo caso) determina quando gli elementi di stato scrivono la loro memoria interna.
Gli ingressi a un elemento di stato devono raggiungere un valore stabile prima che il fronte attivo del clock provochi l'aggiornamento dello stato, supporremo che tutti gli elementi, memorie comprese, siano sensibili al fronte di salita. E quindi gli input che vengono passati a dei dispositivi che memorizzano uno stato devono essere stabili prima che si verifichi un fronte di salita.

La metodologia sensibile ai fronti consente di leggere e scrivere un elemento di stato nello stesso ciclo di clock. Ovvero se l'input ad un elemento di stato è stabile prima del fronte di salita, allora viene cambiato lo stato di tale elemento e per la durata del clock corrente, tale cambiamento può essere letto.

Realizzazione unità di elaborazione
...

Program Counter
...

Abbiamo bisogno di alcuni circuiti digitali già visti:

  • una unità di memoria in cui salvare le istruzioni del programma, che si in grado di fornire in uscita l'istruzione associata all'indirizzo dato in ingresso (a).
  • program counter (PC), un registro utilizzato per memorizzare l'indirizzo dell'istruzione corrente, la memoria viene detta byte addressed, ovvero che l'unità di indirizzamento di base è un singolo byte. In questa architettura il registro è a 64 bit (b).
  • adder, ci serve per incrementare il PC all'istruzione successiva, tutte le istruzioni RISC-V sono rappresentate su 32 bit, quindi il PC salta sempre di 32 bit, ovvero 4 byte (c).
    Pasted image 20230618165255.png
    Di seguito il circuito per incrementare il PC:
    Pasted image 20230618170150.png
    Vediamo come avanza il PC:
    Pasted image 20230618170343.png
    Immaginiamo di avere le tre istruzioni in basso a destra.
    Immaginiamo anche di trovarci (in alto a destra) nel momento in cui si verifica un fronte nel clock.
    Il PC passa l'indirizzo dell'istruzione corrente 0x0..400020 alla memoria, da essa viene prelevata l'istruzione e viene sputata fuori, nello stesso tempo, il PC passa l'indirizzo della stessa istruzione al sommatore che aggiunge 4 byte per passare alla prossima istruzione. Per cui all'inizio al prossimo fronte, nel PC sarà contenuta l'istruzione successiva.
    Pasted image 20230618170842.png
Datapath per le istruzioni del tipo R
...

Per questo tipo di istruzioni (R) ci servono altri due circuiti: Register File e la ALU.
Pasted image 20230618171544.png
Pasted image 20230618171733.png
Quando viene sputata dalla memoria l'istruzione (indirizzata dal PC)
Pasted image 20230618172144.png
Ai registri di lettura in ingresso del register file arrivano i due registri (rs1 e rs2).
Il register file restituisce i dati letti nei due registri passati in input.
Nel caso dell'istruzione specifica considerata add x1, x2, x3 è richiesta una somma tra due registri che vengono passati alla ALU.
Pasted image 20230618172445.png
Il risultato della somma deve essere scritto nel registro indicato nell'istruzione (x1), tale registro viene passato nel registro di scrittura (nello stesso tempo vengono passati i registri di lettura):
Pasted image 20230618172736.png
Il dato viene scritto se il segnale di controllo RegWrite è abilitato. (La scrittura avviene sul fronte del clock, per cui prima che il clock si alzi, il dato da scrivere, il numero del registro e il segnale di controllo devono essere validi).
Essendo la scrittura attiva sui fronti, l'architettura può leggere e scrivere lo stesso registro nello stesso ciclo di clock, infatti add x1, x1, x2 è lecita.
Gli ingressi che specificano al register file il numero del registro hanno ampiezza di 5 bit (registri) mentre i byte che trasportano i dati sono ampi 64 bit.
Nella ALU vi sono (in azzurro nella figura) 4 bit di controllo per selezionare l'operazione da eseguire. L'uscita zero della ALU è utilizzata per i salti.

Datapath per istruzioni del tipo I (load) e S (store)
...

Ci servono altri circuiti digitali:

  • una memoria dati
  • un'unità di estensione del segno
    Pasted image 20230618174057.png
    L'unità di memoria è un elemento di stato. In ingresso prende:
  • l'indirizzo del dato (dalla ALU)
  • l'indirizzo da scrivere (dai Registri)
    In uscita restituisce: il dato letto (ai Registri).

Il datapath diventa:
Pasted image 20230618174130.png
Diversamente da quanto avviene per il register file, la memoria dati ha necessità di un segnale di lettura esplicito (in azzurro: MemRead).
Come nel caso del register file, la scrittura della memoria è attiva su un fronte del segnale di clock
Quando viene richiesto il caricamento in un registro di un dato in memoria l'istruzione è del tipo I:
ld rd, imm(rs1)
Ovvero viene caricato in rd il dato da leggere che viene prelevato da rs1 con offset pari a imm.
Ricordiamo il formato delle istruzioni del tipo I e S:
Pasted image 20230618174406.png
Quando nel register file arriva questo tipo di richiesta rs1 si trova sempre nello stesso posto (nella sequenza di bit delle istruzioni) e arriva sempre in registro di lettura 1.
Il registro rs1 contiene l'indirizzo di memoria da cui si vuole leggere.

La stessa istruzione viene passata al modulo di estensione del segno che estende il segno dei bit nel campo immediate, prende i 12 bit del campo immediato e ne estende il segno, il motivo è che dobbiamo sommare l'indirizzo base contenuto in rs1 con l'offset, contenuto nel campo immediate, ma l'ALU fa operazioni a 64 bit quindi è necessario estendere il segno del campo immediato.

Tale sequenza (estesa) viene passata come operando alla ALU, che ha ricevuto come operando anche (rs1), l'unità di controllo fa in modo che venga selezionata tra le operazioni della ALU una somma, in questo modo al contenuto del registro rs1 (indirizzo della memoria da cui si vuole leggere) si deve sommare l'offset (per prelevare il byte, la word, o la doubleword di cui si necessita). Tale somma restituisce l'indirizzo della memoria in cui si trova il dato che si vuole leggere.

Se è attivo MemRead allora il dato viene letto. E mandato indietro al register file nell'ingresso Dato da scrivere, RegWrite sarà abilitato e viene scritto il dato nel registro.

Nota che RegWrite è abilitato anche nel caso di una somma, di un or, di un and, poiché il risultato dell'operazione deve essere salvato, quindi sarà necessario scegliere quando si scrive se si scrivere perché è stata eseguita una operazione, o un caricamento da memoria (si risolverà tutto alla fine di questo documento, tieni da parte per ora il fatto che devono essere prese delle scelte, immagina che ogni operazione sia eseguita in autonomia, senza essere in concorrenza con le altre).

Nel caso dell'operazione di store invece:
sd rs2, imm(rs1)
in rs1 + offset (imm) viene scritto ciò che è contenuto in rs2, in questo caso RegWrite deve essere non attivo, poiché vogliamo salvare in memoria un dato presente in un registro. In tal caso i registri rs1 e rs2 non devono essere operandi di una qualche operazione della ALU, RegWrite inattivo impedisce proprio questo. Invece la ALU, anche in questo caso, viene usata per estendere il segno del campo immediato da sommare all'indirizzo base (contenuto in rs1). MemWrite sarà attivo e sarà stato ricevuto in Dato scritto il valore del registro rs2 (dato da scrivere in memoria).

rs1 e anche (rs2 quando serve) è sempre nello stesso posto

Il motivo è che in tal modo viene reso più semplice dirigere i segnali che riguarda tale registro (ed quando necessario anche rs2) verso la stessa porta, risparmiando logica aggiuntiva. Gli operandi di una qualche operazione sono sempre nello stesso posto.

L'istruzione di salto
...

L'istruzione di salto richiede il confronto fra due valori e che consente il trasferimento del controllo a un altro indirizzo del programma a seconda del risultato del confronto.
Ci serve adesso aggiornare il program counter, per passare ad altre istruzioni sparse per il codice che si sta leggende (col PC).

Pasted image 20230618194054.png
rs1 e rs2 ci serve per fare i confronti e vedere se sono uguali.
La ALU facendo la sottrazione tra due registri riesce a controllare se due registri sono uguali (vedi ALU) facendo la sottrazione e vedendo se il risultato è uguale a 0, mettendo in uscita 1 (nell'uscite zero) se il risultato è uguale a 0.

Nel caso dell'istruzione di branch quello che vogliamo fare è fare puntare il PC all'indirizzo specificato nel campo immediato dell'istruzione, sommato al valore attuale del PC. In questa situazione la ALU è già occupata, poiché è stata appena fatta la sottrazione tra i due registri. Quindi aggiungiamo un altro sommatore.
Pasted image 20230618194655.png
Con 12 bit riusciamo a fare salti da .
Visto che ogni operazione RISC-V è di 32 bit, sappiamo che il numero di cui saltiamo è pari, per cui facendo uno shift a sinistra (abbiamo un bit in più posto a 0) e riusciamo a fare salti maggiori da (, è e non , poiché appunto, possiamo saltare solo di una quantità pari, l'ultimo bit sarà sempre 0.

Unita di elaborazione unificata
...

Datapath operazioni di tipo R
Pasted image 20230618200006.png
Datapath operazioni di tipo I
Pasted image 20230618200029.png
Datapath operazioni di branch
Pasted image 20230618200049.png
Come unificare tutto in un unico processore?
Pasted image 20230618200209.png
Mettiamo dei multiplexer, nei punti in cui bisogna prendere delle scelte che vengono controllati dall'unità di controllo.
Pasted image 20230618200252.png
Adesso vediamo l'unità di controllo utilizzata da questo processore RISC-V.