Menu

Hardware e software: perché Verilog ragiona in modo diverso da C o Python

Perché Verilog disorienta chi arriva dai linguaggi software: concorrenza per default, il tempo come concetto di prima classe e istruzioni che non vengono eseguite in ordine.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Il tempo del software e il tempo dell'hardware

In un programma il tempo avanza un'istruzione alla volta. La CPU finisce la riga 1, poi esegue la riga 2. Se vuoi che due cose accadano insieme, ricorri ai thread, all'asincronia o a una seconda macchina.

Nell'hardware tutto accade nello stesso momento. Un circuito non fa i turni. Il sommatore somma sempre, il multiplexer seleziona sempre, il flip-flop osserva sempre il clock. Non c'è un program counter. Non esiste una "riga corrente".

Verilog deve descrivere questo mondo con del testo. Il modo in cui lo fa è la causa di tutte le confusioni di questo capitolo.

Due livelli di concorrenza

Quando leggi un modulo Verilog, stai guardando due cose diverse nello stesso momento:

  1. La descrizione statica del circuito. Wire, registri, istanze di porte, istanze di sottomoduli, assegnazioni continue. Esistono tutti contemporaneamente e il loro ordine nel file non conta.
  2. I blocchi procedurali, initial e always, che sembrano piccoli programmi eseguiti passo passo dal simulatore. Dentro uno di questi blocchi le istruzioni vengono eseguite secondo un certo ordine. Ma più blocchi always possono essere attivi insieme, ognuno nel suo piccolo filo di tempo simulato.
module example(input wire a, input wire b, output wire y, output wire z);
    assign y = a & b;        // esiste in ogni momento
    assign z = a | b;        // esiste anch'esso in ogni momento, in parallelo

    always @(posedge a) begin
        // un reattore separato "sempre acceso" che si sveglia a ogni
        // fronte di salita di `a`
    end
endmodule

Le due righe assign non sono una sequenza. Descrivono due pezzi di logica combinatoria che lo strumento di sintesi può disporre fianco a fianco. Il blocco always è una terza cosa che accade in parallelo.

Cosa significa "segnale"

Nel software una variabile conserva un valore finché non lo cambi. In Verilog un segnale ha un valore in modo continuo, e in ogni istante dipende da ciò che lo pilota.

Un wire è pilotato dall'esterno (un assign, la porta di uscita di un sottomodulo, una connessione inout). Un reg è pilotato dall'interno di un blocco procedurale. Entrambi hanno sempre un valore. Non esiste il concetto di "non inizializzato" nel senso del C: i segnali sono pilotati a un valore definito, oppure valgono lo speciale valore sconosciuto x o l'alta impedenza z. Degli ultimi due parliamo in X and Z Values.

Il clock cambia tutto

Appena introduci un clock, il tempo inizia a contare. Un flip-flop è un minuscolo pezzo di hardware che cattura il valore in ingresso sul fronte di salita (o di discesa) di un clock e lo mantiene fino al fronte successivo. Ciò che ti permette di costruire contatori, macchine a stati, pipeline, qualsiasi cosa abbia memoria, è il clock.

Nota q <= d invece di q = d. È un'assegnazione non-blocking, il cavallo da tiro della logica sincrona. Dice "al prossimo fronte di clock, fai diventare q uguale al valore attuale di d". Approfondiamo le regole in Blocking vs Non-blocking; per ora ti basta capire che l'assegnazione non sta fingendo di essere un'istruzione software.

RTL: il modello mentale del trasferimento tra registri

La maggior parte del Verilog sintetizzabile è scritta in uno stile chiamato Register Transfer Level, o RTL. L'idea è semplice:

  • Decidi di quale stato ha bisogno il circuito (i registri).
  • Per ogni registro descrivi due cose: cosa lo resetta e quale logica combinatoria ne calcola il valore successivo.
  • Collega le uscite della logica combinatoria agli ingressi dei registri e hai un circuito funzionante.
always @(posedge clk) begin
    if (reset) state <= IDLE;
    else       state <= next_state;
end

always @(*) begin
    case (state)
        IDLE:   next_state = start ? RUNNING : IDLE;
        RUNNING: next_state = done  ? IDLE    : RUNNING;
        default: next_state = IDLE;
    endcase
end

Questa è una macchina a due stati. Il primo blocco always è sincronizzato dal clock: è un flip-flop. Il secondo è puramente combinatorio: è solo un'equazione. Quasi ogni macchina a stati, contatore e pipeline che scriverai segue questa forma.

Le abitudini che ti bloccano

Se arrivi dal software, ecco un breve elenco di abitudini da lasciare da parte:

  • "Le variabili si aggiornano quando le assegno." Non su un fronte di clock: un'assegnazione non-blocking programma l'aggiornamento per la fine del passo temporale.
  • "Le istruzioni si eseguono dall'alto in basso." Fuori dai blocchi procedurali, no. Dentro un blocco sincronizzato, più o meno, ma la differenza tra blocking e non-blocking cambia cosa significhi davvero "in ordine".
  • "Lo alloco quando mi serve." L'hardware non alloca. Ogni registro e ogni porta devono esistere al momento della sintesi. La dimensione di ogni vettore è fissa.
  • "Questo ciclo è veloce, è una sola operazione." Un ciclo for in Verilog sintetizzabile viene srotolato in hardware parallelo. Un ciclo di 64 iterazioni diventa 64 copie del corpo, non un'istruzione della CPU eseguita 64 volte.

Non stai imparando a scrivere un programma. Stai imparando a descrivere un circuito. L'istinto di leggere dall'alto in basso è esattamente quello sbagliato e ci vuole un po' per riabituarsi.

Cosa viene dopo

Le prossime pagine spiegano come installare una toolchain locale (facoltativa: l'editor nel browser va benissimo) e poi come scrivere il tuo primo modulo da zero. Torneremo sul confronto tra hardware e software ogni volta che qualcosa sembrerà strano, perché quasi tutte le parti "strane" hanno la stessa origine: questo non è software.

Domande frequenti

Qual è la differenza tra hardware e software dal punto di vista di Verilog?

Il software è una sequenza di istruzioni eseguite da una CPU, una dopo l'altra. L'hardware, cioè ciò che Verilog descrive, è una rete di porte e wire che trasportano segnali tutti nello stesso momento. Un file Verilog descrive quella rete. Il simulatore imita il comportamento parallelo; uno strumento di sintesi la trasforma in silicio vero.

Il codice Verilog si esegue dall'alto in basso come il C?

No, e trattarlo così è l'errore più comune di chi inizia. Le istruzioni di primo livello (assign, istanze di moduli, blocchi always) 'esistono' tutte contemporaneamente. Solo dentro i blocchi procedurali, initial e always, succede qualcosa di simile a un'esecuzione sequenziale, e anche lì le assegnazioni non-blocking rompono l'illusione.

Cosa significa 'concorrente' in Verilog?

Significa che più istruzioni descrivono parti di un circuito che funzionano tutte nello stesso momento. Due istruzioni assign nello stesso modulo non sono 'riga 1 e poi riga 2': sono due pezzi di hardware che lavorano in parallelo, entrambi reagendo di continuo ai propri ingressi.

Cos'è la progettazione RTL?

RTL sta per Register Transfer Level. È uno stile di scrittura del Verilog in cui descrivi il circuito come registri (flip-flop) più la logica combinatoria che ne calcola i valori successivi. La maggior parte del Verilog sintetizzabile è RTL. Il livello superiore è quello comportamentale; quello inferiore è il livello delle porte logiche.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA