Modélisation comportementale et synchronisation dans Verilog

Les modèles comportementaux de Verilog contiennent des instructions procédurales qui contrôlent la simulation et manipulent les variables des types de données. Ces toutes les déclarations sont contenues dans les procédures. Chaque procédure est associée à un flux d'activités.

Lors de la simulation du modèle comportemental, tous les flux définis par les instructions «toujours» et «initial» démarrent ensemble au temps de simulation «zéro». Les instructions initiales sont exécutées une fois, et les instructions always sont exécutées de manière répétitive. Dans ce modèle, les variables de registre a et b sont initialisées respectivement au binaire 1 et 0 au temps de simulation «zéro». L'instruction initiale est alors terminée et n'est plus exécutée pendant cette simulation. Cette instruction initiale contient un bloc de début-fin (également appelé bloc séquentiel) d'instructions. Dans ce bloc de type début-fin, a est initialisé en premier suivi de b.

Exemple de modélisation comportementale

module behave; 
reg [1:0]a,b; 

initial 
begin 
   a = ’b1; 
   b = ’b0; 
end 

always 
begin 
   #50 a = ~a; 
end 

always 
begin 
   #100 b = ~b; 
end 
End module

Affectations procédurales

Les affectations procédurales permettent de mettre à jour les variables reg, integer, time et memory. Il existe une différence significative entre l'affectation procédurale et l'affectation continue, comme décrit ci-dessous -

Les affectations continues pilotent les variables réseau et sont évaluées et mises à jour chaque fois qu'un opérande d'entrée change de valeur.

Les affectations procédurales mettent à jour la valeur des variables de registre sous le contrôle des constructions de flux procédurales qui les entourent.

Le côté droit d'une affectation procédurale peut être n'importe quelle expression qui prend la valeur d'une valeur. Cependant, les sélections partielles sur le côté droit doivent avoir des indices constants. Le côté gauche indique la variable qui reçoit l'affectation du côté droit. Le côté gauche d'une affectation procédurale peut prendre l'une des formes suivantes:

  • register, integer, real ou time variable - Une affectation à la référence de nom de l'un de ces types de données.

  • sélection de bits d'un registre, d'un entier, d'une variable réelle ou temporelle - Une affectation à un seul bit qui laisse les autres bits intacts.

  • sélection partielle d'un registre, d'un entier, d'une variable réelle ou temporelle - Une sélection partielle de deux bits contigus ou plus qui laisse le reste des bits inchangé. Pour le formulaire de sélection de pièce, seules les expressions constantes sont autorisées.

  • élément de mémoire - Un seul mot d'une mémoire. Notez que les sélections de bits et les sélections partielles sont illégales sur les références d'éléments de mémoire.

  • concaténation de l'un des éléments ci-dessus - Une concaténation de l'une des quatre formes précédentes peut être spécifiée, ce qui partitionne efficacement le résultat de l'expression de droite et affecte les parties de partition, dans l'ordre, aux différentes parties de la concaténation.

Retard dans l'attribution (pas pour la synthèse)

Dans une affectation retardée Δt, les unités de temps s'écoulent avant que l'instruction ne soit exécutée et que l'affectation de gauche soit effectuée. Avec un délai intra-assignation, le côté droit est évalué immédiatement mais il y a un retard de Δt avant que le résultat soit placé dans l'assignation de gauche. Si une autre procédure modifie un signal du côté droit pendant Δt, elle n'affecte pas la sortie. Les retards ne sont pas pris en charge par les outils de synthèse.

Syntaxe

  • Procedural Assignmentvariable = expression

  • Delayed assignment# Δt variable = expression;

  • Intra-assignment delayvariable = # Δt expression;

Exemple

reg [6:0] sum; reg h, ziltch; 
sum[7] = b[7] ^ c[7]; // execute now. 
ziltch = #15 ckz&h; /* ckz&a evaluated now; ziltch changed 
after 15 time units. */ 

#10 hat = b&c; /* 10 units after ziltch changes, b&c is
evaluated and hat changes. */

Blocage des attributions

Une instruction d'affectation procédurale bloquante doit être exécutée avant l'exécution des instructions qui la suivent dans un bloc séquentiel. Une instruction d'affectation procédurale bloquante n'empêche pas l'exécution des instructions qui la suivent dans un bloc parallèle.

Syntaxe

La syntaxe pour une affectation procédurale bloquante est la suivante -

<lvalue> = <timing_control> <expression>

Où, lvalue est un type de données valide pour une instruction d'affectation de procédure, = est l'opérateur d'affectation et le contrôle de synchronisation est le délai intra-affectation facultatif. Le délai de contrôle de synchronisation peut être soit une commande de retard (par exemple, # 6) soit une commande d'événement (par exemple, @ (posedge clk)). L'expression est la valeur du côté droit que le simulateur attribue au côté gauche. L'opérateur d'affectation = utilisé pour bloquer les affectations procédurales est également utilisé par les affectations procédurales continues et les affectations continues.

Exemple

rega = 0; 
rega[3] = 1;            // a bit-select 
rega[3:5] = 7;          // a part-select 
mema[address] = 8’hff;  // assignment to a memory element 
{carry, acc} = rega + regb;  // a concatenation

Affectations non bloquantes (RTL)

L'affectation procédurale non bloquante vous permet de planifier des affectations sans bloquer le flux procédural. Vous pouvez utiliser l'instruction procédurale non bloquante chaque fois que vous souhaitez effectuer plusieurs affectations de registre dans le même pas de temps sans tenir compte de l'ordre ou de la dépendance les uns par rapport aux autres.

Syntaxe

La syntaxe d'une affectation procédurale non bloquante est la suivante -

<lvalue> <= <timing_control> <expression>

Où lvalue est un type de données valide pour une instruction d'affectation de procédure, <= est l'opérateur d'affectation non bloquant et le contrôle de synchronisation est le contrôle de synchronisation intra-affectation facultatif. Le délai de commande de synchronisation peut être soit une commande de retard, soit une commande d'événement (par exemple, @ (posedge clk)). L'expression est la valeur du côté droit que le simulateur attribue au côté gauche. L'opérateur d'affectation non bloquant est le même que celui que le simulateur utilise pour l'opérateur relationnel inférieur ou égal. Le simulateur interprète l'opérateur <= comme un opérateur relationnel lorsque vous l'utilisez dans une expression et interprète l'opérateur <= comme un opérateur d'affectation lorsque vous l'utilisez dans une construction d'affectation procédurale non bloquante.

Comment le simulateur évalue les affectations procédurales non bloquantes Lorsque le simulateur rencontre une affectation procédurale non bloquante, le simulateur évalue et exécute l'affectation procédurale non bloquante en deux étapes comme suit -

  • Le simulateur évalue le côté droit et planifie l'attribution de la nouvelle valeur pour qu'elle ait lieu à un moment spécifié par une commande de synchronisation procédurale. Le simulateur évalue le côté droit et planifie l'attribution de la nouvelle valeur pour qu'elle ait lieu à un moment spécifié par une commande de synchronisation procédurale.

  • A la fin du pas de temps, dans lequel le délai donné a expiré ou l'événement approprié s'est produit, le simulateur exécute l'affectation en attribuant la valeur au côté gauche.

Exemple

module evaluates2(out); 
output out; 
reg a, b, c; 
initial 

begin 
   a = 0; 
   b = 1; 
   c = 0; 
end 
always c = #5 ~c; 
always @(posedge c) 

begin 
   a <= b; 
   b <= a; 
end 
endmodule

Conditions

L'instruction conditionnelle (ou l'instruction if-else) est utilisée pour décider si une instruction est exécutée ou non.

Formellement, la syntaxe est la suivante -

<statement> 
::= if ( <expression> ) <statement_or_null> 
||= if ( <expression> ) <statement_or_null> 
   else <statement_or_null> 
<statement_or_null> 

::= <statement> 
||= ;

<expression> est évaluée; s'il est vrai (c'est-à-dire qu'il a une valeur connue différente de zéro), la première instruction s'exécute. S'il est faux (a une valeur nulle ou la valeur est x ou z), la première instruction ne s'exécute pas. S'il existe une instruction else et que <expression> est fausse, l'instruction else s'exécute. Étant donné que la valeur numérique de l'expression if est testée pour être égale à zéro, certains raccourcis sont possibles.

Par exemple, les deux instructions suivantes expriment la même logique -

if (expression) 
if (expression != 0)

Comme la partie else d'un if-else est facultative, il peut y avoir confusion lorsqu'un else est omis d'une séquence if imbriquée. Ceci est résolu en associant toujours le else au précédent le plus proche s'il manque un else.

Exemple

if (index > 0) 
if (rega > regb) 
   result = rega; 
   else // else applies to preceding if 
   result = regb; 

If that association is not what you want, use a begin-end block statement 
to force the proper association 

if (index > 0) 
begin 

if (rega > regb) 
result = rega; 
end 
   else 
   result = regb;

Construction de: if- else- if

La construction suivante se produit si souvent qu'elle mérite une brève discussion séparée.

Example

if (<expression>) 
   <statement> 
   else if (<expression>) 
   <statement> 
   else if (<expression>) 
   <statement> 
   else  
   <statement>

Cette séquence de if (connue sous le nom de construction if-else-if) est la manière la plus générale d'écrire une décision à plusieurs voies. Les expressions sont évaluées dans l'ordre; si une expression est vraie, l'instruction qui lui est associée est exécutée, ce qui met fin à toute la chaîne. Chaque instruction est soit une seule instruction, soit un bloc d'instructions.

La dernière partie else de la construction if-else-if gère le cas «aucun des éléments ci-dessus» ou par défaut où aucune des autres conditions n'est satisfaite. Parfois, il n'y a pas d'action explicite pour la valeur par défaut; dans ce cas, la fin d'autre peut être omise ou elle peut être utilisée pour la vérification d'erreur afin de détecter une condition impossible.

Déclaration de cas

L'instruction case est une instruction de décision multidirectionnelle spéciale qui teste si une expression correspond à l'une des nombreuses autres expressions et se branche en conséquence. L'énoncé de cas est utile pour décrire, par exemple, le décodage d'une instruction de microprocesseur. L'instruction case a la syntaxe suivante -

Example

<statement> 
::= case ( <expression> ) <case_item>+ endcase 
||= casez ( <expression> ) <case_item>+ endcase 
||= casex ( <expression> ) <case_item>+ endcase 
<case_item> 
::= <expression> <,<expression>>* : <statement_or_null> 
||= default : <statement_or_null> 
||= default <statement_or_null>

Les expressions de cas sont évaluées et comparées dans l'ordre exact dans lequel elles sont données. Lors de la recherche linéaire, si l'une des expressions d'élément de cas correspond à l'expression entre parenthèses, l'instruction associée à cet élément de cas est exécutée. Si toutes les comparaisons échouent et que l'élément par défaut est donné, l'instruction d'élément par défaut est exécutée. Si l'instruction par défaut n'est pas donnée et que toutes les comparaisons échouent, aucune des instructions d'élément de cas n'est exécutée.

Outre la syntaxe, l'instruction case diffère de la construction multi-voies if-else-if de deux manières importantes -

  • Les expressions conditionnelles de la construction if-else-if sont plus générales que la comparaison d'une expression avec plusieurs autres, comme dans l'instruction case.

  • L'instruction case fournit un résultat définitif lorsqu'il existe des valeurs x et z dans une expression.

Instructions en boucle

Il existe quatre types d'instructions en boucle. Ils fournissent un moyen de contrôler l'exécution d'une instruction zéro, une ou plusieurs fois.

  • forever exécute en permanence une instruction.

  • repeat exécute une instruction un nombre fixe de fois.

  • while exécute une instruction jusqu'à ce qu'une expression devienne fausse. Si l'expression commence par false, l'instruction n'est pas exécutée du tout.

  • pour contrôler l'exécution de ses instructions associées par un processus en trois étapes, comme suit -

    • Exécute une affectation normalement utilisée pour initialiser une variable qui contrôle le nombre de boucles exécutées

    • Évalue une expression: si le résultat est zéro, la boucle for se termine, et si elle n'est pas nulle, la boucle for exécute sa ou ses instructions associées, puis exécute l'étape 3

    • Exécute une affectation normalement utilisée pour modifier la valeur de la variable de contrôle de boucle, puis répète l'étape 2

Voici les règles de syntaxe pour les instructions en boucle -

Example

<statement> 
::= forever <statement> 
||=forever 
begin 
   <statement>+ 
end  

<Statement> 
::= repeat ( <expression> ) <statement> 
||=repeat ( <expression> ) 
begin
   <statement>+ 
end  

<statement> 
::= while ( <expression> ) <statement> 
||=while ( <expression> ) 
begin 
   <statement>+ 
end  
<statement> 
::= for ( <assignment> ; <expression> ; <assignment> ) 
<statement> 
||=for ( <assignment> ; <expression> ; <assignment> ) 
begin 
   <statement>+ 
end

Commandes de retard

Contrôle du retard

L'exécution d'une instruction procédurale peut être contrôlée par délai en utilisant la syntaxe suivante -

<statement> 
::= <delay_control> <statement_or_null> 
<delay_control> 
::= # <NUMBER> 
||= # <identifier> 
||= # ( <mintypmax_expression> )

L'exemple suivant retarde l'exécution de l'affectation de 10 unités de temps -

# 10 rega = regb;

Les trois exemples suivants fournissent une expression après le signe dièse (#). L'exécution des délais d'affectation de la durée de simulation spécifiée par la valeur de l'expression.

Contrôle des événements

L'exécution d'une instruction procédurale peut être synchronisée avec un changement de valeur sur un réseau ou un registre, ou avec l'occurrence d'un événement déclaré, en utilisant la syntaxe de contrôle d'événement suivante -

Example

<statement> 
::= <event_control> <statement_or_null> 

<event_control> 
::= @ <identifier> 
||= @ ( <event_expression> ) 

<event_expression> 
::= <expression> 
||= posedge <SCALAR_EVENT_EXPRESSION> 
||= negedge <SCALAR_EVENT_EXPRESSION> 
||= <event_expression> <or <event_expression>>

* <SCALAR_EVENT_EXPRESSION> est une expression qui se résout en une valeur d'un bit.

Les changements de valeur sur les réseaux et les registres peuvent être utilisés comme événements pour déclencher l'exécution d'une instruction. C'est ce qu'on appelle la détection d'un événement implicite. La syntaxe Verilog vous permet également de détecter le changement en fonction de la direction du changement, c'est-à-dire vers la valeur 1 (posedge) ou vers la valeur 0 (negedge). Le comportement de posedge et negedge pour les valeurs d'expression inconnues est le suivant:

  • un negedge est détecté lors du passage de 1 à inconnu et de inconnu à 0
  • un posedge est détecté lors du passage de 0 à inconnu et de inconnu à 1

Procédures: blocs toujours et initiaux

Toutes les procédures de Verilog sont spécifiées dans l'un des quatre blocs suivants. 1) Blocs initiaux 2) Bloque toujours 3) Tâche 4) Fonction

Les instructions initial et always sont activées au début de la simulation. Les blocs initiaux ne s'exécutent qu'une seule fois et son activité meurt lorsque l'instruction est terminée. En revanche, le bloc always s'exécute à plusieurs reprises. Son activité ne meurt que lorsque la simulation est terminée. Il n'y a pas de limite au nombre de blocs initiaux et toujours qui peuvent être définis dans un module. Les tâches et les fonctions sont des procédures activées à partir d'un ou plusieurs endroits dans d'autres procédures.

Blocs initiaux

La syntaxe de l'instruction initiale est la suivante -

<initial_statement> 
::= initial <statement>

L'exemple suivant illustre l'utilisation de l'instruction initiale pour l'initialisation des variables au début de la simulation.

Initial 
Begin 
   Areg = 0; // initialize a register 
   For (index = 0; index < size; index = index + 1) 
   Memory [index] = 0; //initialize a memory 
   Word 
End

Une autre utilisation typique des blocs initiaux est la spécification de descriptions de formes d'onde qui s'exécutent une fois pour fournir un stimulus à la partie principale du circuit simulé.

Initial 
Begin 
   Inputs = ’b000000; 
   // initialize at time zero 
   #10 inputs = ’b011001; // first pattern 
   #10 inputs = ’b011011; // second pattern 
   #10 inputs = ’b011000; // third pattern 
   #10 inputs = ’b001000; // last pattern 
End

Bloque toujours

L'instruction «always» se répète continuellement tout au long de la simulation. La syntaxe de l'instruction always est donnée ci-dessous

<always_statement> 
::= always <statement>

L'instruction 'always', en raison de sa nature en boucle, n'est utile que lorsqu'elle est utilisée en conjonction avec une forme de contrôle de synchronisation. Si une instruction 'always' ne fournit aucun moyen d'avancer le temps, l'instruction 'always' crée une condition de blocage de simulation. Le code suivant, par exemple, crée une boucle infinie sans délai -

Always areg = ~areg;

Fournir un contrôle de synchronisation au code ci-dessus crée une description potentiellement utile - comme dans l'exemple suivant -

Always #half_period areg = ~areg;