Décrire des circuits combinatoires

L’instruction d’affectation concurrente

L’affectation d’un signal de type wire s’effectue au moyen de l’instruction assign :

assign nom de signal = expression;

Si le signal est de type vecteur, il est possible d’affecter un bit ou un groupe de bits dans un intervalle d’indices :

assign nom de signal[indice]        = expression;
assign nom de signal[indice:indice] = expression;

Si l’expression retourne un vecteur, il est possible de le décomposer en plusieurs signaux de destination :

assign {nom de signal, nom de signal, ...} = expression;

Exemple :

wire a;
wire [2:0] b;
wire [3:0] c;

assign {a, b, c} = 8'b10110101
// a vaut 1'b1
// b vaut 3'b011
// c vaut 4'b0101

En VHDL, l’affectation conditionnelle (when...else) est une instruction à part entière. En Verilog, on obtient le même effet au moyen de l’opérateur conditionnel ?: comme ceci :

assign nom de signal = condition ? expression si vrai : expression si faux;

Expressions

Opérateurs

À quelques exceptions près, Verilog fournit les mêmes opérateurs que le langage C :

Syntaxe Opération
a + b a plus b
a - b a moins b
a * b a multiplié par b
a / b a divisé par b
a % b a modulo b
a ** b a à la puissance b
a < b a strictement inférieur à b
a <= b a inférieur ou égal à b
a > b a strictement supérieur à b
a >= b a supérieur ou égal à b
a == b a égal à b
a != b a différent de b
!a a non nul
a && b a non nul et b non nul
a || b a non nul ou b non nul
~a non logique bit à bit sur a
a & b et logique bit à bit entre a et b
a | b ou logique bit à bit entre a et b
& a et logique entre tous les bits de a
| a ou logique entre tous les bits de a
a << b a décalé de b bits vers la gauche
a <<< b a décalé de b bits vers la gauche (préserve le signe)
a >> b a décalé de b bits vers la droite
a >>> b a décalé de b bits vers la droite (préserve le signe)
a ? b : c si a est non nul, alors b, sinon c

Dans le cas des opérateurs logiques, si deux opérandes n’ont pas la même taille, le plus petit est complété avec des 0 même s’il est signé.

Les opérateurs de comparaison retournent le bit x si l’un des opérandes contient des x ou des z. Il existe également les opérateurs === et !== qui incluent les x et z dans la comparaison.

Concaténation

On écrit entre accolades, séparées par des virgules, les expressions dont on souhaite concaténer les résultats :

{expression, expression, ...}

À l’intérieur des accolades, il est possible de répéter une même expression plusieurs fois en utilisant la syntaxe suivante :

nombre de répétitions{expression}

Exemple :

wire a;
wire [2:0] b;
wire [3:0] c;
wire [7:0] d;
wire [7:0] e;

assign a = 1'b1;
assign b = 3'b011;
assign c = 4'b0101;
assign d = {a, b, c};    // d vaut 8'b10110101
assign e = {2{0}, 2{b}}; // e vaut 8'b00011011

Processus

L’instruction always est similaire à l’instruction process du VHDL. Le plus souvent, on utilisera la syntaxe suivante :

always @ liste de sensibilité
    instruction séquentielle ou bloc

Comme en VHDL, la liste de sensibilité indique les noms des signaux qui, lorsqu’ils changent de valeur, déclenchent l’exécution des instructions du processus. Lorsqu’un processus décrit un circuit combinatoire, elle peut prendre l’une des formes suivantes :

always @ nom de signal
    instruction séquentielle ou bloc

always @ (nom de signal, nom de signal, ...)
    instruction séquentielle ou bloc

always @ *
    instruction séquentielle ou bloc

La troisième variante ci-dessus est disponible à partir de Verilog 2001, elle signifie que la liste de sensibilité doit être construite automatiquement à partir du corps du processus. Le même mécanisme existe en VHDL 2008 avec le mot-clé all.

Le corps du processus peut être constitué d’une seule instruction, ou d’un bloc d’instructions entre les mots-clés begin et end :

begin
    instruction
    ...
end

Les instructions utilisables dans un processus sont :

