Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Industrial programming >> VHDL

Vérification aléatoire contrainte

La vérification aléatoire contrainte est une stratégie de banc d'essai qui repose sur la génération de transactions pseudo-aléatoires pour l'appareil sous test (DUT). L'objectif est d'atteindre une couverture fonctionnelle d'un certain nombre d'événements prédéfinis grâce à une interaction aléatoire avec le DUT.

Open Source VHDL Verification Methodology (OSVVM) est une bibliothèque VHDL gratuite qui comprend un certain nombre de packages pratiques pour créer des bancs de test aléatoires contraints. Nous sommes particulièrement intéressés par le RandomPkg et le CoveragePck, que nous utiliserons dans cet article. Je recommande de visiter la page OSVVM GitHub pour en savoir plus sur les fonctionnalités de cette bibliothèque.

L'appareil testé

Je vais plonger directement dans un exemple pour mieux expliquer en quoi un banc de test aléatoire contraint diffère du banc de test classique, qui utilise des tests dirigés. Nous avons créé un tampon FIFO en anneau dans l'article précédent de ce blog, mais nous n'avons pas créé de banc d'essai d'auto-vérification pour vérifier l'exactitude du module.

Nous allons créer un banc de test approprié pour le FIFO du tampon en anneau qui utilise une vérification aléatoire contrainte.

entity ring_buffer is
  generic (
    RAM_WIDTH : natural;
    RAM_DEPTH : natural
  );
  port (
    clk : in std_logic;
    rst : in std_logic;

    -- Write port
    wr_en : in std_logic;
    wr_data : in std_logic_vector(RAM_WIDTH - 1 downto 0);

    -- Read port
    rd_en : in std_logic;
    rd_valid : out std_logic;
    rd_data : out std_logic_vector(RAM_WIDTH - 1 downto 0);

    -- Flags
    empty : out std_logic;
    empty_next : out std_logic;
    full : out std_logic;
    full_next : out std_logic;

    -- The number of elements in the FIFO
    fill_count : out integer range RAM_DEPTH - 1 downto 0
  );
end ring_buffer;

L'entité du module de mémoire tampon en anneau est indiquée dans le code ci-dessus. Nous allons traiter le DUT comme une boîte noire, ce qui signifie que nous ne supposerons aucune connaissance de la façon dont le DUT est implémenté. Après tout, cet article concerne le testbench, pas le ring buffer FIFO.

Nous allons instancier le DUT dans le testbench en utilisant la méthode d'instanciation d'entité. L'instanciation est triviale, donc je vais omettre le code pour l'instant, mais il peut être téléchargé plus tard dans cet article.

Les génériques du DUT seront mappés sur les valeurs suivantes :

Stratégie du banc d'essai

Passons en revue la stratégie de test avant de commencer à mettre en œuvre quoi que ce soit. L'image ci-dessous montre le concept principal du testbench que nous sommes sur le point de créer.

Nous effectuerons des transactions d'écriture aléatoires du côté entrée du DUT. Les données d'entrée seront définies sur une valeur aléatoire à chaque cycle d'horloge et les stroboscopes sur l'entrée d'activation d'écriture auront une durée aléatoire.

De même, nous effectuerons des lectures au hasard. Nous allons affirmer le signal d'activation de lecture en rafales qui durent un nombre aléatoire de cycles d'horloge.

Il y aura un modèle comportemental en parallèle du DUT. Il s'agit d'un FIFO qui est implémenté différemment du tampon en anneau utilisé dans le DUT, mais qui a toujours la même interface. Contrairement au DUT, le modèle comportemental n'a pas besoin d'être synthétisable. Cela nous donne la liberté d'utiliser des fonctionnalités de programmation VHDL avancées pour le créer.

Nous comparerons la sortie du DUT avec la sortie du modèle comportemental dans un processus séparé. Ce processus sera seul responsable de faire cette comparaison à chaque cycle d'horloge en utilisant des instructions assert. Si les deux implémentations FIFO se comportent différemment à tout moment, un échec d'assertion entraînera l'arrêt de la simulation avec une erreur.

Enfin, nous collecterons des données de couverture fonctionnelle en observant les transactions qui vont et viennent du DUT. Un point de couverture fonctionnelle peut signifier par exemple une lecture et une écriture simultanées, ou que la FIFO est remplie au moins une fois. Nous surveillerons ces événements dans notre processus principal de séquenceur de banc d'essai. La simulation sera arrêtée lorsque tous les événements de couverture fonctionnelle que nous surveillons se seront produits.

Importation de la bibliothèque OSVVM

La bibliothèque OSVVM peut être utilisée avec n'importe quel simulateur prenant en charge VHDL-2008. Il est peut-être déjà inclus dans les bibliothèques par défaut fournies avec votre simulateur. Il est inclus dans ModelSim PE Student Edition, qui peut être téléchargé gratuitement à partir de Mentor Graphics.

ModelSim est livré avec une ancienne version d'OSVVM, mais ça va, il a tout ce dont nous avons besoin. Nous pouvons simplement continuer et importer les packages aléatoires et de couverture comme ceci :

library osvvm;
use osvvm.RandomPkg.all;
use osvvm.CoveragePkg.all;

La dernière version de la bibliothèque OSVVM peut toujours être téléchargée depuis la page GitHub. Faites-le si votre simulateur ne l'a pas inclus ou si vous souhaitez utiliser les dernières fonctionnalités de la bibliothèque.

Déclarer les variables OSSVM

La bibliothèque OSVVM contient des packages avec des types protégés. Les variables créées à partir de celles-ci auraient une portée limitée au processus dans lequel elles ont été définies. Par conséquent, nous les déclarerons à la place en tant que variables partagées dans la région déclarative de l'architecture de testbench, comme indiqué dans le code ci-dessous.

 -- OSVVM variables
  shared variable rv : RandomPType;
  shared variable bin1, bin2, bin3, bin4, bin5, bin6 : CovPType;

Le rv variable de type RandomPType sert à générer des valeurs aléatoires. Nous n'avons besoin que d'un seul d'entre eux, car nous pouvons utiliser le même objet dans chaque processus où nous devons générer des valeurs aléatoires. La dernière ligne de code déclare six variables de type CovPType .

Nous avons déclaré six variables de couverture parce que nous allons avoir six objectifs de couverture, nous appellerons ces objets «bins». Les variables partagées doivent être initialisées avant de pouvoir être utilisées pour collecter des données de couverture. Nous faisons cela en appelant le AddBins procédure sur chacun des CovPType poubelles.

    -- Set up coverage bins
    bin1.AddBins("Write while empty", ONE_BIN);
    bin2.AddBins("Read while full", ONE_BIN);
    bin3.AddBins("Read and write while almost empty", ONE_BIN);
    bin4.AddBins("Read and write while almost full", ONE_BIN);
    bin5.AddBins("Read without write when almost empty", ONE_BIN);
    bin6.AddBins("Write without read when almost full", ONE_BIN);

Nous fournissons une description de chaîne de la zone de couverture comme premier paramètre du AddBins procédure. Cette chaîne réapparaîtra à la fin de la simulation lorsque nous imprimerons les statistiques pour chacun des groupes de couverture. Comme vous pouvez le voir dans les descriptions textuelles, nous allons utiliser les bacs pour vérifier si des cas particuliers très spécifiques se sont produits ou non.

AddBins est une procédure surchargée qui peut être utilisée pour créer plusieurs tableaux de bord dans les variables bin. Cependant, nous n'aurons qu'un seul tableau de bord associé à chaque bac. Nous fournirons donc la constante de commodité ONE_BIN en paramètre du AddBins procédure. Cela initialisera le CovPType variables avec un bac chacune. Les tableaux de bord représentés par les bacs sont considérés comme couverts lorsque les événements qu'ils surveillent se sont produits au moins une fois.

Générer une entrée aléatoire

Commençons par créer le processus qui génère les données d'entrée dans le DUT. Le FIFO du tampon en anneau est conçu pour ignorer les tentatives d'écrasement et de lecture. Par conséquent, nous pouvons simplement écrire des données aléatoires en rafales de durée aléatoire. Nous n'avons pas à nous demander si le DUT est réellement prêt à absorber les données ou non.

  PROC_WRITE : process
  begin
    wr_en <= rv.RandSlv(1)(1) and not rst;

    for i in 0 to rv.RandInt(0, 2 * RAM_DEPTH) loop
      wr_data <= rv.RandSlv(RAM_WIDTH);
      wait until rising_edge(clk);
    end loop;
  end process;

La seule considération que ce processus prend est que le DUT n'est pas réinitialisé. Nous activons ou désactivons au hasard le signal d'activation d'écriture dans la première ligne de ce processus, mais il ne sera activé que si rst est '0' .

La boucle for suivante écrira des données aléatoires dans le DUT pendant un nombre aléatoire de cycles d'horloge, même si le signal d'activation n'est pas actif. Nous pouvons le faire car le DUT est censé ignorer le wr_data port à moins que le wr_en le signal est '1' . Après la boucle for, le programme reviendra au début du processus, déclenchant une autre transaction d'écriture aléatoire.

Exécuter des lectures aléatoires

Le processus qui lit les données du DUT est similaire au processus d'écriture. On peut activer aléatoirement le rd_en signal à tout moment car le DUT est conçu pour ignorer les tentatives de lecture lorsqu'il est vide. Les données qui apparaissent sur le rd_data le port n'est pas réellement coché. Ce processus contrôle uniquement le signal de validation de lecture.

  PROC_READ : process
  begin
    rd_en <= rv.RandSlv(1)(1) and not rst;

    for i in 0 to rv.RandInt(0, 2 * RAM_DEPTH) loop
      wait until rising_edge(clk);
    end loop;
  end process;

Vérification comportementale

Nous allons construire un modèle comportemental du DUT au sein de notre banc d'essai pour vérifier son comportement. Il s'agit d'une stratégie de banc d'essai bien connue. Tout d'abord, nous alimentons le modèle comportemental simultanément avec la même entrée que le DUT. Ensuite, nous pouvons comparer la sortie des deux pour vérifier si le DUT a le bon comportement.

L'image ci-dessus montre la disposition de base d'un tel banc d'essai. Le modèle comportemental fonctionne en parallèle avec le DUT. Nous l'utilisons comme modèle pour vérifier les sorties du DUT.

Le FIFO du banc d'essai

Nous utiliserons une liste chaînée pour implémenter le modèle comportemental. Les listes chaînées ne peuvent pas être synthétisées, mais elles sont parfaites pour les bancs d'essai. Vous vous souviendrez peut-être de Comment créer une liste chaînée en VHDL article si vous êtes un lecteur régulier de ce blog. Nous utiliserons le code qui en découle pour implémenter le modèle comportemental du FIFO du tampon en anneau.

package DataStructures is
   type LinkedList is protected
 
      procedure Push(constant Data : in integer);
      impure function Pop return integer;
      impure function IsEmpty return boolean;
 
   end protected;
end package DataStructures;

La déclaration de package pour la FIFO de liste liée est indiquée dans le code ci-dessus. C'est un type protégé qui a les trois fonctions; Push, Pop et IsEmpty. Ceux-ci sont utilisés pour ajouter et supprimer des éléments du FIFO, ainsi que pour vérifier s'il ne reste aucun élément.

  -- Testbench FIFO that emulates the DUT
  shared variable fifo : LinkedList;

Les types protégés sont des constructions de type classe en VHDL. Nous allons créer un objet de la liste chaînée en déclarant une variable partagée dans la région déclarative du testbench, comme indiqué dans le code ci-dessus.

Le modèle comportemental

Pour émuler complètement le comportement du tampon FIFO en anneau, nous déclarons deux nouveaux signaux qui reflètent les signaux de sortie du DUT. Le premier signal contient les données de sortie du modèle comportemental et le second est le signal valide associé.

  -- Testbench FIFO signals
  signal fifo_out : integer;
  signal fifo_out_valid : std_logic := '0';

Le code ci-dessus montre la déclaration des deux signaux de sortie du modèle comportemental. Nous n'avons pas besoin de signaux d'entrée dédiés pour le modèle comportemental, car ils sont les mêmes que ceux qui sont connectés au DUT. Nous utilisons des signaux pour émuler la sortie du DUT car cela nous permet de collecter facilement des données de couverture, comme nous le verrons plus loin dans cet article.

PROC_BEHAVIORAL_MODEL : process
begin
  wait until rising_edge(clk) and rst = '0';

  -- Emulate a write
  if wr_en = '1' and full = '0' then
    fifo.Push(to_integer(unsigned(wr_data)));
    report "Push " & integer'image(to_integer(unsigned(wr_data)));
  end if;
    
  -- Emulate a read
  if rd_en = '1' and empty = '0' then
    fifo_out <= fifo.Pop;
    fifo_out_valid <= '1';
  else
    fifo_out_valid <= '0';
  end if;
  
end process;

