In questo articolo vedremo e commenteremo lo schema della CPU MIPS ad un colpo di clock, per capire a cosa servono certe unità funzionali.

Ecco lo schema semplificato di una CPU MIPS ad un colpo di clock.

architettura-mips-1-colpo-clock

Capisco che a prima vista può essere una brutta esperienza, ma per tranquillizzarti ti dico che non è terribile da capire, e dopo un po' di ragionamento diventa anche un po' simpatico.

Le fasi di un colpo di clock

Come inizio è utile tenere a mente le fasi di esecuzione di un'istruzione:

  • Fetch: l'istruzione viene presa dalla memoria istruzioni;
  • Decode: l'istruzione viene spacchettata, e i singoli pezzi assumono il loro significato;
  • Execute: l'ALU esegue un calcolo;
  • Memory: eventualmente il risultato dell'ALU o la lettura da un registro viene scrita in memoria, oppure la memoria viene letta;
  • Write-Back: eventualmente il risultato dell'ALU o la lettura da memoria viene scritta in un registro.

Ora è abbastanza veloce vedere delle similitudini con lo schema:

  • Fetch: comprende il PC (Program Counter) e l'adder vicino;
  • Decode: comprende lo spacchettamento dell'istruzione, l'unità di controllo e la lettura dai registri;
  • Execute: riguarda la parte in cui l'ALU opera;
  • Memory: accesso alla memoria dati;
  • Write-Back: l'uscita del mux in basso a destra.

È opprtuno però far presente che nella CPU ad un colpo di clock questa separazione delle fasi non è proprio netta; capiremo meglio il motivo quando vedremo la CPU con pipeline.

Formato delle Istruzioni

In architettura MIPS abbiamo 3 tipi di istruzioni: R, I, J. Nell'articolo sull'assembly MIPS approfondisco questo aspetto, ma ai fini di questa spiegazione ci serve solo in generale il formato delle istruzioni.

Tipo Formato (bit)
R oc (6) rs (5) rt (5) rd (5) shamt (5) funct (6)
I oc (6) rs (5) rt (5) imm (16)
J oc (6) dest (26)

Alcuni argomenti sono comuni tra R ed I, mentre altri variano a seconda del formato. Facciamo chiarezza:

  • oc: è l'opcode dell'istruzione, ovvero un codice che la identifica (e quindi ne determina il formato);
  • rs: registro sorgente;
  • rt: registro target;
  • rd: registro destinazione;
  • shamt: shift amount; se l'operazione è uno shift, rappresenta il numero di posizioni da shiftare;
  • func: insieme al segnale di controllo AluOp determina la funzione da passare all'ALU;
  • imm: parte immediata, ovvero una costante;
  • dest: costante che indica l'indirizzo a cui saltare.

Segnali di Controllo

Dopo aver letto l'opcode, l'unità di controllo attiva opportuni segnali di controllo, affinché la CPU si comporti bene. Sullo schema i segnali di controllo sono evidenziati in blu, e sono:

  • RegDst: sceglie il registro di destinazione tra rd ed rt. È utile perché permette di supportare le due codifiche R ed I senza aggiungere troppa circuiteria. Semplicemente, a seconda del formato viene scelto uno dei due argomenti come destinazione;
  • Jump: attivo se l'istruzione è della famiglia jump;
  • Branch: attivo se l'istruzione è della famiglia branch;
  • MemRead: attivo se l'istruzione legge dalla memoria;
  • MemToReg: attivo se il dato letto dalla memoria deve essere schiaffato in un registro;
  • AluOp: identifica l'operazione da far eseguire all'ALU (riporto le istruzioni):
    • 00: addi, lw, sw;
    • 01: beq, bne;
    • 1x: dipende dal campo func:
      • 32: add;
      • 34: sub;
      • 36: and;
      • 37: or;
      • 42: slt.
  • MemWrite: attivo se l'istruzione scrive in memoria;
  • AluSrc: attivo se il secondo operando dell'ALU deve essere il campo imm;
  • RegWrite: attivo se l'istruzione scrive in un registro.

Molto carino. Ora tutti i segnali di controllo hanno un senso e non ci spaventano più; quindi possiamo passare ad altro.

Analisi dello Schema

Spezziamo lo schema in blocchi, e commentiamolo.

Fetch

In questa fase dobbiamo prendere la prossima istruzione dalla memoria istruzioni. Sappiamo che tutte le istruzioni sono lunghe una word (4 byte), quindi abbiamo un bell'adder che fa solo il calcolo indirizzo_attuale + 4;

La gestione di salti o branch avviene nella fase successiva, cioè quando abbiamo i segnali di controllo ben impostati.

Decode

Qui dobbiamo spacchettare l'istruzione ed in base all'oc attivare i segnali di controllo opportuni. Vediamo meglio come la CPU si comporta.

Branch e Jump

Qui fa comodo analizzare insieme questi due segnali di controllo, per vedere come si altera il PC:

  • Se Branch è attivo, significa che dobbiamo saltare se la condizione è vera. Il controllo della condizione viene fatto dall'ALU con una differenza tra i valori dei due registri da controllare. Se il risultato è 0 significa che la condizione è verificata, e quindi dobbiamo saltare. Per questo abbiamo Branch AND 0 che attiva il mux che sceglie tra il risultato dell'adder visto prima, e l'indirizzo relativo del salto;
  • Se Jump è attivo, dobbiamo saltare all'indirizzo contenuto nel campo dest, quindi il PC viene aggiornato con l'indirizzo del salto.

Ti lascio una domanda che ti serve per ragionare meglio su questa fase: che succede se Jump e Branch sono entrambi attivi?

Ora in base ai valori dei campi dell'istruzione, vengono letti i dati dai registri.

Execute

Abbiamo gli argomenti dell'ALU e l'operazione da eseguire. L'ALU calcola, ed il risultato può rappresentare:

  • L'indirizzo da cui leggere in RAM;
  • L'abilitazione al salto di un branch;
  • Un valore da scrivere in un registro.

Memory

In base a quanto detto poco fa ed ai segnali di controllo, si può leggere o scrivere.

Write-Back

Semplicemente:

  • Se MemToReg è attivo, il dato letto viene buttato direttamente nel registro di destinazione;
  • Altrimenti viene scritto il risultato dell'ALU.

Abbiamo finito.

Conclusione

Per riordinare le idee, oggi abbiamo scoperto:

  • Come è fatta una CPU MIPS ad un colpo di clock;
  • Quali sono le fasi di esecuzione;
  • Quali segnali di controllo esistono, e come vengono attivati;
  • Come i segnali di controllo influiscono nel flusso di esecuzione;
  • Che i segnali di controllo sono importanti;

Per fissare meglio questi concetti, ti consiglio di fare questo tipo di esercizio:

  1. Prendi un'istruzione;
  2. Ragiona sui segnali di contollo che devono essere attivi;
  3. Segui il flusso di quell'istruzione.

Ad esempio, prendiamo lw $reg, ADDR.
I segnali di controllo saranno:

  • RegDst: 0;
  • Jump: 0;
  • Branch: 0;
  • MemRead: 1;
  • MemToReg: 1;
  • AluOp: sum;
  • MemWrite: 0;
  • AluSrc: 1;
  • RegWrite: 1.

Continua tu con altre istruzioni. Buona fortuna!

Per qualsiasi cosa, lascia un commento.