L’affectation d’un signal dans un processus

On distingue deux variantes de l’affectation :

L’affectation bloquante se comporte comme l’affectation d’une variable en VHDL : elle prend effet immédiatement, ce qui permet de réutiliser le résultat d’un calcul dans les instructions suivantes. Si nous devions écrire le module Comparator avec un processus, on pourrait écrire :

always @(a_i, b_i) begin
    lt_o = a_i < b_i;
    gt_o = a_i > b_i;
    // lt_o et gt_o sont à jour au moment d'exécuter cette instruction :
    eq_o = ~(lt_o | gt_o);
end

L’affectation non bloquante prend effet à la fin du processus (en VHDL, on dit qu’elle prend effet avec un retard fictif δ\delta). Pour obtenir le même comportement que dans l’exemple ci-dessus, il faudrait inclure lt_o et gt_o dans la liste de sensibilité pour forcer un recalcul de eq_o à chaque fois qu’ils sont mis à jour :

always @(a_i, b_i, lt_o, gt_o) begin
    lt_o <= a_i < b_i;
    gt_o <= a_i > b_i;
    // Cette instruction utilise les anciennes valeurs de lt_o et gt_o :
    eq_o <= ~(lt_o | gt_o);
end

Les signaux affectés dans un processus doivent être déclarés avec le mot-clé reg ou integer. Dans le cas d’un port de sortie, vous pouvez utiliser ensemble les deux mots-clés output et reg :

output reg y_o

L’instruction if

L’instruction if a une syntaxe similaire au langage C :

if (condition)
    instruction séquentielle ou bloc
else
    instruction séquentielle ou bloc

La condition est considérée comme vraie si elle retourne une valeur différente de 0, x ou z. Un bloc d’instructions est délimité par les mots-clés begin et end.

La clause else est obligatoire si un processus décrit un circuit combinatoire.

L’instruction case

Cette instruction ressemble à l’instruction switch du langage C, et à l’instruction case du VHDL.

case (expression)
    valeur:              instruction séquentielle ou bloc
    valeur, valeur, ...: instruction séquentielle ou bloc
    default:             instruction séquentielle ou bloc
endcase

Il existe deux variantes casez et casex :

Cela permet de simplifier l’écriture de certaines fonctions logiques, comme dans l’exemple ci-dessous, avec l’instruction case :

always @(cmd, a, b)
    case (cmd)
        3'b000, 3'b001: y = a + b;
        3'b010:         y = a - b;
        3'b011:         y = b - a;
        3'b100, 3'b110: y = a & b;
        3'b101, 3'b111: y = a | b;
    endcase

Avec l’instruction casez :

always @(cmd, a, b)
    casez (cmd)
        3'b00z: y = a + b;
        3'b010: y = a - b;
        3'b011: y = b - a;
        3'b1z0: y = a & b;
        3'b1z1: y = a | b;
    endcase

Lorsqu’elles sont utilisées comme valeurs indifférentes, les valeurs x et z peuvent s’écrire sous la forme d’un point d’interrogation :

always @(cmd, a, b)
    casez (cmd)
        3'b00?: y = a + b;
        3'b010: y = a - b;
        3'b011: y = b - a;
        3'b1?0: y = a & b;
        3'b1?1: y = a | b;
    endcase

La boucle for

Sa syntaxe est similaire à la boucle for du langage C :

for (initialisation; condition; opération)
    instruction séquentielle ou bloc

L’exemple ci-dessous utilise une boucle for pour compter le nombre de bits à 1 dans un vecteur représentant l’état de seize interrupteurs :

module NumberOfSwitchesUp (
    input      [15:0] switches_i,
    output reg [4:0]  count_o
);

always @ switches_i begin
    integer n;
    count_o = 0;
    for (n = 0; n < 16; n = n + 1)
        if (switches_i[n])
            count_o = count_o + 1;
end

endmodule

Nous avons déclaré une variable locale n de type integer comme compteur de boucle. Grâce aux affectations bloquantes, les affectations count_o = count_o + 1 auront l’effet souhaité. Il n’est pas nécessaire de déclarer d’autre variable.