Le processus qui implémente le modèle comportemental du tampon circulaire FIFO est illustré dans le code ci-dessus. Ce processus sera déclenché à chaque front montant de l'horloge, si le signal de réinitialisation n'est pas actif.

Le modèle comportemental envoie une nouvelle valeur au FIFO du banc d'essai chaque fois que le wr_en signal est affirmé alors que le full le signal est '0' . De même, la logique de lecture dans le processus de modèle comportemental fonctionne en écoutant le rd_en et empty signaux. Ce dernier est contrôlé par le DUT, mais nous vérifierons qu'il fonctionne dans le prochain processus que nous créerons.

Vérification des sorties

Toute la logique responsable de la vérification des sorties du DUT est rassemblée dans un processus nommé « PROC_VERIFY ». Nous utilisons des déclarations assert pour vérifier que les sorties du DUT correspondent à celles du modèle comportemental. Nous vérifions également que le DUT et le modèle comportemental s'accordent sur le moment où le FIFO est vide.

Le code du processus de vérification est indiqué ci-dessous.

PROC_VERIFY : process
begin
  wait until rising_edge(clk) and rst = '0';
  
  -- Check that DUT and TB FIFO are reporting empty simultaneously
  assert (empty = '1' and fifo.IsEmpty) or
         (empty = '0' and not fifo.IsEmpty)
    report "empty=" & std_logic'image(empty) 
      & " while fifo.IsEmpty=" & boolean'image(fifo.IsEmpty)
    severity failure;

  -- Check that the valid signals are matching
  assert rd_valid = fifo_out_valid
    report "rd_valid=" & std_logic'image(rd_valid) 
      & " while fifo_out_valid=" & std_logic'image(fifo_out_valid)
    severity failure;

  -- Check that the output from the DUT matches the TB FIFO
  if rd_valid then
    assert fifo_out = to_integer(unsigned(rd_data))
      report "rd_data=" & integer'image(to_integer(unsigned(rd_data)))
        & " while fifo_out=" & integer'image(fifo_out)
      severity failure;
      report "Pop " & integer'image(fifo_out);
  end if;

end process;

Le processus est déclenché par le front montant de l'horloge, comme on peut le voir sur la première ligne de code. Le DUT est un processus cadencé, et la logique aval qui est connectée au DUT devrait également être synchrone avec le signal d'horloge. Par conséquent, il est logique de vérifier les sorties sur le front d'horloge montant.

Le deuxième bloc de code vérifie que le empty le signal provenant du DUT n'est affirmé que lorsque la FIFO du banc de test est vide. Le DUT et le modèle comportemental doivent tous deux convenir que le FIFO est vide ou non pour que ce test réussisse.

Vient ensuite une comparaison des signaux valides de données lues. Le DUT et le modèle comportemental doivent produire des données simultanément, sinon il y a quelque chose qui ne va pas.

Enfin, nous vérifions que les données de sortie du DUT correspondent à l'élément suivant que nous extrayons de la FIFO du testbench. Ceci, bien sûr, ne se produit que si le rd_valid signal est affirmé, ce qui signifie que le rd_data le signal peut être échantillonné.

Collecte des données de couverture

Pour contrôler le flux principal du banc de test, nous allons créer un processus séquenceur. Ce processus initialisera les bacs de couverture, exécutera les tests et arrêtera le testbench lorsque tous les objectifs de couverture auront été atteints. Le code ci-dessous montre le processus complet "PROC_SEQUENCER".

PROC_SEQUENCER : process
begin

  -- Set up coverage bins
  bin1.AddBins("Write while empty", ONE_BIN);
  bin2.AddBins("Read while full", ONE_BIN);
  bin3.AddBins("Read and write while almost empty", ONE_BIN);
  bin4.AddBins("Read and write while almost full", ONE_BIN);
  bin5.AddBins("Read without write when almost empty", ONE_BIN);
  bin6.AddBins("Write without read when almost full", ONE_BIN);

  wait until rising_edge(clk);
  wait until rising_edge(clk);
  rst <= '0';
  wait until rising_edge(clk);

  loop
    wait until rising_edge(clk);

    -- Collect coverage data
    bin1.ICover(to_integer(wr_en = '1' and empty = '1'));
    bin2.ICover(to_integer(rd_en = '1' and full = '1'));
    bin3.ICover(to_integer(rd_en = '1' and wr_en = '1' and
                           empty = '0' and empty_next = '1'));
    bin4.ICover(to_integer(rd_en = '1' and wr_en = '1' and
                           full = '0' and full_next = '1'));
    bin5.ICover(to_integer(rd_en = '1' and wr_en = '0' and
                           empty = '0' and empty_next = '1'));
    bin6.ICover(to_integer(rd_en = '0' and wr_en = '1' and
                           full = '0' and full_next = '1'));

    -- Stop the test when all coverage goals have been met
    exit when
      bin1.IsCovered and
      bin2.IsCovered and
      bin3.IsCovered and
      bin4.IsCovered and
      bin5.IsCovered and
      bin6.IsCovered;
  end loop;
  
  report("Coverage goals met");

  -- Make sure that the DUT is empty before terminating the test
  wr_en <= force '0';
  rd_en <= force '1';
  loop
    wait until rising_edge(clk);
    exit when empty = '1';
  end loop;

  -- Print coverage data
  bin1.WriteBin;
  bin2.WriteBin;
  bin3.WriteBin;
  bin4.WriteBin;
  bin5.WriteBin;
  bin6.WriteBin;
  
  finish;
end process;

Tout d'abord, nous initialisons les objets du bac de couverture en appelant le AddBins procédure sur eux, comme nous l'avons déjà discuté plus tôt dans cet article. Ensuite, une fois la réinitialisation publiée, nous procédons à la collecte des données de couverture. À chaque front montant de l'horloge, le code à l'intérieur de la construction de la boucle sera exécuté.

Le premier bloc de code à l'intérieur de la boucle sert à collecter les données de couverture. Nous pouvons appeler le ICover procédure sur le bin pour enregistrer un hit sur le point de couverture qu'il représente. Si nous fournissons le paramètre entier 0 , l'appel n'aura aucun effet. Si nous utilisons le paramètre entier 1 , cela comptera comme un succès.

Il n'y a qu'un seul "bin" à l'intérieur de chacun des objets bin de couverture, car nous les avons initialisés en utilisant le ONE_BIN constant. Cette poubelle unique est joignable en appelant le ICover(1) . Nous pouvons enregistrer un succès ou un échec sur le point de couverture en convertissant nos expressions booléennes en nombres entiers 1 ou 0 en utilisant le to_integer fonction

Une fois les données de couverture enregistrées, nous vérifions si tous les objectifs de couverture ont été atteints en appelant le IsCovered fonction sur tous les bacs. Ensuite, nous sortons de la boucle si tous les objectifs de couverture ont été atteints.

Nous nous assurerons que le DUT est vide avant de terminer le test. Pour y parvenir, nous prenons le contrôle des processus d'écriture et de lecture en forçant le wr_en signaler à '0' et le rd_en signaler au '1' .

Enfin, nous imprimons des statistiques sur le nombre de fois que chaque objectif de couverture a été atteint en appelant le WriteBin fonction sur chacune des tranches de couverture. Le finish mot-clé à la fin du processus entraînera l'arrêt de la simulation par le simulateur.

Exécuter le banc d'essai

Vous pouvez télécharger l'intégralité du projet ModelSim avec tous les fichiers VHDL en utilisant le formulaire ci-dessous.

Après avoir chargé le projet en exécutant le do-file inclus dans le Zip, nous pouvons lancer le testbench simplement en tapant « runtb » dans la console ModelSim. Le temps d'exécution du testbench sera aléatoire car les objectifs de couverture sont atteints au hasard. Cependant, les résultats du test sont reproductibles car c'est en fait une séquence pseudo-aléatoire qui est utilisée.

Nous n'avons pas initialisé de graine dans notre code, ce qui signifie que la valeur de graine par défaut sera utilisée pour le générateur pseudo-aléatoire. Il est possible de définir une graine différente en appelant le InitSeed procédure sur le RandomPType objet, cela produira une séquence aléatoire différente.

La sortie de la console

La sortie imprimée sur la console ModelSim après avoir donné la commande "runtb" est illustrée ci-dessous. Il y aura beaucoup de poussées aléatoires vers et depuis le FIFO du banc de test pendant l'exécution de la simulation.

VSIM 2> runtb
# ** Warning: Design size of 15929 statements or 2 leaf instances exceeds
#             ModelSim PE Student Edition recommended capacity.
# Expect performance to be quite adversely affected.
# ** Note: Push 34910
#    Time: 790 ns  Iteration: 0  Instance: /ring_buffer_tb
...
# ** Note: Pop 37937
#    Time: 83100 ns  Iteration: 0  Instance: /ring_buffer_tb
# ** Note: Pop 13898
#    Time: 83110 ns  Iteration: 0  Instance: /ring_buffer_tb
# %% WriteBin: 
# %% Write while empty  Bin:(1)   Count = 2  AtLeast = 1
# 
# %% WriteBin: 
# %% Read while full  Bin:(1)   Count = 3  AtLeast = 1
# 
# %% WriteBin: 
# %% Read and write while almost empty  Bin:(1)   Count = 106  AtLeast = 1
# 
# %% WriteBin: 
# %% Read and write while almost full  Bin:(1)   Count = 1  AtLeast = 1
# 
# %% WriteBin: 
# %% Read without write when almost empty  Bin:(1)   Count = 1  AtLeast = 1
# 
# %% WriteBin: 
# %% Write without read when almost full  Bin:(1)   Count = 3  AtLeast = 1
#
# Break in Process PROC_SEQUENCER at C:/crv/ring_buffer_tb.vhd line 127

Les statistiques de tous les groupes de couverture sont imprimées lorsque tous les objectifs de couverture ont été atteints. Certaines des poubelles n'ont été touchées qu'une seule fois, tandis qu'une a été touchée 106 fois. Mais au final, chaque poubelle a été touchée au moins une fois. Ainsi, nous pouvons savoir que tous les événements pour lesquels nous avons défini des tranches de couverture ont été testés et vérifiés.

La forme d'onde

Examinons la forme d'onde pour avoir une idée de ce que le banc d'essai a fait. L'image ci-dessous montre la forme d'onde avec le fill_count signal représenté comme une valeur analogique. La FIFO est pleine lorsque la trace de ce signal est en haut, et vide lorsqu'elle est en bas.

Comme nous pouvons le voir sur la forme d'onde, le tampon en anneau est rempli et vidé au hasard. Pourtant, nous n'avons pas explicitement programmé ces inclinaisons et déclins du niveau de remplissage. Néanmoins, nous observons un modèle organique d'interaction avec le DUT.

En savoir plus sur la vérification aléatoire contrainte

La vérification aléatoire contrainte est une bonne stratégie de banc d'essai lorsque le vecteur de test a trop de permutations pour qu'un test exhaustif soit pratique. Les interactions aléatoires présentent un comportement plus naturel que ne l'aurait fait un test de cas d'angle dirigé, sans sacrifier la précision.

Nous pouvons être sûrs que tous les cas extrêmes ont été rencontrés, tant que nous avons correctement configuré la collecte des données de couverture. L'avantage supplémentaire est que les tests aléatoires sont plus susceptibles d'exposer les faiblesses du DUT que vous ne testez pas spécifiquement. Tant que vous connaissez tous les cas extrêmes, vous pouvez créer des tests dirigés pour eux. Mais les cas critiques sont facilement négligés, et c'est là que vous pourriez bénéficier de la méthodologie de vérification aléatoire contrainte.

Cet article n'a fait qu'effleurer la surface de ce que vous pouvez faire avec la vérification aléatoire contrainte. Je recommande de lire la documentation sur la page OSVVM GitHub pour approfondir le sujet.

Je recommande également le cours Advanced VHDL Testbenches and Verification de SynthWorks, auquel je ne suis pas affilié. Cependant, j'ai suivi la version 5 jours de ce cours physique. Le cours est enseigné par Jim Lewis, président du VHDL Analysis and Standardization Group (VASG). Dans l'ensemble, un excellent investissement pour toute entreprise qui souhaite faire passer ses bancs de test VHDL au niveau supérieur.


VHDL

  1. Cadence accélère la vérification SoC d'un milliard de portes
  2. Synopsys permet des conceptions multi-dies avec IP HBM3 et vérification
  3. Contrôle d'accès avec QR, RFID et vérification de la température
  4. Le côté inconfortable, imprévisible et aléatoire de la maintenance
  5. Comment générer des nombres aléatoires en Java
  6. Java 8 - Flux
  7. Contrôle des caractéristiques du tour à banc incliné avec graphiques de vérification
  8. Traitement isométrique différentiel et vérification par simulation de la conception de circuits imprimés à grande vitesse
  9. Le programme de vérification des performances CAGI pour les compresseurs rotatifs