Menu

Assignation continue en Verilog : l'instruction assign

Comment assign fonctionne - la relation toujours vraie qu'il décrit, ce qu'il peut et ne peut pas piloter, et les motifs où il brille comparé au code procédural.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

assign décrit une vérité permanente

Une déclaration wire crée un signal. Un assign décrit ce qui le pilote. La relation est continue : peu importe ce qui est à droite, la partie gauche est égale à ça, à chaque instant du temps simulé :

wire y;
assign y = a & b;

Deux choses impliquées par ces deux lignes :

  • y existe comme wire dans le circuit.
  • y est à tout instant le AND bitwise de a et b. Change une entrée et y suit.

Pas d'horloge ni d'événement qui déclenche la mise à jour. Le simulateur voit a ou b changer, marque y comme sale et réévalue l'expression. En matériel ça correspond à quelques portes AND : combinatoire, sans état, instantané.

La forme implicite

Tu peux combiner la déclaration et l'assignation sur une seule ligne :

wire y = a & b;

C'est la même chose que les deux lignes ci-dessus. Utile pour les wires inline dont personne en dehors de cette portée ne se soucie. Beaucoup de guides de style préfèrent en fait la forme implicite parce qu'elle met la déclaration juste à côté de l'équation.

Ce que assign peut piloter

La cible de assign doit être un type net - en Verilog classique, c'est wire (ou un des cousins plus rares comme tri, wand, wor). Ce ne peut pas être un reg. Si tu déclares accidentellement ta cible comme reg :

reg y;
assign y = a & b;   // ERREUR : impossible de piloter un reg avec assign

Le compilateur te le dira. Soit change la cible en wire, soit déplace la logique dans un bloc always @(*)reg est la cible légale.

Le côté droit peut être n'importe quoi qui s'évalue à une valeur : littéraux, signaux, paramètres, expressions d'opérateurs, appels de fonctions. Il peut mélanger plusieurs largeurs d'entrée ; les règles d'élargissement standard s'appliquent.

Quand utiliser assign vs always

Les deux peuvent produire de la logique combinatoire. Le choix porte surtout sur la façon dont le code se lit :

  • assign est mieux quand la relation est une expression unique. Additionneurs, muxes simples construits avec ?:, masques, bits de parité, tout ce que tu peux écrire en une ligne.
  • always @(*) est mieux quand tu as besoin d'instructions procédurales. Instructions case multi-voies, if/else if imbriqués, tout ce qui bénéficie de regs intermédiaires nommés. On couvre ça dans Always Block.

Voici le même mux 4 vers 1 écrit des deux façons :

Les deux modules se synthétisent essentiellement au même multiplexeur. La version assign est une ligne de code ; la version always en fait six. Pour quatre cas c'est serré ; pour seize cas le bloc case est nettement plus lisible.

Motifs courants

Logique combinatoire simple

assign sum   = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);

Une expression, un wire. Le pain et le beurre de assign.

Un mux 2 vers 1

assign out = sel ? a : b;

Expression conditionnelle unique - le synthétiseur la transforme en un mux 2 vers 1. L'écriture la plus propre possible de « sélectionne entre a et b ».

Empaquetage de bits

assign status = {error, overflow, ready, busy, 4'b0};

La concaténation à droite d'un assign est ainsi qu'on empaquette des flags dans un octet de statut. Le résultat est calculé et piloté en permanence.

Sortie tri-state

assign data_pin = output_enable ? data_out : 1'bz;

Quand output_enable est haut, pilote la broche. Quand bas, relâche en haute impédance. C'est le motif standard aux broches de puce où plusieurs pilotes peuvent partager un wire.

Concurrence : plusieurs assigns ne sont pas séquentiels

Un rappel qui ne cesse pas d'être pertinent : plusieurs instructions assign dans le même module fonctionnent toutes en parallèle. Ce n'est pas une séquence :

assign y = a & b;     // existe à tout instant
assign z = a | b;     // existe aussi à tout instant, indépendamment

L'ordre dans le fichier est sans importance. Les deux équations sont simultanément vraies. Le synthétiseur peut placer la porte AND à gauche de la porte OR ou à droite ; ça n'a pas d'importance, les deux portes tirent en permanence.

Si tu veux un comportement d'apparence séquentielle, tu tends la main vers un bloc always (et probablement une horloge). C'est un autre chapitre.

Pilotes multiples : le motif bus

Un wire peut avoir plus d'un assign qui le cible, mais tu ne veux presque jamais ça sauf pour les bus tri-state. Deux pilotes se battant pour un wire produit un comportement indéfini :

assign y = a;
assign y = b;   // MAUVAIS - deux pilotes, le simulateur en choisit un ou le x-e

Le motif légitime : chaque pilote relâche à z quand inactif, et au plus un est actif à un instant donné.

assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;

Ça marche parce qu'à un instant donné, au plus une des deux ternaires produit une valeur non z. La valeur réelle du wire est celle du pilote qui ne relâche pas.

Sur la logique interne - partout qui n'est pas une broche de puce ou un bus partagé on-chip - un pilote par wire. Les bugs multi-pilotes sont vicieux à débugger.

Ce que assign ne peut pas faire

Quelques choses pour lesquelles assign est le mauvais outil :

  • Stockage. assign décrit des relations combinatoires ; il ne peut pas introduire un flip-flop. Si tu as besoin qu'une valeur soit mémorisée entre les cycles d'horloge, c'est un bloc always @(posedge clk).
  • Logique procédurale multi-étapes. Tu ne peux pas écrire if/else ou case à l'intérieur d'un assign. Le plus proche est une chaîne de ?:, qui devient moche au-delà de trois branches.
  • Piloter des registres depuis l'intérieur d'un bloc procédural. Les cibles reg ont besoin d'une assignation procédurale, pas assign.

Connaître les limites est ainsi que tu décides quand passer à always.

La suite

Tu as maintenant vu tout le côté structurel de Verilog : déclarer des modules, les instancier et câbler de la logique combinatoire avec assign. Le prochain chapitre entre dans les blocs procéduraux - les constructions initial et always où le temps et l'ordre commencent à compter.

Questions fréquentes

Qu'est-ce qu'une assignation continue en Verilog ?

assign target = expression; déclare une relation permanente et continue : target est toujours égal à expression. Chaque fois qu'un signal dans l'expression change, le simulateur réévalue la partie droite et met à jour target. Pas d'horloge, pas d'événement - la relation est vraie à chaque instant.

Que peut-on cibler avec un assign en Verilog ?

assign peut piloter un wire, mais jamais un reg. La cible doit être un type net. Si tu as besoin d'assigner à quelque chose à l'intérieur d'un bloc always, déclare-le comme reg. Le compilateur rejettera assign x = ... si x est reg, et rejettera x = ... à l'intérieur d'un always si x est wire.

Quand utiliser assign vs un bloc always ?

Utilise assign pour la logique combinatoire simple - une expression en entrée, un signal en sortie, pas besoin de if/else. Utilise always @(*) quand la logique a besoin d'instructions procédurales (un case, une chaîne if/else if, une boucle for). Les deux produisent du matériel combinatoire ; le choix porte sur la lisibilité.

Peut-on avoir plusieurs assigns vers le même wire en Verilog ?

Uniquement si tu modélises un bus tri-state où chaque pilote relâche le wire à z quand inactif. Deux assigns essayant de piloter le wire vers des valeurs définies en même temps produisent de la contention - le simulateur peut en choisir un, peut X-er le signal, ça dépend de l'outil. Pour la logique combinatoire ordinaire, un pilote par wire.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER