Recherche…


Remarques

VHDL est un acronyme composé de VHSIC (circuit intégré à très haut débit) HDL (Hardware Description Language). En tant que langage de description du matériel, il est principalement utilisé pour décrire ou modéliser des circuits. Le langage VHDL est un langage idéal pour décrire des circuits car il offre des constructions de langage qui décrivent facilement les comportements simultanés et séquentiels, ainsi qu'un modèle d'exécution qui supprime les ambiguïtés introduites lors de la modélisation d'un comportement concurrent.

Le VHDL est généralement interprété dans deux contextes différents: pour la simulation et pour la synthèse. Lorsqu'il est interprété pour la synthèse, le code est converti (synthétisé) en éléments matériels équivalents qui sont modélisés. Seul un sous-ensemble du VHDL est généralement disponible pour être utilisé pendant la synthèse, et les constructions de langage prises en charge ne sont pas normalisées. c'est une fonction du moteur de synthèse utilisé et du matériel cible. Lorsque VHDL est interprété pour la simulation, toutes les constructions de langage sont disponibles pour modéliser le comportement du matériel.

Versions

Version Date de sortie
IEEE 1076-1987 1988-03-31
IEEE 1076-1993 1994-06-06
IEEE 1076-2000 2000-01-30
IEEE 1076-2002 2002-05-17
IEEE 1076c-2007 2007-09-05
IEEE 1076-2008 2009-01-26

Installation ou configuration

Un programme VHDL peut être simulé ou synthétisé. La simulation est ce qui ressemble le plus à l'exécution dans d'autres langages de programmation. Synthesis traduit un programme VHDL en un réseau de portes logiques. De nombreux outils de simulation et de synthèse VHDL font partie des suites EDA (Electronic Design Automation) commerciales. Ils gèrent également fréquemment d'autres langages de description du matériel (HDL), tels que Verilog, SystemVerilog ou SystemC. Certaines applications libres et open source existent.

Simulation VHDL

GHDL est probablement le simulateur VHDL libre et open source le plus mature. Il existe trois llvm différentes en fonction du backend utilisé: gcc , llvm ou mcode . Les exemples suivants montrent comment utiliser GHDL (version mcode ) et Modelsim, le simulateur HDL commercial de Mentor Graphics, sous un système d'exploitation GNU / Linux. Les choses seraient très similaires avec d'autres outils et d'autres systèmes d'exploitation.

Bonjour le monde

Créez un fichier hello_world.vhd contenant:

-- File hello_world.vhd
entity hello_world is
end entity hello_world;

architecture arc of hello_world is
begin
  assert false report "Hello world!" severity note;
end architecture arc;

Une unité de compilation VHDL est un programme VHDL complet qui peut être compilé seul. Les entités sont des unités de compilation VHDL utilisées pour décrire l'interface externe d'un circuit numérique, c'est-à-dire ses ports d'entrée et de sortie. Dans notre exemple, l' entity est nommée hello_world et est vide. Le circuit que nous modélisons est une boîte noire, il n’a pas d’entrée ni de sortie. Les architectures sont un autre type d'unité de compilation. Ils sont toujours associés à une entity et servent à décrire le comportement du circuit numérique. Une entité peut avoir une ou plusieurs architectures pour décrire le comportement de l'entité. Dans notre exemple, l' entité est associée à une seule architecture nommée arc qui ne contient qu'une seule déclaration VHDL:

  assert false report "Hello world!" severity note;

La déclaration sera exécutée au début de la simulation et imprimera le Hello world! message sur la sortie standard. La simulation prendra alors fin car il n'y a plus rien à faire. Le fichier source VHDL que nous avons écrit contient deux unités de compilation. Nous aurions pu les séparer dans deux fichiers différents, mais nous n’aurions pas pu les séparer dans des fichiers différents: une unité de compilation doit être entièrement contenue dans un fichier source. Notez que cette architecture ne peut pas être synthétisée car elle ne décrit pas une fonction pouvant être directement traduite en portes logiques.

Analysez et exécutez le programme avec GHDL:

$ mkdir gh_work
$ ghdl -a --workdir=gh_work hello_world.vhd
$ ghdl -r --workdir=gh_work hello_world
hello_world.vhd:6:8:@0ms:(assertion note): Hello world!

Le répertoire gh_work est l'endroit où GHDL stocke les fichiers qu'il génère. C'est ce que dit l'option --workdir=gh_work . La phase d'analyse vérifie l'exactitude de la syntaxe et produit un fichier texte décrivant les unités de compilation trouvées dans le fichier source. La phase d'exécution compile, relie et exécute le programme. Notez que, dans la version mcode de GHDL, aucun fichier binaire n'est généré. Le programme est recompilé chaque fois que nous le simulons. Les versions gcc ou llvm se comportent différemment. Notez également que ghdl -r ne prend pas le nom d'un fichier source VHDL, comme ghdl -a does, mais le nom d'une unité de compilation. Dans notre cas, nous lui passons le nom de l' entity . Comme il n'y a qu'une seule architecture associée, il n'est pas nécessaire de spécifier celle à simuler.

Avec Modelsim:

$ vlib ms_work
$ vmap work ms_work
$ vcom hello_world.vhd
$ vsim -c hello_world -do 'run -all; quit'
...
# ** Note: Hello world!
#    Time: 0 ns  Iteration: 0  Instance: /hello_world
...

vlib , vmap , vcom et vsim sont quatre commandes fournies par Modelsim. vlib crée un répertoire ( ms_work ) où les fichiers générés seront stockés. vmap associe un répertoire créé par vlib à un nom logique ( work ). vcom compile un fichier source VHDL et, par défaut, stocke le résultat dans le répertoire associé au nom logique de work . Enfin, vsim simule le programme et produit le même type de sortie que GHDL. Notez à nouveau que ce vsim demande vsim n'est pas un fichier source mais le nom d'une unité de compilation déjà compilée. L'option -c indique au simulateur de s'exécuter en mode ligne de commande au lieu du mode interface utilisateur graphique (GUI) par défaut. L'option -do est utilisée pour passer un script TCL à exécuter après le chargement du design. TCL est un langage de script très fréquemment utilisé dans les outils EDA. La valeur de l'option -do peut être le nom d'un fichier ou, comme dans notre exemple, une chaîne de commandes TCL. run -all; quit ordonne au simulateur d'exécuter la simulation jusqu'à ce qu'il se termine naturellement - ou pour toujours s'il dure éternellement - puis de quitter.

Compteur synchrone

-- File counter.vhd
-- The entity is the interface part. It has a name and a set of input / output
-- ports. Ports have a name, a direction and a type. The bit type has only two
-- values: '0' and '1'. It is one of the standard types.
entity counter is
  port(
    clock: in  bit;    -- We are using the rising edge of CLOCK
    reset: in  bit;    -- Synchronous and active HIGH
    data:  out natural -- The current value of the counter
  );
end entity counter;

-- The architecture describes the internals. It is always associated
-- to an entity.
architecture sync of counter is
  -- The internal signals we use to count. Natural is another standard
  -- type. VHDL is not case sensitive.
  signal current_value: natural;
  signal NEXT_VALUE:    natural;
begin
  -- A process is a concurrent statement. It is an infinite loop.
  process
  begin
    -- The wait statement is a synchronization instruction. We wait
    -- until clock changes and its new value is '1' (a rising edge).
    wait until clock = '1';
    -- Our reset is synchronous: we consider it only at rising edges
    -- of our clock.
    if reset = '1' then
      -- <= is the signal assignment operator.
      current_value <= 0;
    else
      current_value <= next_value;
    end if;
  end process;

  -- Another process. The sensitivity list is another way to express
  -- synchronization constraints. It (approximately) means: wait until
  -- one of the signals in the list changes and then execute the process
  -- body. Sensitivity list and wait statements cannot be used together 
  -- in the same process.
  process(current_value)
  begin
    next_value <= current_value + 1;
  end process;

  -- A concurrent signal assignment, which is just a shorthand for the
  -- (trivial) equivalent process.
  data <= current_value;
end architecture sync;

Bonjour le monde

Il existe de nombreuses manières d’imprimer le classique "Hello world!" message dans VHDL. Le plus simple est probablement quelque chose comme:

-- File hello_world.vhd
entity hello_world is
end entity hello_world;

architecture arc of hello_world is
begin
  assert false report "Hello world!" severity note;
end architecture arc;

Un environnement de simulation pour le compteur synchrone

Environnements de simulation

Un environnement de simulation pour une conception VHDL (Design Under Test ou DUT) est une autre conception VHDL qui, au minimum:

  • Déclare des signaux correspondant aux ports d'entrée et de sortie du DUT.
  • Instancie le DUT et connecte ses ports aux signaux déclarés.
  • Instancie les processus qui pilotent les signaux connectés aux ports d'entrée du DUT.

En option, un environnement de simulation peut instancier d'autres conceptions que le DUT, comme par exemple les générateurs de trafic sur les interfaces, les moniteurs pour vérifier les protocoles de communication, les vérificateurs automatiques des sorties du DUT ...

L'environnement de simulation est analysé, élaboré et exécuté. La plupart des simulateurs offrent la possibilité de sélectionner un ensemble de signaux à observer, de tracer leurs formes d'onde graphiques, de placer des points d'arrêt dans le code source, de saisir le code source ...

Idéalement, un environnement de simulation devrait être utilisable en tant que test de non-régression robuste, c'est-à-dire qu'il devrait automatiquement détecter les violations des spécifications DUT, signaler les messages d'erreur utiles et garantir une couverture raisonnable des fonctionnalités DUT. Lorsque de tels environnements de simulation sont disponibles, ils peuvent être réexécutés à chaque changement du dispositif sous test pour vérifier qu’il est toujours fonctionnellement correct, sans qu’il soit nécessaire de procéder à des inspections visuelles et fastidieuses des traces de simulation.

Dans la pratique, la conception d'environnements de simulation idéaux, voire simples, est un défi. Il est souvent aussi difficile, voire plus, que de concevoir le DUT lui-même.

Dans cet exemple, nous présentons un environnement de simulation pour l'exemple de compteur synchrone . Nous montrons comment l'exécuter en utilisant GHDL et Modelsim et comment observer des formes d'onde graphiques en utilisant GTKWave avec GHDL et le visualiseur de forme d'onde intégré avec Modelsim. Nous discutons ensuite d’un aspect intéressant des simulations: comment les arrêter?

Un premier environnement de simulation pour le compteur synchrone

Le compteur synchrone possède deux ports d'entrée et un port de sortie. Un environnement de simulation très simple pourrait être:

-- File counter_sim.vhd
-- Entities of simulation environments are frequently black boxes without
-- ports.
entity counter_sim is
end entity counter_sim;

architecture sim of counter_sim is

  -- One signal per port of the DUT. Signals can have the same name as
  -- the corresponding port but they do not need to.
  signal clk:  bit;
  signal rst:  bit;
  signal data: natural;

begin

  -- Instantiation of the DUT
  u0: entity work.counter(sync)
  port map(
    clock => clk,
    reset => rst,
    data  => data
  );

  -- A clock generating process with a 2ns clock period. The process
  -- being an infinite loop, the clock will never stop toggling.
  process
  begin
    clk <= '0';
    wait for 1 ns;
    clk <= '1';
    wait for 1 ns;
  end process;

  -- The process that handles the reset: active from beginning of
  -- simulation until the 5th rising edge of the clock.
  process
  begin
    rst  <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst  <= '0';
    wait; -- Eternal wait. Stops the process forever.
  end process;

end architecture sim;

Simuler avec GHDL

Laissez-nous compiler et simuler cela avec GHDL:

$ mkdir gh_work
$ ghdl -a --workdir=gh_work counter_sim.vhd
counter_sim.vhd:27:24: unit "counter" not found in 'library "work"'
counter_sim.vhd:50:35: no declaration for "rising_edge"

Alors les messages d'erreur nous disent deux choses importantes:

  • L'analyseur GHDL a découvert que notre conception instancie une entité nommée counter mais cette entité n'a pas été trouvée dans le work bibliothèque. C'est parce que nous n'avons pas compilé de counter avant counter_sim . Lors de la compilation de conceptions VHDL qui instancient des entités, les niveaux inférieurs doivent toujours être compilés avant les niveaux supérieurs (les conceptions hiérarchiques peuvent également être compilées de haut en bas, mais uniquement si elles instancient des component et non des entités).
  • La fonction rising_edge utilisée par notre conception n'est pas définie. Cela est dû au fait que cette fonction a été introduite dans VHDL 2008 et nous n'avons pas dit à GHDL d'utiliser cette version du langage (par défaut, il utilise VHDL 1993 avec une tolérance de la syntaxe VHDL 1987).

Laissez-nous réparer les deux erreurs et lancez la simulation:

$ ghdl -a --workdir=gh_work --std=08 counter.vhd counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
^C

Notez que l'option --std=08 est nécessaire pour l'analyse et la simulation. Notez également que nous avons lancé la simulation sur l'entité counter_sim , architecture sim , pas sur un fichier source.

Comme notre environnement de simulation a un processus sans fin (le processus qui génère l'horloge), la simulation ne s'arrête pas et nous devons l'interrompre manuellement. Au lieu de cela, nous pouvons spécifier une heure d'arrêt avec l'option --stop-time :

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns
ghdl:info: simulation stopped by --stop-time

En l'état, la simulation ne nous en dit pas beaucoup sur le comportement de notre DUT. Vidons les changements de valeur des signaux dans un fichier:

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns --vcd=counter_sim.vcd
Vcd.Avhpi_Error!
ghdl:info: simulation stopped by --stop-time

(ignorez le message d'erreur, c'est quelque chose qui doit être corrigé dans GHDL et cela n'a aucune conséquence). Un fichier counter_sim.vcd a été créé. Il contient au format VCD (ASCII) tous les changements de signal pendant la simulation. GTKWave peut nous montrer les formes d'onde graphiques correspondantes:

$ gtkwave counter_sim.vcd

où on peut voir que le compteur fonctionne comme prévu.

Une forme d'onde GTKWave

Simuler avec Modelsim

Le principe est exactement le même avec Modelsim:

$ vlib ms_work
...
$ vmap work ms_work
...
$ vcom -nologo -quiet -2008 counter.vhd counter_sim.vhd
$ vsim -voptargs="+acc" 'counter_sim(sim)' -do 'add wave /*; run 60ns'

entrer la description de l'image ici

Notez l' -voptargs="+acc" transmise à vsim : elle empêche le simulateur d'optimiser le signal de data et nous permet de le voir sur les formes d'onde.

Finissant gracieusement les simulations

Avec les deux simulateurs, nous avons dû interrompre la simulation sans fin ou spécifier un temps d'arrêt avec une option dédiée. Ce n'est pas très pratique. Dans de nombreux cas, l'heure de fin d'une simulation est difficile à prévoir. Il serait préférable d'arrêter la simulation depuis l'intérieur du code VHDL de l'environnement de simulation, lorsqu'une condition particulière est atteinte, par exemple lorsque la valeur actuelle du compteur atteint 20%. Ceci peut être réalisé avec une assertion dans le code. processus qui gère la réinitialisation:

  process
  begin
    rst <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst <= '0';
    loop
      wait until rising_edge(clk);
      assert data /= 20 report "End of simulation" severity failure;
    end loop;
  end process;

Tant que les data diffèrent de 20, la simulation se poursuit. Lorsque les data atteignent 20, la simulation se bloque avec un message d'erreur:

$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:90:24:@51ns:(assertion failure): End of simulation
ghdl:error: assertion failed
  from: process work.counter_sim(sim2).P1 at counter_sim.vhd:90
ghdl:error: simulation failed

Notez que nous avons recompilé uniquement l'environnement de simulation: c'est le seul design qui a changé et c'est le niveau supérieur. Si nous n'avions modifié que counter.vhd , nous aurions dû recompiler les deux: counter.vhd car il a changé et counter_sim.vhd parce que cela dépend de counter.vhd .

Briser la simulation avec un message d'erreur n'est pas très élégant. L'analyse automatique des messages de simulation peut également poser un problème pour décider si un test de non-régression automatique est réussi ou non. Une solution meilleure et beaucoup plus élégante consiste à arrêter tous les processus lorsqu'une condition est atteinte. Cela peut être fait, par exemple, en ajoutant un signal boolean fin de simulation ( eof ). Par défaut, il est initialisé à false au début de la simulation. Un de nos processus le mettra à la valeur true au moment de mettre fin à la simulation. Tous les autres processus surveilleront ce signal et s'arrêteront avec une wait éternelle quand cela deviendra true :

  signal eos:  boolean;
...
  process
  begin
    clk <= '0';
    wait for 1 ns;
    clk <= '1';
    wait for 1 ns;
    if eos then
      report "End of simulation";
      wait;
    end if;
  end process;

  process
  begin
    rst <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst <= '0';
    for i in 1 to 20 loop
      wait until rising_edge(clk);
    end loop;
    eos <= true;
    wait;
  end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:120:24:@50ns:(report note): End of simulation

Last but not least, il existe une solution encore meilleure introduite dans VHDL 2008 avec le package standard env et les procédures d’ stop et de finish qu’elle déclare:

use std.env.all;
...
  process
  begin
    rst    <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst    <= '0';
    for i in 1 to 20 loop
      wait until rising_edge(clk);
    end loop;
    finish;
  end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
simulation finished @49ns

Signaux vs variables, bref aperçu de la sémantique de simulation de VHDL

Cet exemple traite de l'un des aspects les plus fondamentaux du langage VHDL: la sémantique de la simulation. Il est destiné aux débutants VHDL et présente une vue simplifiée où de nombreux détails ont été omis (processus reportés, interface de procédure VHDL, variables partagées ...) Les lecteurs intéressés par la sémantique complète réelle doivent se référer au manuel de référence du langage (LRM).

Signaux et variables

La plupart des langages de programmation impératifs classiques utilisent des variables. Ce sont des conteneurs de valeur. Un opérateur d'affectation est utilisé pour stocker une valeur dans une variable:

a = 15;

et la valeur actuellement stockée dans une variable peut être lue et utilisée dans d'autres instructions:

if(a == 15) { print "Fifteen" }

VHDL utilise également des variables et ont exactement le même rôle que dans la plupart des langages impératifs. Mais VHDL offre également un autre type de conteneur de valeur: le signal. Les signaux stockent également des valeurs, peuvent également être affectés et lus. Le type de valeurs pouvant être stockées dans les signaux est (presque) identique à celui des variables.

Alors, pourquoi avoir deux types de conteneurs de valeur? La réponse à cette question est essentielle et au cœur de la langue. Comprendre la différence entre les variables et les signaux est la première chose à faire avant d'essayer de programmer quoi que ce soit dans VHDL.

Illustrons cette différence sur un exemple concret: l'échange.

Remarque: tous les fragments de code suivants font partie de processus. Nous verrons plus tard quels sont les processus.

    tmp := a;
    a   := b;
    b   := tmp;

échange les variables a et b . Après avoir exécuté ces 3 instructions, le nouveau contenu de a est l'ancien contenu de b et inversement. Comme dans la plupart des langages de programmation, une troisième variable temporaire ( tmp ) est nécessaire. Si, au lieu de variables, nous voulions échanger des signaux, nous écririons:

    r <= s;
    s <= r;

ou:

    s <= r;
    r <= s;

avec le même résultat et sans besoin d'un troisième signal temporaire!

Remarque: l’opérateur d’affectation de signaux VHDL <= est différent de l’opérateur d’attribution de variables := .

Regardons un deuxième exemple dans lequel nous supposons que le sous-programme print imprime la représentation décimale de son paramètre. Si a est une variable entière et que sa valeur actuelle est 15, en cours d'exécution:

    a := 2 * a;
    a := a - 5;
    a := a / 5;
    print(a);

imprimera:

5

Si nous exécutons cette étape par étape dans un débogueur, nous pouvons voir la valeur d’ a changement de 15 à 30, 25 et 5.

Mais si s est un signal entier et que sa valeur actuelle est 15, en cours d'exécution:

    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);

imprimera:

15
3

Si nous exécutons cette étape par étape dans un débogueur, nous ne verrons aucune modification de valeur de s avant l’instruction d’ wait . De plus, la valeur finale de s ne sera pas 15, 30, 25 ou 5 mais 3!

Ce comportement apparemment étrange est dû à la nature fondamentalement parallèle du matériel numérique, comme nous le verrons dans les sections suivantes.

Parallélisme

VHDL étant un langage de description de matériel (HDL), il est parallèle par nature. Un programme VHDL est un ensemble de programmes séquentiels exécutés en parallèle. Ces programmes séquentiels sont appelés processus:

P1: process
begin
  instruction1;
  instruction2;
  ...
  instructionN;
end process P1;

P2: process
begin
  ...
end process P2;

Les processus, tout comme le matériel qu'ils modélisent, ne s'arrêtent jamais: ce sont des boucles infinies. Après l'exécution de la dernière instruction, l'exécution continue avec la première.

Comme avec tout langage de programmation prenant en charge une forme ou une autre du parallélisme, un ordonnanceur est chargé de décider quel processus exécuter (et quand) lors d'une simulation VHDL. De plus, le langage offre des constructions spécifiques pour la communication et la synchronisation inter-processus.

La planification

Le planificateur gère une liste de tous les processus et, pour chacun d'entre eux, enregistre son état actuel qui peut être running , run-able ou suspended . Il y a au plus un processus en running : celui qui est actuellement exécuté. Tant que le processus en cours d'exécution n'exécute pas une instruction d' wait , il continue de s'exécuter et empêche l'exécution de tout autre processus. Le planificateur VHDL n'est pas préemptif: chaque processus est responsable de se suspendre et de laisser les autres processus s'exécuter. C'est l'un des problèmes que rencontrent fréquemment les débutants en VHDL: le processus libre.

  P3: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    r <= a;
  end process P3;

Note: la variable a est déclarée localement alors que les signaux s et r sont déclarés ailleurs, à un niveau supérieur. Les variables VHDL sont locales au processus qui les déclare et ne peuvent pas être vues par d'autres processus. Un autre processus pourrait également déclarer une variable nommée a , ce ne serait pas la même variable que celle du processus P3 .

Dès que le programmateur reprendra le processus P3 , la simulation se bloquera, l'heure actuelle de la simulation ne progressera plus et le seul moyen de l'arrêter sera de tuer ou d'interrompre la simulation. La raison en est que P3 n’a pas wait déclaration et restera donc en état d’éternité, running revue ses 3 instructions. Aucun autre processus ne pourra jamais fonctionner, même s'il est run-able .

Même les processus contenant une instruction wait peuvent provoquer le même problème:

  P4: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    if a = 16 then
      wait on s;
    end if;
    r <= a;
  end process P4;

Remarque: l'opérateur d'égalité VHDL est = .

Si le processus P4 est repris alors que la valeur du signal s est 3, il fonctionnera pour toujours car la condition a a = 16 ne sera jamais vraie.

Supposons que notre programme VHDL ne contient pas de tels processus pathologiques. Lorsque le processus en cours exécute une instruction d' wait , celle-ci est immédiatement suspendue et le planificateur la met à l'état suspended . L'instruction d' wait comporte également la condition pour que le processus redevienne run-able . Exemple:

    wait on s;

signifie me suspendre jusqu'à la valeur du signal s change . Cette condition est enregistrée par le programmateur. Le planificateur sélectionne ensuite un autre processus parmi les run-able , le met en état de running et l'exécute. Et les mêmes répétitions jusqu'à ce que tous les run-able ont été exécutés et suspendu les processus.

Remarque importante: lorsque plusieurs processus sont run-able , le standard VHDL ne spécifie pas comment le planificateur doit sélectionner celui à exécuter. Une conséquence est que, selon le simulateur, la version du simulateur, le système d'exploitation ou autre chose, deux simulations du même modèle VHDL pourraient, à un moment donné, faire des choix différents et sélectionner un processus différent à exécuter. Si ce choix avait un impact sur les résultats de la simulation, on pourrait dire que le VHDL est non déterministe. Comme le non-déterminisme est généralement indésirable, il serait de la responsabilité des programmeurs d'éviter les situations non déterministes. Heureusement, VHDL prend soin de cela et c'est là que les signaux entrent dans l'image.

Signaux et communication inter-processus

VHDL évite le non déterminisme en utilisant deux caractéristiques spécifiques:

  1. Les processus ne peuvent échanger des informations que par des signaux
  signal r, s: integer;  -- Common to all processes
...
  P5: process
    variable a: integer; -- Different from variable a of process P6
  begin
    a := s + 1;
    r <= a;
    a := r + 1;
    wait on s;
  end process P5;

  P6: process
    variable a: integer; -- Different from variable a of process P5
  begin
    a := r + 1;
    s <= a;
    wait on r;
  end process P6;

Remarque: les commentaires VHDL s'étendent de -- à la fin de la ligne.

  1. La valeur d'un signal VHDL ne change pas pendant l'exécution des processus

Chaque fois qu'un signal est affecté, la valeur assignée est enregistrée par le programmateur mais la valeur actuelle du signal reste inchangée. Ceci est une autre différence majeure avec les variables qui prennent leur nouvelle valeur immédiatement après leur affectation.

Regardons une exécution du processus P5 ci-dessus et supposons que a=5 , s=1 et r=0 quand il est repris par le planificateur. Après avoir exécuté l'instruction a := s + 1; , la valeur de la variable a change et devient 2 (1 + 1). Lors de l'exécution de l'instruction suivante r <= a; il est la nouvelle valeur de a (2) qui est attribué à r . Mais r étant un signal, la valeur actuelle de r est toujours 0. Donc, lors de l'exécution d' a := r + 1; , la variable a prend (immédiatement) la valeur 1 (0 + 1), pas 3 (2 + 1) comme le dirait l'intuition.

Quand le signal r prendra-t-il vraiment sa nouvelle valeur? Lorsque le planificateur aura exécuté tous les processus exécutables, ils seront tous suspendus. Ceci est également appelé: après un cycle delta . Ce n'est qu'alors que le planificateur examinera toutes les valeurs affectées aux signaux et mettra à jour les valeurs des signaux. Une simulation VHDL est une alternance de phases d'exécution et de phases de mise à jour du signal. Pendant les phases d'exécution, la valeur des signaux est gelée. Symboliquement, on dit qu'entre une phase d'exécution et la phase de mise à jour du signal suivante, un delta de temps s'est écoulé. Ce n'est pas du temps réel. Un cycle delta n'a pas de durée physique.

Grâce à ce mécanisme de mise à jour du signal retardé, le VHDL est déterministe. Les processus ne peuvent communiquer qu'avec des signaux et les signaux ne changent pas pendant l'exécution des processus. Ainsi, l'ordre d'exécution des processus n'a pas d'importance: leur environnement externe (les signaux) ne change pas pendant l'exécution. Montrons ceci sur l'exemple précédent avec les processus P5 et P6 , où l'état initial est P5.a=5 , P6.a=10 , s=17 , r=0 et où le planificateur décide d'exécuter P5 premier et P6 suivant . Le tableau suivant montre la valeur des deux variables, les valeurs actuelles et suivantes des signaux après l'exécution de chaque instruction de chaque processus:

processus / instruction P5.a P6.a s.current s.next r.current r.next
Etat initial 5 dix 17 0
P5 / a := s + 1 18 dix 17 0
P5 / r <= a 18 dix 17 0 18
P5 / a := r + 1 1 dix 17 0 18
P5 / wait on s 1 dix 17 0 18
P6 / a := r + 1 1 1 17 0 18
P6 / s <= a 1 1 17 1 0 18
P6 / wait on r 1 1 17 1 0 18
Après la mise à jour du signal 1 1 1 18

Avec les mêmes conditions initiales, si l'ordonnanceur décide d'exécuter P6 premier et P5 ensuite:

processus / instruction P5.a P6.a s.current s.next r.current r.next
Etat initial 5 dix 17 0
P6 / a := r + 1 5 1 17 0
P6 / s <= a 5 1 17 1 0
P6 / wait on r 5 1 17 1 0
P5 / a := s + 1 18 1 17 1 0
P5 / r <= a 18 1 17 1 0 18
P5 / a := r + 1 1 1 17 1 0 18
P5 / wait on s 1 1 17 1 0 18
Après la mise à jour du signal 1 1 1 18

Comme on peut le voir, après l'exécution de nos deux processus, le résultat est le même quel que soit l'ordre d'exécution.

Cette sémantique d'attribution de signaux contre-intuitive est la raison d'un deuxième type de problèmes que rencontrent fréquemment les débutants VHDL: l'affectation qui ne semble pas fonctionner car elle est retardée d'un cycle delta. Lors de l'exécution du processus P5 étape par étape dans un débogueur, après que r ait été affecté à 18 et a r + 1 a été attribué, on peut s'attendre à ce que la valeur de a soit 19 mais le débogueur dit obstinément que r=0 et a=1 ...

Remarque: le même signal peut être attribué plusieurs fois au cours de la même phase d'exécution. Dans ce cas, c'est la dernière affectation qui décide de la valeur suivante du signal. Les autres affectations n'ont aucun effet, comme si elles n'avaient jamais été exécutées.

Il est temps de vérifier notre compréhension: revenez à notre tout premier exemple d’échange et essayez de comprendre pourquoi:

  process
  begin
    ---
    s <= r;
    r <= s;
    ---
  end process;

échange en fait les signaux r et s sans avoir besoin d'un troisième signal temporaire et pourquoi:

  process
  begin
    ---
    r <= s;
    s <= r;
    ---
  end process;

serait strictement équivalent. Essayez de comprendre pourquoi, si s est un signal entier et que sa valeur actuelle est 15, et que nous exécutons:

  process
  begin
    ---
    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);
    ---
  end process;

les deux premières assignations de signal s n'ont aucun effet, pourquoi s est finalement assigné 3 et pourquoi les deux valeurs imprimées sont 15 et 3.

Temps physique

Afin de modéliser le matériel, il est très utile de pouvoir modéliser le temps physique nécessaire à certaines opérations. Voici un exemple de la façon dont cela peut être fait en VHDL. L'exemple modélise un compteur synchrone et il s'agit d'un code VHDL complet et autonome pouvant être compilé et simulé:

-- File counter.vhd
entity counter is
end entity counter;

architecture arc of counter is
  signal clk: bit; -- Type bit has two values: '0' and '1'
  signal c, nc: natural; -- Natural (non-negative) integers
begin
  P1: process
  begin
    clk <= '0';
    wait for 10 ns; -- Ten nano-seconds delay
    clk <= '1';
    wait for 10 ns; -- Ten nano-seconds delay
  end process P1;

  P2: process
  begin
    if clk = '1' and clk'event then
      c <= nc;
    end if;
    wait on clk;
  end process P2;

  P3: process
  begin
    nc <= c + 1 after 5 ns; -- Five nano-seconds delay
    wait on c;
  end process P3;
end architecture arc;

Dans le processus P1 l'instruction wait n'est pas utilisée pour attendre que la valeur d'un signal change, comme nous l'avons vu jusqu'à présent, mais pour attendre une durée donnée. Ce processus modélise un générateur d'horloge. Le signal clk est l'horloge de notre système, il est périodique avec une période de 20 ns (50 MHz) et a un cycle de service.

Le processus P2 modélise un registre qui, si un front montant de clk vient de se produire, assigne la valeur de son entrée nc à sa sortie c , puis attend le prochain changement de valeur de clk .

Le processus P3 modélise un incrémenteur qui affecte la valeur de son entrée c , incrémentée de un, à sa sortie nc ... avec un délai physique de 5 ns. Il attend ensuite que la valeur de son entrée c change. C'est aussi nouveau. Jusqu'à présent, nous attribuions toujours des signaux avec:

  s <= value;

ce qui, pour les raisons expliquées dans les sections précédentes, peut implicitement se traduire par:

  s <= value; -- after delta

Ce petit système de matériel numérique pourrait être représenté par la figure suivante:

Un compteur synchrone

Avec l'introduction du temps physique, et sachant que nous avons aussi un temps symbolique mesuré en delta , nous avons maintenant un temps à deux dimensions que nous désignerons T+DT est un temps physique mesuré en nano-secondes et D un nombre de deltas (sans durée physique).

L'image complète

Il y a un aspect important de la simulation VHDL que nous n'avons pas encore abordé: après une phase d'exécution, tous les processus sont suspended . Nous avons déclaré de manière informelle que le programmateur met à jour les valeurs des signaux qui ont été attribués. Mais, dans notre exemple de compteur synchrone, doit-il mettre à jour les signaux clk , c et nc en même temps? Qu'en est-il des délais physiques? Et ce qui arrive ensuite avec tous les processus en suspended état et aucun en run-able en run-able état?

L'algorithme de simulation complet (mais simplifié) est le suivant:

  1. Initialisation
    • Régler l'heure actuelle Tc sur 0 + 0 (0 ns, 0 cycle delta)
    • Initialiser tous les signaux.
    • Exécutez chaque processus jusqu'à ce qu'il soit suspendu dans une déclaration d' wait .
      • Enregistrez les valeurs et les délais d'attribution des signaux.
      • Enregistrez les conditions pour que le processus reprenne (délai ou changement de signal).
    • Calculez la prochaine fois que Tn est la première des:
      • Le temps de reprise des processus est suspendu en wait for <delay> .
      • La prochaine fois qu'une valeur de signal doit changer.
  1. Cycle de simulation
    • Tc=Tn .
    • Mettre à jour les signaux qui doivent être.
    • Mettre en état d' run-able tous les processus qui attendaient un changement de valeur de l'un des signaux mis à jour.
    • Mettez en état d' run-able tous les processus qui ont été suspendus par une instruction wait for <delay> et pour lesquels le temps de reprise est Tc .
    • Exécutez tous les processus exécutables jusqu'à ce qu'ils soient suspendus.
      • Enregistrez les valeurs et les délais d'attribution des signaux.
      • Enregistrez les conditions pour que le processus reprenne (délai ou changement de signal).
    • Calculez la prochaine fois que Tn est la première des:
      • Le temps de reprise des processus est suspendu en wait for <delay> .
      • La prochaine fois qu'une valeur de signal doit changer.
    • Si Tn est l'infini, arrêtez la simulation. Sinon, lancez un nouveau cycle de simulation.

Simulation manuelle

Pour conclure, nous allons maintenant exercer manuellement l'algorithme de simulation simplifié sur le compteur synchrone présenté ci-dessus. Nous décidons arbitrairement que, lorsque plusieurs processus sont exécutables, l'ordre sera P3 > P2 > P1 . Les tableaux suivants représentent l'évolution de l'état du système lors de l'initialisation et des premiers cycles de simulation. Chaque signal a sa propre colonne dans laquelle la valeur actuelle est indiquée. Lorsqu'une affectation de signal est exécutée, la valeur programmée est ajoutée à la valeur actuelle, par exemple a/b@T+D si la valeur actuelle est a et la prochaine valeur sera b à l'instant T+D (temps physique plus cycles delta) . Les 3 dernières colonnes indiquent la condition pour reprendre les processus suspendus (nom des signaux qui doivent changer ou heure à laquelle le processus doit reprendre).

Phase d'initialisation:

Les opérations Tc Tn clk c nc P1 P2 P3
Définir l'heure actuelle 0 + 0
Initialiser tous les signaux 0 + 0 '0' 0 0
P3/nc<=c+1 after 5 ns 0 + 0 '0' 0 0/1 @ 5 + 0
P3/wait on c 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/if clk='1'... 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/end if 0 + 0 '0' 0 0/1 @ 5 + 0 c
P2/wait on clk 0 + 0 '0' 0 0/1 @ 5 + 0 clk c
P1/clk<='0' 0 + 0 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 clk c
P1/wait for 10 ns 0 + 0 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c
Calculer la prochaine fois 0 + 0 0 + 1 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c

Cycle de simulation # 1

Les opérations Tc Tn clk c nc P1 P2 P3
Définir l'heure actuelle 0 + 1 '0' / '0' @ 0 + 1 0 0/1 @ 5 + 0 10 + 0 clk c
Mise à jour des signaux 0 + 1 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
Calculer la prochaine fois 0 + 1 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c

Note: pendant le premier cycle de simulation, il n’ya pas de phase d’exécution car aucun de nos 3 processus n’a sa condition de reprise satisfaite. P2 attend un changement de valeur de clk et il y a eu une transaction sur clk , mais comme les anciennes et les nouvelles valeurs sont identiques, ce n'est pas un changement de valeur.

Cycle de simulation # 2

Les opérations Tc Tn clk c nc P1 P2 P3
Définir l'heure actuelle 5 + 0 '0' 0 0/1 @ 5 + 0 10 + 0 clk c
Mise à jour des signaux 5 + 0 '0' 0 1 10 + 0 clk c
Calculer la prochaine fois 5 + 0 10 + 0 '0' 0 1 10 + 0 clk c

Note: encore une fois, il n'y a pas de phase d'exécution. nc changé mais aucun processus n'attend nc .

Cycle de simulation # 3

Les opérations Tc Tn clk c nc P1 P2 P3
Définir l'heure actuelle 10 + 0 '0' 0 1 10 + 0 clk c
Mise à jour des signaux 10 + 0 '0' 0 1 10 + 0 clk c
P1/clk<='1' 10 + 0 '0' / '1' @ 10 + 1 0 1 clk c
P1/wait for 10 ns 10 + 0 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c
Calculer la prochaine fois 10 + 0 10 + 1 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c

Cycle de simulation # 4

Les opérations Tc Tn clk c nc P1 P2 P3
Définir l'heure actuelle 10 + 1 '0' / '1' @ 10 + 1 0 1 20 + 0 clk c
Mise à jour des signaux 10 + 1 '1' 0 1 20 + 0 clk c
P2/if clk='1'... 10 + 1 '1' 0 1 20 + 0 c
P2/c<=nc 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 c
P2/end if 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 c
P2/wait on clk 10 + 1 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
Calculer la prochaine fois 10 + 1 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c

Cycle de simulation # 5

... passer à l'anglais pour continuer à lire

Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow
Les opérations Tc Tn clk c nc P1 P2 P3
Définir l'heure actuelle 10 + 2 '1' 0/1 @ 10 + 2 1 20 + 0 clk c
Mise à jour des signaux 10 + 2 '1' 1 1 20 + 0 clk c
P3/nc<=c+1 after 5 ns 10 + 2 '1' 1 1/2 @ 15 + 0 20 + 0 clk
P3/wait on c 10 + 2 '1' 1 1/2 @ 15 + 0 20 + 0 clk c