introduction
Cet article présente brièvement les contraintes SystemVerilog.
Lien précédent :
Mon dossier d'apprentissage System Verilog (1)
Mon dossier d'apprentissage System Verilog (2)
Mon dossier d'apprentissage System Verilog (3)
Mon dossier d'apprentissage System Verilog (4)
Mon dossier d'apprentissage System Verilog (5)
Dossier d'apprentissage My System Verilog (6)
Mon dossier d'apprentissage System Verilog (7)
Mon dossier d'apprentissage System Verilog (8)
Mon dossier d'apprentissage System Verilog (9)
Introduction
Qu'est-ce qu'un test direct ?
Un ingénieur de vérification créera d'abord quelque chose appelé un plan de vérification, détaillant chaque fonction de la conception qui doit être testée dans une simulation RTL, et comment chaque test créera un scénario indépendant pour cette fonction spécifique.
Par exemple, s'il existe un périphérique qui doit configurer ses registres afin de démarrer une transaction de bus AXI, nous utiliserons différents tests pour configurer ces registres différemment et obtenir une bonne couverture.
Ce sont des tests simples, chacun effectuant une tâche spécifique pour accomplir quelque chose.
Qu'est-ce qu'un test aléatoire ?
Les conceptions complexes avec de nombreux scénarios et de nombreux cas particuliers peuvent être mieux vérifiées par des tests aléatoires et nécessitent beaucoup moins d'efforts et de temps. En utilisant le même exemple ci-dessus, chaque fois que le test est exécuté avec une graine différente, le test configure des registres périphériques avec des valeurs aléatoires, permettant différents scénarios pour chaque exécution. Cela garantira que nous touchons les cas du coin et attrapons tous les bogues cachés.
contrainte
SystemVerilog permet aux utilisateurs de spécifier des contraintes de manière compacte et déclarative, qui sont ensuite traitées par un solveur interne pour générer des valeurs aléatoires qui satisfont toutes les conditions. Fondamentalement, les contraintes ne sont rien de plus qu'un moyen pour nous de définir les valeurs légales à attribuer à une variable aléatoire. Les variables normales sont déclarées aléatoires par le mot clé rand.
L'exemple ci-dessus déclare une classe nommée pkt avec une contrainte sur son champ d'adresse. Notez que ses deux propriétés sont précédées du mot-clé rand, qui indique au solveur que ces variables doivent être randomisées lorsqu'elles sont demandées. La contrainte est appelée addr Limit et elle spécifie que le solveur peut affecter n'importe quelle valeur aléatoire aux adresses inférieures ou égales à 8'hB. Étant donné que la variable 8 bits addr est de type bit, elle peut avoir n'importe quelle valeur de 0 à 255, mais les valeurs valides seront limitées à 11 si elles sont contraintes.
Comme vous pouvez le voir, cette fonctionnalité puissante nous permettra de créer des variables contraintes dans une plage valide pour la conception et se traduira par une meilleure validation. Dans les prochains chapitres, nous verrons comment utiliser efficacement différentes constructions dans SystemVerilog qui nous permettent de mieux décrire les contraintes.
Variables aléatoires
Les variables sont des variables aléatoires déclarées avec les mots clés rand ou randc. Ils peuvent être utilisés dans des variables ordinaires, des tableaux, des tableaux dynamiques ou des files d'attente.
rand
Regardons une classe simple qui a une variable de 3 bits appelée data qui est randomisée 10 fois. La fonction randomize() est appelée dans le cadre d'un objet de classe pour randomiser toutes les variables de type rand dans l'objet de classe.
Une variable déclarée aléatoire est une variable aléatoire standard dont les valeurs sont uniformément réparties sur sa plage. Par exemple, les données variables dans l'extrait de code ci-dessus sont un entier non signé de 8 bits avec une plage de 0 à 255. Si la variable est randomisée sans aucune contrainte, toute valeur comprise dans la plage sera attribuée à la variable avec une probabilité égale. Lors de tentatives de randomisation successives, cette variable peut se retrouver avec la même valeur, mais avec une probabilité de 1/256.
ranc
Une variable déclarée comme randc est bouclée de manière aléatoire, parcourant ainsi toutes les valeurs de sa plage avant de répéter une valeur particulière. Par exemple, les données variables dans l'extrait ci-dessus ont une plage de 0 à 3. Si la variable est randomisée sans aucune contrainte, toute valeur comprise dans la plage sera attribuée à la variable, mais pour les randomisations successives, la même valeur ne se répétera pas tant que toutes les valeurs n'auront pas été attribuées au moins une fois. les variables randc sont résolues avant les autres variables aléatoires. Nous utiliserons le même exemple ci-dessus, mais avec une légère différence - avec les données comme variables randc.
Notez que toutes les valeurs dans la plage 0-3'h7 sont épuisées avant qu'une valeur ne soit répétée.
Bloc de contrainte
Les blocs de contraintes sont des membres de classe, tout comme les variables, les fonctions et les tâches. Ils ont des noms uniques au sein de la classe. Ces blocs d'expression sont souvent utilisés pour contraindre la valeur d'une variable aléatoire à certaines valeurs spécifiées dans un bloc de contrainte.
grammaire
Les expressions répertoriées entre accolades spécifient les conditions que le solveur doit prendre en compte lors de l'attribution de valeurs aléatoires aux variables. Il n'est pas nécessaire d'avoir une contrainte pour chaque variable, ni de limiter un bloc de contrainte à une seule condition relative à une seule variable. Cependant, les contraintes conflictuelles ne peuvent pas être étendues sur plusieurs blocs à moins qu'elles ne soient désactivées à l'aide de la méthode CONSTRAINT_MODE() que nous verrons dans Désactivation des contraintes.
Une contrainte vide n'a aucun effet sur la génération de nombres aléatoires.
En raison de la nature déclarative de ces blocs, il existe certaines restrictions. Notez que les blocs de contrainte sont entourés d'accolades plutôt que par les mots-clés de début et de fin des instructions de programme.
Les contraintes peuvent être placées à l'intérieur ou à l'extérieur de la définition du corps de la classe. Lorsque des contraintes sont définies en dehors du corps d'une classe, elles sont appelées external/constraints et sont accessibles à l'aide de l'opérateur de résolution de portée :: .
Exemple de contrainte intra-classe
// ==== 类内约束示例 ====
class EXP;
randc bit [3:0] mode;
constraint CON_MODE {
mode >5;
mode <16;
mode[0] == 1'b0;
};
endclass
module Constraint1();
EXP exp;
initial
begin
exp = new();
for(int k = 0; k<6;k++)
begin
exp.randomize();
$display("mode = %0d",exp.mode);
end
end
endmodule
Sortie de simulation :
Exemple de contraintes hors classe
Les contraintes externes peuvent être implicites ou explicites. Une erreur se produit si des contraintes explicites sont utilisées et qu'aucun bloc de contrainte correspondant n'est fourni en dehors du corps de la classe. Mais pour les contraintes implicites, aucune erreur ne se produit, mais le simulateur peut émettre un avertissement. (Il y aura des différences entre les différents émulateurs ici, pas nécessairement toutes les erreurs)
// ==== 类内约束示例 ====
class EXP;
randc bit [3:0] mode;
constraint CON_IMPLICIT;//显示声明
extern constraint COM_EXPLICIT;//用关键字 extern 表示隐式声明
endclass
constraint EXP::CON_IMPLICIT {mode >5; };//显式约束在类外若不提供约束块则会报错
constraint EXP::COM_EXPLICIT {mode <15;};//隐式约束在类外若不提供约束块则会警告
module Constraint1();
EXP exp;
initial
begin
exp = new();
for(int k = 0; k<10;k++)
begin
exp.randomize();
$display("mode = %0d",exp.mode);
end
end
endmodule
Résultats de la simulation:
Lorsque les contraintes implicites ne sont pas déclarées en dehors de la classe, Questa Sim signalera un avertissement :
- Fournit plusieurs blocs de contrainte pour un prototype de contrainte donné
- Écrivez un bloc de contrainte avec le même nom que celui utilisé dans la déclaration de prototype externe
randomisation de tableau
La randomisation SystemVerilog fonctionne également avec des structures de données de tableau telles que des tableaux statiques, des tableaux dynamiques et des files d'attente. Les variables doivent être déclarées avec le type rand ou randc pour être randomisées.
tableau statique
La randomisation des tableaux statiques est simple et peut être effectuée comme n'importe quel autre type de variable SystemVerilog.
tableau dynamique
Un tableau dynamique est un tableau dont la taille n'est pas prédéterminée lors de la déclaration du tableau. Ces tableaux peuvent être de taille variable car de nouveaux membres peuvent être ajoutés au tableau à tout moment.
Considérez l'exemple suivant où nous déclarons un tableau dynamique comme indiqué par les crochets vides [ ] de type rand. Une contrainte est définie qui limite la taille du tableau dynamique entre 5 et 8. Une autre définition de contrainte affecte à chaque élément du tableau sa valeur d'index.
La randomisation produit des tableaux vides si la taille n'est pas limitée - fonctionne pour les tableaux dynamiques et les files d'attente.
Notez que la taille du tableau est randomisée à 9 (à partir de la contrainte c_array) et l'élément à chaque index a la valeur de l'index lui-même (à partir de la contrainte c_val)
file d'attente
Méthodes de contrainte courantes
Voyons maintenant quelques façons courantes d'écrire des expressions de contrainte dans des blocs de contrainte.
expressions simples
Il ne peut y avoir qu'un seul opérateur relationnel (> >= < <= ==) dans une expression.
L'affectation ne peut pas être effectuée à l'intérieur d'un bloc de contrainte car il ne contient que des expressions. Au lieu de cela, vous devez utiliser l'opérateur d'équivalence ==, comme indiqué dans la contrainte nommée my_min dans l'exemple ci-dessus, où min obtiendra la valeur 16 et toutes les autres variables seront randomisées. C'est un moyen de fixer certaines valeurs aux variables, même si le solveur essaie de les randomiser.
Une expression légèrement plus complexe peut également être utilisée, comme indiqué ci-dessus, où min représente la variable stockant la température en Fahrenheit, et low représente la variable dans l'objet de classe Temp, qui contient la température en degrés Celsius.
class RAND_CLASS;
randc bit [7:0] MIN_VALUE,MAX_VALUE,TYPICAL_VALUE,FIXED_VALUE;
constraint RAND_CON{
MIN_VALUE >= 10;
TYPICAL_VALUE > 20; TYPICAL_VALUE < 100;
MAX_VALUE < 200;
}
constraint FIXED_CON{
FIXED_VALUE == 128;
}
function string display();
return $sformatf("MIN_VALUE=%0d MAX_VALUE=%0d TYPICAL_VALUE=%0d FIXED_VALUE=%0d ",MIN_VALUE,MAX_VALUE,TYPICAL_VALUE,FIXED_VALUE);
endfunction
endclass
module TEST_CON();
initial
begin
static RAND_CLASS RAND_CLASS_OBJ = new();
for(int k = 0;k<100;k++)
begin
// RAND_CLASS RAND_CLASS_OBJ = new();
RAND_CLASS_OBJ.randomize();
$display("k = %0d %s",k,RAND_CLASS_OBJ.display());
end
end
endmodule
opérateur intérieur
Les limites inférieure et supérieure peuvent être spécifiées à l'aide des opérateurs intégrés comme alternative aux expressions présentées ci-dessous.
Notez que la structure intérieure comprend une borne inférieure et une borne supérieure. SystemVerilog collecte toutes les valeurs et choisit entre elles avec une probabilité égale, sauf s'il existe d'autres contraintes sur les variables.
opérateur non interne
Si vous voulez une valeur en dehors d'une certaine plage , une contrainte inversée peut être écrite comme indiqué ci-dessous. Notez que la randomisation répétée donne toutes les valeurs sauf celles comprises entre 3 et 6.
distribution pondérée
L'opérateur dist permet de créer des distributions pondérées afin que certaines valeurs soient choisies plus souvent que d'autres. L'opérateur := spécifie que chaque valeur spécifiée dans la plage doit être pondérée de manière égale, tandis que l'opérateur :/ spécifie une distribution égale de poids entre toutes les valeurs.
:= opérateur
Dans la contrainte ci-dessus, 0 a un poids de 20, 6 est 40, 7 est 10, 1 à 5 est 50, pour un total de 320. Par conséquent, la probabilité de choisir 0 est de 20/320 et la probabilité de choisir une valeur entre 1 et 5 est de 50/320. Prenons un exemple simple.
:/ opérateur
Dans dist2, 0 a un poids de 20, 6 est 10, 7 est 20, et la somme des poids de 1 à 5 est 50, donc 10 chacun. Par conséquent, la probabilité de choisir 0 est de 20/100 et la probabilité de choisir une valeur entre 1 et 5 est de 10/100. Prenons un exemple simple.
contrainte à double sens
Les blocs contraints ne sont pas exécutés de haut en bas comme le code procédural, mais sont actifs en même temps. Regardons cela avec un autre exemple.
à l'intérieur
Le mot-clé inside dans SystemVerilog permet de vérifier si une valeur donnée se situe dans la plage spécifiée à l'aide de la phrase inside. En plus d'être utilisé comme contrainte, il peut également être utilisé dans IF et d'autres instructions conditionnelles.
grammaire
Exemple:
utilisé dans les instructions conditionnelles
Dans l'exemple suivant, l'opérateur intérieur est utilisé à la fois dans l'instruction If else et dans l'opérateur ternaire. La valeur de flag est 1 si la valeur randomisée de m_data est comprise entre 4 et 9 inclus, sinon, la valeur de flag est 0.
module TEST_INSIDE ();
bit [3:0] m_data;
bit flag;
initial
begin
for(int i=0;i<10;i++)
begin
m_data = $random();
flag = m_data inside {[5:10]} ? 1:0;
if(m_data inside {[5:10]})
$display("m_data = %0d inside [5:10],flag = %0d",m_data,flag);
else
$display("m_data = %0d outside [5:10],flag = %0d",m_data,flag);
end
end
endmodule
Résultats de la simulation:
utilisé dans les contraintes
Les opérateurs intrinsèques sont très utiles dans les contraintes, rendant le code plus court et plus lisible.
non-intérieur
L'opposé de ce que fait l'opérateur intérieur peut être fait en le faisant précéder d'un symbole non-opérateur !. Cela s'applique à la fois aux instructions de contrainte et aux instructions conditionnelles. L'exemple ci-dessous est le même que ce que nous avons vu précédemment, sauf que ses contraintes ont été ajustées pour refléter l'instruction Inside inversée.
exemple
Supposons que nous ayons une mémoire située entre la plage d'adresses 0x4000 et 0x5FFF, qui est divisée en deux. La première partie est utilisée pour stocker des instructions et la seconde partie est utilisée pour stocker des données. Supposons que nous voulions randomiser l'adresse des données afin qu'elles tombent dans la section de données de la mémoire, nous pouvons facilement utiliser l'opérateur intérieur.
Contrainte d'implication
SV introduit deux structures pour déclarer des relations conditionnelles :
Notez que mode n'est pas nécessairement 2 pour toutes les valeurs de len supérieures à 10. Cependant, la contrainte est que si mode vaut 2, len doit être supérieur à 10.
exemple
Opérateur d'association (opérateur d'implication)
L'opérateur d'association -> peut être utilisé dans les expressions de contrainte pour montrer une relation conditionnelle entre deux variables. Si l'expression à gauche de -> est vraie, alors l'expression de contrainte à droite doit être satisfaite ; sinon, vice versa.
exemple
class RAND_CLASS;
rand bit [3:0] mode;
rand bit mode_val;
constraint COM_MODE {
mode inside {[1:4]} -> mode_val == 1;
}
endclass
module TEST_CON();
int k = 0;
initial
begin
RAND_CLASS RAND_CLASS_OBJ = new;
repeat(10)
begin
k++;
RAND_CLASS_OBJ.randomize();
$display("k = %0d mode = %0d mode_val = %0d",k,RAND_CLASS_OBJ.mode,RAND_CLASS_OBJ.mode_val);
end
end
endmodule
Résultats de la simulation:
contraintes if-else
Les contraintes if-else autorisent l'imbrication. Lorsque le nombre d'instructions de branchement est supérieur à 1, des accolades {} sont utilisées, ce qui est très similaire au langage C. Regardez un exemple :
class RAND_CLASS;
rand bit [3:0] mode;
rand bit mode_val;
constraint COM_MODE {
// mode inside {[1:4]} -> mode_val == 1;
if(mode inside {[1:4]})
mode_val == 1;
else
{
if(mode == 10)
mode_val == 1;
else
mode_val == 0;
}
}
endclass
module TEST_CON();
int k = 0;
initial
begin
RAND_CLASS RAND_CLASS_OBJ = new;
repeat(10)
begin
k++;
RAND_CLASS_OBJ.randomize();
$display("k = %0d mode = %0d mode_val = %0d",k,RAND_CLASS_OBJ.mode,RAND_CLASS_OBJ.mode_val);
end
end
endmodule
Résultats de la simulation:
foreach contrainte
Les contraintes foreach sont généralement utilisées pour contraindre les tableaux ;
Regardons d'abord un petit exemple :
class RAND_CLASS;
rand bit [3:0] RAND_ARRAY[16];
constraint CON_ARRAY {
foreach(RAND_ARRAY[i])
{
RAND_ARRAY[i] == 15-i;
}
}
endclass
module TEST_CON();
initial
begin
RAND_CLASS RAND_CLASS_OBJ = new;
repeat(5)
begin
RAND_CLASS_OBJ.randomize();
$display("RAND_ARRAY = %p",RAND_CLASS_OBJ.RAND_ARRAY);
end
end
endmodule
Résultats de la simulation:
tableau/file d'attente dynamique
Étant donné que la taille du tableau/de la file d'attente dynamique est inconnue lors de sa déclaration, il ne peut pas utiliser foreach directement. Par conséquent, sa taille de tableau doit également être limitée. Exemple:
class RAND_CLASS;
// 动态数组/队列
rand bit [3:0] RAND_ARRAY [];
rand bit [3:0] RAND_QUENE [$];
// 队列约束
constraint CON_QUENE {
RAND_QUENE.size() == 10;
foreach(RAND_QUENE[k])
{
RAND_QUENE[k] == 10-k;
}
}
// 动态数组约束
constraint CON_ARRAY {
foreach(RAND_ARRAY[k])
{
RAND_ARRAY[k] == k;
}
}
function new ();
RAND_ARRAY = new[5]; // 构造函数声明规格
endfunction
endclass
module TEST_CON();
initial
begin
RAND_CLASS RAND_CLASS_OBJ = new;
RAND_CLASS_OBJ.randomize();
$display("RAND_ARRAY = %p \nRAND_ARRAY = %p",RAND_CLASS_OBJ.RAND_ARRAY,RAND_CLASS_OBJ.RAND_QUENE);
end
endmodule
simulation:
Tableaux multidimensionnels
Les contraintes SystemVerilog sont suffisamment puissantes pour s'appliquer également aux tableaux multidimensionnels. Dans l'exemple ci-dessous, nous avons un tableau statique multidimensionnel avec une structure condensée.
Exemple:
class RAND_CLASS;
// 多维数据定义
rand bit [3:0] [3:0] RAND_MD_ARRAY [4][4];
// 多维数据约束
constraint CON_MD_ARRAY {
foreach(RAND_MD_ARRAY[i])
{
foreach(RAND_MD_ARRAY[i][j])
{
foreach(RAND_MD_ARRAY[i][j][k])
{
RAND_MD_ARRAY[i][j][k] == k+i+j;
}
}
}
}
endclass
module TEST_CON();
initial
begin
RAND_CLASS RAND_CLASS_OBJ = new;
RAND_CLASS_OBJ.randomize();
for(int i=0; i<4; i++)
begin
for(int j=0; j<4; j++)
begin
$display("RAND_MD_ARRAY[%0d][%0d] = %p ",i,j,RAND_CLASS_OBJ.RAND_MD_ARRAY[i][j]);
end
end
end
endmodule
Résultats de la simulation:
tableau dynamique multidimensionnel
Contraindre les tableaux dynamiques multidimensionnels est délicat et peut ne pas être pris en charge par tous les émulateurs. (Questa Sim, 64 bits, la version 2017 ne prend pas en charge) Dans l'exemple ci-dessous, la taille de l'élément XorY du tableau 2D Md array est inconnue.
Contraintes d'itération de réduction de tableau
Il s'agit d'une autre construction et technique très utile prise en charge par SV.
La méthode de réduction de tableau produit une valeur unique à partir d'un tableau décompressé de valeurs entières int. Cela peut être utilisé dans une contrainte pour permettre à l'expression d'être prise en compte lors de la randomisation.
Par exemple, considérons un tableau de N éléments qui doivent être randomisés de sorte que la somme de tous les éléments soit égale à une certaine valeur. L'opérateur de réduction de tableau peut être utilisé avec une clause with afin qu'il itère sur chaque élément du tableau et l'inclue dans le solveur de contraintes.
résoudre avant
Par défaut, le solveur contraint de SV essaie de donner une distribution uniforme de valeurs aléatoires. Par conséquent, toute valeur légale a la même probabilité d'être une solution pour une contrainte donnée.
Mais l'utilisation de résoudre avant peut modifier la distribution des probabilités de sorte que certains cas peuvent être forcés d'être choisis plus souvent que d'autres. Nous verrons l'effet de la résolution en comparant un exemple avec et sans cette construction.
Exemple de distribution aléatoire
Par exemple, considérons l'exemple suivant, où une variable aléatoire b de 3 bits peut avoir 8 valeurs légales, et la valeur 0 a la même probabilité que toutes les autres valeurs possibles.
Notez que même s'il y a des valeurs en double dans la sortie de simulation ci-dessous, cela signifie simplement que la randomisation précédente n'a aucun effet sur l'itération actuelle. Ainsi, le générateur aléatoire est libre de choisir l'une des 8 valeurs, quelle que soit la valeur précédente.
pas de résolution avant
Prenons l'exemple suivant, où deux variables aléatoires sont déclarées. La contrainte garantit que b obtient 0x3 chaque fois que a vaut 1.
Lorsque a vaut 0, b peut prendre n'importe laquelle de ces 4 valeurs. Voici donc 4 combinaisons. Ensuite, lorsque a vaut 1, b ne peut prendre qu'une seule valeur, il n'y a donc qu'une seule combinaison.
Il y a donc 5 combinaisons possibles, et si le solveur de contraintes doit attribuer à chaque combinaison une probabilité égale, alors la probabilité d'en choisir une est de 1/5.
Le tableau suivant répertorie la probabilité de chaque combinaison de a et b.
Il y a résoudre avant
SystemVerilog permet à un mécanisme d'ordonner les variables afin qu'elles puissent être sélectionnées indépendamment de b. Cela se fait à l'aide du mot-clé solve.
Puisque a est résolu en premier, la probabilité de choisir 0 ou 1 est de 50 %. Ensuite, la probabilité de choisir une valeur pour b dépend de la valeur choisie pour a.
Notez que la probabilité de b est presque de 0 % avant et après l'utilisation de résoudre, elle est passée à un peu plus de 50 %.
restrictions d'utilisation
les variables randc ne sont pas autorisées, car elles sont toujours résolues en premier
Les variables doivent être des valeurs entières int Il
ne doit pas y avoir de dépendances circulaires dans le tri, telles que résoudre a avant b et résoudre b avant a utilisés en même temps.
contraintes statiques
Les contraintes statiques sont partagées entre toutes les instances de la classe. Déclarez avec le mot clé static.
Les contraintes ne sont affectées par le mot-clé static que lorsqu'elles sont activées et désactivées à l'aide de la méthode Constraint_mode(). Lorsqu'une contrainte non statique est désactivée à l'aide de cette méthode, la contrainte est désactivée dans l'instance spécifique de la classe sur laquelle la méthode est appelée. Cependant, lorsqu'une contrainte statique est désactivée et activée à l'aide de cette méthode, la contrainte est désactivée et activée dans toutes les instances de la classe.
grammaire
Ensuite, nous comparerons les contraintes non statiques et statiques pour voir en quoi elles diffèrent.
contraintes non statiques
Par défaut, il s'agit d'une contrainte non statique, qui est copiée dans chaque instance de la classe. Exemple:
contraintes statiques
Désactiver les contraintes non statiques
Fermez les contraintes non statiques correspondant à c1, les résultats de la simulation :
Désactiver les contraintes statiques
Lorsque les contraintes statiques sont désactivées, les deux instanciations de la classe sont affectées.
Exemple de partition de mémoire
Considérez les exemples pratiques suivants que vous rencontrez généralement dans des projets réels.
randomisation des blocs de mémoire
Supposons que nous ayons une SRAM de 2 Ko dans notre conception pour stocker certaines données, supposons que nous ayons besoin de trouver un bloc d'adresse dans l'espace RAM de 2 Ko qui peut être utilisé à des fins spécifiques.
exemple
// ========================================================
// | 说明:内存分区示例
// |
// |
// ========================================================
// 类定义
class MEMORY_BLOCK;
// 数据字段声明
bit [31:0] MEM_RAM_STR_ADDR;//内存RAM的起始地址
bit [31:0] MEM_RAM_END_ADDR;//内存RAM的结束地址
rand bit [31:0] BLOCK_STR_ADDR;//指向块内存的起始地址
rand bit [31:0] BLOCK_END_ADDR;//指向块内存的结束地址
rand int BLOCK_SIZE;//块内存空间大小 单位 B
// 约束声明
constraint CON_ADDR {
BLOCK_STR_ADDR >= MEM_RAM_STR_ADDR;
BLOCK_STR_ADDR < MEM_RAM_END_ADDR;
BLOCK_STR_ADDR % 4 == 0;//块起始地址 4字节对齐
BLOCK_END_ADDR == BLOCK_STR_ADDR + BLOCK_SIZE - 1;
};
constraint CON_BLOCK_SIZE {
BLOCK_SIZE inside {128,256,512};
};
// 函数定义
function void display_mem_part_detail();
$display("==== ==== ==== Memory Block Detail ==== ==== ====");
$display("RAM START ADDR = 0x%0h",MEM_RAM_STR_ADDR);
$display("RAM END ADDR = 0x%0h",MEM_RAM_END_ADDR);
$display("BLOCK START ADDR = 0x%0h",BLOCK_STR_ADDR);
$display("BLOCK END ADDR = 0x%0h",BLOCK_END_ADDR);
$display("BLOCK SIZE = 0x%0h",BLOCK_SIZE);
$display("==== ==== ==== ==== ==== ==== ==== ==== ==== ==== =");
endfunction
endclass
// Test Bench
module MEM_PART_EXP ();
MEMORY_BLOCK MEMORY_BLOCK_OBJ = new;
initial
begin
MEMORY_BLOCK_OBJ.MEM_RAM_STR_ADDR = 32'h0;
MEMORY_BLOCK_OBJ.MEM_RAM_END_ADDR = 32'hFFF;//4kB RAM
MEMORY_BLOCK_OBJ.randomize();
MEMORY_BLOCK_OBJ.display_mem_part_detail();
end
endmodule
simulation:
Diviser le bloc de mémoire uniformément
// ========================================================
// | 说明:内存分区示例
// |
// |
// ========================================================
// 类定义
class MEMORY_BLOCK;
// 数据字段声明
bit [31:0] MEM_RAM_STR_ADDR;//内存RAM的起始地址
bit [31:0] MEM_RAM_END_ADDR;//内存RAM的结束地址
rand int MEM_PART_NUM;//内存分块个数
rand bit [31:0] PART_STR_ADDR [];//指向块内存的起始地址
rand int MEM_PART_SIZE;//块内存空间大小 单位 B
// 约束声明
constraint CON_PART_NUM {
MEM_PART_NUM >= 4;
MEM_PART_NUM < 8;
};
constraint CON_PART_SIZE {
MEM_PART_SIZE == (MEM_RAM_END_ADDR - MEM_RAM_STR_ADDR)/MEM_PART_NUM;
};
constraint CON_PART {
PART_STR_ADDR.size() == MEM_PART_NUM;
foreach (PART_STR_ADDR[i])
{
if(i)
PART_STR_ADDR[i] == PART_STR_ADDR[i-1] + MEM_PART_SIZE;
else
PART_STR_ADDR[i] == MEM_RAM_STR_ADDR;
}
};
// 函数定义
function void display_mem_part_detail();
$display("==== ==== ==== Memory Block Details ==== ==== ====");
$display("RAM START ADDR = 0x%0h",MEM_RAM_STR_ADDR);
$display("RAM END ADDR = 0x%0h",MEM_RAM_END_ADDR);
$display("Memory Partitions= %0d",MEM_PART_NUM);
$display("Partitions Size = %0d",MEM_PART_SIZE);
$display("==== ==== ==== ==== ==== ==== ==== ==== ==== ==== =");
$display("Partition Details:");
foreach(PART_STR_ADDR[i])
$display("Part %0d : Strat Addr = 0x%0h",i,PART_STR_ADDR[i]);
endfunction
endclass
// Test Bench
module MEM_PART_EXP ();
MEMORY_BLOCK MEMORY_BLOCK_OBJ = new;
initial
begin
MEMORY_BLOCK_OBJ.MEM_RAM_STR_ADDR = 32'h0;
MEMORY_BLOCK_OBJ.MEM_RAM_END_ADDR = 32'hFFF;//4kB RAM
MEMORY_BLOCK_OBJ.randomize();
MEMORY_BLOCK_OBJ.display_mem_part_detail();
end
endmodule
simulation:
partitionnement non uniforme de la mémoire
Exemple:
// ========================================================
// | 说明:内存分区示例
// |
// |
// ========================================================
// 类定义
class MEMORY_BLOCK;
// 数据字段声明
bit [31:0] MEM_RAM_STR_ADDR;//内存RAM的起始地址
bit [31:0] MEM_RAM_END_ADDR;//内存RAM的结束地址
rand int MEM_PART_NUM; //内存分块个数
rand bit [31:0] PART_STR_ADDR [];//指向块内存的起始地址
rand int MEM_PART_SIZE [];//块内存空间大小 单位 B
// 约束声明
constraint CON_PART_NUM {
MEM_PART_NUM >= 4;
MEM_PART_NUM < 8;
};
constraint CON_PART_SIZE {
MEM_PART_SIZE.size() == MEM_PART_NUM;
MEM_PART_SIZE.sum() == MEM_RAM_END_ADDR - MEM_RAM_STR_ADDR + 1;
foreach (MEM_PART_SIZE[i])
MEM_PART_SIZE[i] inside {16,32,64,128,256,512,1024};
};
constraint CON_PART {
PART_STR_ADDR.size() == MEM_PART_NUM;
foreach (PART_STR_ADDR[i])
{
if(i)
PART_STR_ADDR[i] == PART_STR_ADDR[i-1] + MEM_PART_SIZE[i-1];
else
PART_STR_ADDR[i] == MEM_RAM_STR_ADDR;
}
};
// 函数定义
function void display_mem_part_detail();
$display("==== ==== ==== Memory Block Details ==== ==== ====");
$display("RAM START ADDR = 0x%0h",MEM_RAM_STR_ADDR);
$display("RAM END ADDR = 0x%0h",MEM_RAM_END_ADDR);
$display("Memory Partitions= %0d",MEM_PART_NUM);
$display("==== ==== ==== ==== ==== ==== ==== ==== ==== ==== =");
$display("Partition Details:");
foreach(PART_STR_ADDR[i])
$display("Part %0d : Strat Addr = 0x%0h Size = %0d Bytes",i,PART_STR_ADDR[i],MEM_PART_SIZE[i]);
endfunction
endclass
// Test Bench
module MEM_PART_EXP ();
MEMORY_BLOCK MEMORY_BLOCK_OBJ = new;
initial
begin
MEMORY_BLOCK_OBJ.MEM_RAM_STR_ADDR = 32'h0;
MEMORY_BLOCK_OBJ.MEM_RAM_END_ADDR = 32'hFFF;//4kB RAM
MEMORY_BLOCK_OBJ.randomize();
MEMORY_BLOCK_OBJ.display_mem_part_detail();
end
endmodule
simulation:
Partitionnement non uniforme de la mémoire (avec bande interdite d'isolement)
// ========================================================
// | 说明:内存分区示例
// |
// |
// ========================================================
// 类定义
class MEMORY_BLOCK;
// 数据字段声明
bit [31:0] MEM_RAM_STR_ADDR;//内存RAM的起始地址
bit [31:0] MEM_RAM_END_ADDR;//内存RAM的结束地址
rand int MEM_PART_NUM; //内存分块个数
rand bit [31:0] PART_STR_ADDR [];//指向块内存的起始地址
rand int MEM_PART_SIZE [];//块内存空间大小 单位 B
rand int MEM_SPACE [];//内存块之间的空隙 单位 B
// 约束声明
constraint CON_PART_NUM {
MEM_PART_NUM >= 16;
MEM_PART_NUM <= 32;
};
constraint CON_PART_SIZE {
MEM_PART_SIZE.size() == MEM_PART_NUM;
MEM_SPACE.size() == MEM_PART_NUM - 1;
MEM_PART_SIZE.sum() + MEM_SPACE.sum() == MEM_RAM_END_ADDR - MEM_RAM_STR_ADDR + 1;
foreach (MEM_PART_SIZE[i])
{
MEM_PART_SIZE[i] inside {16,32,64,128,256,512,1024};
if(i < MEM_SPACE.size())
MEM_SPACE[i] inside {0,2,4,8,16,32,64};
}
};
constraint CON_PART {
PART_STR_ADDR.size() == MEM_PART_NUM;
foreach (PART_STR_ADDR[i])
{
if(i)
PART_STR_ADDR[i] == PART_STR_ADDR[i-1] + MEM_PART_SIZE[i-1] + MEM_SPACE[i-1];
else
PART_STR_ADDR[i] == MEM_RAM_STR_ADDR;
}
};
// 函数定义
function void display_mem_part_detail();
$display("==== ==== ==== Memory Block Details ==== ==== ====");
$display("RAM START ADDR = 0x%0h",MEM_RAM_STR_ADDR);
$display("RAM END ADDR = 0x%0h",MEM_RAM_END_ADDR);
$display("Memory Partitions= %0d",MEM_PART_NUM);
$display("==== ==== ==== ==== ==== ==== ==== ==== ==== ==== =");
$display("Partition Details:");
foreach(PART_STR_ADDR[i])
$display("Part %0d : Strat Addr = 0x%0h Size = %0d Bytes Space = %0d Bytes",i,PART_STR_ADDR[i],MEM_PART_SIZE[i],MEM_SPACE[i]);
endfunction
endclass
// Test Bench
module MEM_PART_EXP ();
MEMORY_BLOCK MEMORY_BLOCK_OBJ = new;
initial
begin
MEMORY_BLOCK_OBJ.MEM_RAM_STR_ADDR = 32'h0;
MEMORY_BLOCK_OBJ.MEM_RAM_END_ADDR = 32'h3FFF;//16kB RAM
MEMORY_BLOCK_OBJ.randomize();
MEMORY_BLOCK_OBJ.display_mem_part_detail();
end
endmodule
simulation:
partitions pour le programme et les données
Dans cet exemple, la mémoire est divisée en régions pour le programme, les données et l'espace vide. Un tableau dynamique est utilisé pour stocker la taille de chaque programme, les données et l'espace vide. En utilisant les contraintes de SV, la taille totale de toutes les régions correspond à l'espace RAM total.
Le code ci-dessous randomise le nombre total de régions de programme, de données et d'espace. Ce nombre affecte également la taille de chaque programme, données et espace, de sorte que la somme de tous les programmes, données et espace vide doit être égale à l'espace RAM total.
Exemple:
typedef struct{
int STR_ADDR;
int END_ADDR;
}S_RAM_ADDR;
class RAM_SPACE;
// 变量定义
rand int PROGRAM_NUM ;//程序内存块的数目
rand int DATA_NUM ;//数据内存块的数目
rand int SPACE_NUM ;//分隔内存块的数目
rand int MAX_PROGRAM_NUM;//程序内存块的最大个数
rand int MAX_DATA_NUM ;//程序数据块的最大个数
rand int MAX_SPACE_NUM ;//SPACE的最大个数
rand int PAOGRAM_SIZE[]; //每个程序内存块的尺寸 单位:字节
rand int DATA_SIZE[] ; //每个程序内存块的尺寸 单位:字节
rand int SPACE_SIZE[] ; //每个程序内存块的尺寸 单位:字节
S_RAM_ADDR S_RAM_ADDR_;//S_RAM_ADDR 结构体变量
// 约束管理
constraint C_MAX{
MAX_PROGRAM_NUM == 100;
MAX_DATA_NUM == 100;
MAX_SPACE_NUM == 40;
};
constraint C_NUM{
PROGRAM_NUM inside{[1:MAX_PROGRAM_NUM]};
DATA_NUM inside{[1:MAX_DATA_NUM]};
SPACE_NUM inside{[1:MAX_SPACE_NUM]};
};
constraint C_SIZE{
PAOGRAM_SIZE.size() == PROGRAM_NUM;
DATA_SIZE.size() == DATA_NUM;
SPACE_SIZE.size() == SPACE_NUM;
};
constraint C_RAM{
foreach(PAOGRAM_SIZE[i])
{
PAOGRAM_SIZE[i] inside {4,8,32,64,128,512};
PAOGRAM_SIZE[i] dist {[4:8]:/10,[32:64]:/20,[128:512]:/75};
PAOGRAM_SIZE[i] % 4 == 0;
}
foreach(DATA_SIZE[i])
{
DATA_SIZE[i] inside {64,128,512,1024};
DATA_SIZE[i] dist{64:=10,128:=20,512:=60,1024:=10};
}
foreach(SPACE_SIZE[i])
{
SPACE_SIZE[i] inside {4,8,32,64,128,512,1024};
DATA_SIZE[i] dist{4:=5,8:=5,32:=5,64:=10,128:=40,512:=60,1024:=10};
}
S_RAM_ADDR_.END_ADDR-S_RAM_ADDR_.STR_ADDR+1 == PAOGRAM_SIZE.sum()+DATA_SIZE.sum()+SPACE_SIZE.sum();
};
// 函数定义
function void display();
$display("========== ========== RAM SPACE DISTRIBUTION DETAILS ========== ==========");
$display("PROGRAM_NUM = %0d , DATA_NUM = %0d , SPACE_NUM = %0d",PROGRAM_NUM,DATA_NUM,SPACE_NUM);
foreach(PAOGRAM_SIZE[i])
$display("PAOGRAM_SIZE[%0d] = %0d Bytes",i,PAOGRAM_SIZE[i]);
foreach(DATA_SIZE[i])
$display("DATA_SIZE[%0d] = %0d Bytes",i,DATA_SIZE[i]);
foreach(SPACE_SIZE[i])
$display("SPACE_SIZE[%0d] = %0d Bytes",i,SPACE_SIZE[i]);
$display("========== ========== ========== ========== ======== ========== ==========");
endfunction
endclass
module TB_CON();
RAM_SPACE OBJ = new();
initial
begin
OBJ.S_RAM_ADDR_.STR_ADDR = 32'd0;
OBJ.S_RAM_ADDR_.END_ADDR = 16*1024-1;
assert(OBJ.randomize());
OBJ.display();
assert(OBJ.randomize());
OBJ.display();
end
endmodule
Résultats de la simulation:
# ========== ========== RAM SPACE DISTRIBUTION DETAILS ========== ==========
# PROGRAM_NUM = 78 , DATA_NUM = 53 , SPACE_NUM = 32
# PAOGRAM_SIZE[0] = 4 Bytes
# PAOGRAM_SIZE[1] = 128 Bytes
# PAOGRAM_SIZE[2] = 4 Bytes
# PAOGRAM_SIZE[3] = 128 Bytes
# PAOGRAM_SIZE[4] = 8 Bytes
# PAOGRAM_SIZE[5] = 128 Bytes
# PAOGRAM_SIZE[6] = 4 Bytes
# PAOGRAM_SIZE[7] = 4 Bytes
# PAOGRAM_SIZE[8] = 4 Bytes
# PAOGRAM_SIZE[9] = 512 Bytes
# PAOGRAM_SIZE[10] = 512 Bytes
# PAOGRAM_SIZE[11] = 512 Bytes
# PAOGRAM_SIZE[12] = 512 Bytes
# PAOGRAM_SIZE[13] = 32 Bytes
# PAOGRAM_SIZE[14] = 4 Bytes
# PAOGRAM_SIZE[15] = 128 Bytes
# PAOGRAM_SIZE[16] = 128 Bytes
# PAOGRAM_SIZE[17] = 32 Bytes
# PAOGRAM_SIZE[18] = 4 Bytes
# PAOGRAM_SIZE[19] = 4 Bytes
# PAOGRAM_SIZE[20] = 128 Bytes
# PAOGRAM_SIZE[21] = 4 Bytes
# PAOGRAM_SIZE[22] = 4 Bytes
# PAOGRAM_SIZE[23] = 4 Bytes
# PAOGRAM_SIZE[24] = 4 Bytes
# PAOGRAM_SIZE[25] = 4 Bytes
# PAOGRAM_SIZE[26] = 4 Bytes
# PAOGRAM_SIZE[27] = 512 Bytes
# PAOGRAM_SIZE[28] = 32 Bytes
# PAOGRAM_SIZE[29] = 4 Bytes
# PAOGRAM_SIZE[30] = 4 Bytes
# PAOGRAM_SIZE[31] = 128 Bytes
# PAOGRAM_SIZE[32] = 4 Bytes
# PAOGRAM_SIZE[33] = 4 Bytes
# PAOGRAM_SIZE[34] = 4 Bytes
# PAOGRAM_SIZE[35] = 4 Bytes
# PAOGRAM_SIZE[36] = 8 Bytes
# PAOGRAM_SIZE[37] = 4 Bytes
# PAOGRAM_SIZE[38] = 4 Bytes
# PAOGRAM_SIZE[39] = 4 Bytes
# PAOGRAM_SIZE[40] = 4 Bytes
# PAOGRAM_SIZE[41] = 4 Bytes
# PAOGRAM_SIZE[42] = 4 Bytes
# PAOGRAM_SIZE[43] = 4 Bytes
# PAOGRAM_SIZE[44] = 32 Bytes
# PAOGRAM_SIZE[45] = 32 Bytes
# PAOGRAM_SIZE[46] = 4 Bytes
# PAOGRAM_SIZE[47] = 512 Bytes
# PAOGRAM_SIZE[48] = 4 Bytes
# PAOGRAM_SIZE[49] = 4 Bytes
# PAOGRAM_SIZE[50] = 4 Bytes
# PAOGRAM_SIZE[51] = 4 Bytes
# PAOGRAM_SIZE[52] = 4 Bytes
# PAOGRAM_SIZE[53] = 4 Bytes
# PAOGRAM_SIZE[54] = 512 Bytes
# PAOGRAM_SIZE[55] = 512 Bytes
# PAOGRAM_SIZE[56] = 4 Bytes
# PAOGRAM_SIZE[57] = 4 Bytes
# PAOGRAM_SIZE[58] = 4 Bytes
# PAOGRAM_SIZE[59] = 4 Bytes
# PAOGRAM_SIZE[60] = 128 Bytes
# PAOGRAM_SIZE[61] = 128 Bytes
# PAOGRAM_SIZE[62] = 4 Bytes
# PAOGRAM_SIZE[63] = 4 Bytes
# PAOGRAM_SIZE[64] = 4 Bytes
# PAOGRAM_SIZE[65] = 4 Bytes
# PAOGRAM_SIZE[66] = 4 Bytes
# PAOGRAM_SIZE[67] = 4 Bytes
# PAOGRAM_SIZE[68] = 4 Bytes
# PAOGRAM_SIZE[69] = 4 Bytes
# PAOGRAM_SIZE[70] = 4 Bytes
# PAOGRAM_SIZE[71] = 4 Bytes
# PAOGRAM_SIZE[72] = 4 Bytes
# PAOGRAM_SIZE[73] = 4 Bytes
# PAOGRAM_SIZE[74] = 4 Bytes
# PAOGRAM_SIZE[75] = 128 Bytes
# PAOGRAM_SIZE[76] = 512 Bytes
# PAOGRAM_SIZE[77] = 4 Bytes
# DATA_SIZE[0] = 64 Bytes
# DATA_SIZE[1] = 512 Bytes
# DATA_SIZE[2] = 64 Bytes
# DATA_SIZE[3] = 64 Bytes
# DATA_SIZE[4] = 64 Bytes
# DATA_SIZE[5] = 64 Bytes
# DATA_SIZE[6] = 64 Bytes
# DATA_SIZE[7] = 512 Bytes
# DATA_SIZE[8] = 64 Bytes
# DATA_SIZE[9] = 64 Bytes
# DATA_SIZE[10] = 64 Bytes
# DATA_SIZE[11] = 512 Bytes
# DATA_SIZE[12] = 64 Bytes
# DATA_SIZE[13] = 128 Bytes
# DATA_SIZE[14] = 512 Bytes
# DATA_SIZE[15] = 64 Bytes
# DATA_SIZE[16] = 64 Bytes
# DATA_SIZE[17] = 512 Bytes
# DATA_SIZE[18] = 512 Bytes
# DATA_SIZE[19] = 128 Bytes
# DATA_SIZE[20] = 64 Bytes
# DATA_SIZE[21] = 1024 Bytes
# DATA_SIZE[22] = 512 Bytes
# DATA_SIZE[23] = 512 Bytes
# DATA_SIZE[24] = 64 Bytes
# DATA_SIZE[25] = 64 Bytes
# DATA_SIZE[26] = 64 Bytes
# DATA_SIZE[27] = 64 Bytes
# DATA_SIZE[28] = 64 Bytes
# DATA_SIZE[29] = 64 Bytes
# DATA_SIZE[30] = 64 Bytes
# DATA_SIZE[31] = 64 Bytes
# DATA_SIZE[32] = 64 Bytes
# DATA_SIZE[33] = 64 Bytes
# DATA_SIZE[34] = 128 Bytes
# DATA_SIZE[35] = 64 Bytes
# DATA_SIZE[36] = 64 Bytes
# DATA_SIZE[37] = 64 Bytes
# DATA_SIZE[38] = 512 Bytes
# DATA_SIZE[39] = 512 Bytes
# DATA_SIZE[40] = 64 Bytes
# DATA_SIZE[41] = 128 Bytes
# DATA_SIZE[42] = 512 Bytes
# DATA_SIZE[43] = 64 Bytes
# DATA_SIZE[44] = 64 Bytes
# DATA_SIZE[45] = 64 Bytes
# DATA_SIZE[46] = 512 Bytes
# DATA_SIZE[47] = 64 Bytes
# DATA_SIZE[48] = 64 Bytes
# DATA_SIZE[49] = 64 Bytes
# DATA_SIZE[50] = 64 Bytes
# DATA_SIZE[51] = 64 Bytes
# DATA_SIZE[52] = 64 Bytes
# SPACE_SIZE[0] = 4 Bytes
# SPACE_SIZE[1] = 4 Bytes
# SPACE_SIZE[2] = 4 Bytes
# SPACE_SIZE[3] = 4 Bytes
# SPACE_SIZE[4] = 4 Bytes
# SPACE_SIZE[5] = 4 Bytes
# SPACE_SIZE[6] = 4 Bytes
# SPACE_SIZE[7] = 4 Bytes
# SPACE_SIZE[8] = 4 Bytes
# SPACE_SIZE[9] = 4 Bytes
# SPACE_SIZE[10] = 4 Bytes
# SPACE_SIZE[11] = 4 Bytes
# SPACE_SIZE[12] = 4 Bytes
# SPACE_SIZE[13] = 4 Bytes
# SPACE_SIZE[14] = 4 Bytes
# SPACE_SIZE[15] = 4 Bytes
# SPACE_SIZE[16] = 4 Bytes
# SPACE_SIZE[17] = 4 Bytes
# SPACE_SIZE[18] = 4 Bytes
# SPACE_SIZE[19] = 4 Bytes
# SPACE_SIZE[20] = 4 Bytes
# SPACE_SIZE[21] = 4 Bytes
# SPACE_SIZE[22] = 4 Bytes
# SPACE_SIZE[23] = 4 Bytes
# SPACE_SIZE[24] = 4 Bytes
# SPACE_SIZE[25] = 4 Bytes
# SPACE_SIZE[26] = 4 Bytes
# SPACE_SIZE[27] = 4 Bytes
# SPACE_SIZE[28] = 4 Bytes
# SPACE_SIZE[29] = 4 Bytes
# SPACE_SIZE[30] = 4 Bytes
# SPACE_SIZE[31] = 4 Bytes
# ========== ========== ========== ========== ======== ========== ==========
Contraintes du protocole de bus
Les blocs numériques communiquent généralement entre eux à l'aide de protocoles de bus, dont certains exemples incluent AMBA AXI WishBone, OCP, etc. Le maître du bus envoyant des données conformes au protocole d'accès fournit des signaux de contrôle qui indiquent à l'esclave quand le paquet est authentifié, si le paquet est lu ou écrit, et combien d'octets de données ont été envoyés. Le serveur maître envoie également une adresse suivie des données à stocker à cette adresse.
Regardons un exemple où le testbench agit en tant que maître et contraint l'objet de classe de paquet de bus avec des données de validation.
// 总线协议约束示例
// 类定义
class CLASS_BUS_PROTOCOL ;
rand int M_ADDR;
rand bit [31:0] M_DATA;
rand bit [1:0] M_BURST;
rand bit [2:0] M_LENGTH;
constraint C_ADDR {
M_ADDR % 4 ==0;
};
function void display();
$display("==== ==== ==== ==== Transaction Details ==== ==== ==== ====");
$display("M_ADDR = 0x%0h",M_ADDR);
$display("M_DATA = 0x%0h",M_DATA);
$display("M_BURST = %0d Bytes / xfr",M_BURST+1);
$display("M_LENGTH = %0d",M_LENGTH+1);
$display("==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====");
endfunction
endclass
module TB_BUS_PROTOCOL ();
int S_STR_ADDR;
int S_END_ADDR;
CLASS_BUS_PROTOCOL OBJ = new();
initial
begin
S_STR_ADDR = 32'd0;
S_END_ADDR = 1023;
OBJ.randomize() with {
M_ADDR >= S_STR_ADDR;
M_ADDR < S_END_ADDR;
M_ADDR + (M_LENGTH+1) * (M_BURST+1) < S_END_ADDR;
};
OBJ.display();
end
endmodule
Résultats de la simulation:
# ==== ==== ==== ==== Transaction Details ==== ==== ==== ====
# M_ADDR = 0x374
# M_DATA = 0xc72f5a8f
# M_BURST = 3 Bytes / xfr
# M_LENGTH = 6
# ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
pre_randomize & post_randomize
Les variables déclarées aléatoires ou aléatoires dans une classe sont rendues aléatoires à l'aide de la méthode intégrée randomize(). Renvoie 1 si la randomisation a réussi et 0 si la randomisation a échoué. Peut échouer pour diverses raisons telles que des violations de contraintes, le solveur n'étant pas en mesure d'arriver à une valeur qui satisfait toutes les contraintes, etc. Les objets de classe ne sont pas automatiquement randomisés, nous devons donc appeler manuellement la méthode randomize() pour la randomisation.
Exemple simple :
randomize() appelle automatiquement deux fonctions de rappel avant et après le calcul des valeurs aléatoires.
pré-randomisation
post-randomisation
couverture
Tout ce que nous avons à faire est de remplacer les méthodes vides existantes pre_randomize() et post_randomize() par nos propres définitions. C'est une manière astucieuse de changer les caractéristiques aléatoires d'un objet. Si la classe est une classe dérivée et qu'aucune des méthodes n'a d'implémentation définie par l'utilisateur, les deux méthodes appelleront automatiquement sa superfonction.
Notez que pre_randomize() et post_randomize() ne sont pas virtuelles, mais fonctionnent comme des méthodes virtuelles. Si vous essayez de les rendre virtuels manuellement, vous pouvez rencontrer une erreur de compilation comme indiqué ci-dessous.
- Si randomize() échoue, post_randomize() n'est pas appelé
- La méthode randomize() est intégrée et ne peut pas être remplacée
- Si la randomisation échoue, ces variables conservent leurs valeurs d'origine et ne sont pas modifiées
Contraintes en ligne
Considérez une classe qui a déjà des contraintes bien écrites et qui doit randomiser des variables de classe avec un ensemble différent de contraintes décidées par l'utilisateur. En utilisant la construction with, l'utilisateur peut déclarer des contraintes en ligne au point où la méthode randomize() est appelée. Le solveur prendra en compte ces contraintes supplémentaires avec les contraintes d'origine de l'objet.
Lorsque les contraintes sont en conflit, l'émulateur signalera une erreur :
Autre exemple de conflit :
contraintes souples
Les contraintes régulières sont appelées contraintes dures car le solveur doit toujours les satisfaire. Si le solveur ne trouve pas de solution, l'opération de randomisation échouera.
Cependant, les contraintes déclarées comme souples donnent au solveur une certaine flexibilité s'il existe d'autres contraintes conflictuelles que les contraintes doivent satisfaire - contraintes dures ou contraintes souples avec une priorité plus élevée.
Les contraintes souples sont utilisées pour spécifier les valeurs par défaut et les distributions des variables aléatoires.
Exemple : contraintes souples, plage de valeurs de données [4:12]
En cas de conflit avec des contraintes strictes en ligne :
Quelle est la différence entre les contraintes souples/dures ?
L'exemple ci-dessus supprime le mot-clé soft :
annuler les contraintes
Les contraintes peuvent être activées ou désactivées par le biais de la contrainte_mode();
grammaire
Constraint_mode() peut être utilisé comme fonction ou comme tâche ;
Constraint_mode() est une méthode intégrée et ne peut pas être remplacée.
méthode
exemple
Contraintes proches :
Si vous activez une contrainte qui n'existe pas, le simulateur signalera une erreur :
désactiver la randomisation
La randomisation des variables au sein d'une classe est désactivée à l'aide de rand_mode(). Lorsqu'elle est désactivée, la variable n'est plus aléatoire.
exemple
Par rapport:
randcase
Parfois, nous rencontrons une situation où nous voulons que le solveur choisisse au hasard une instruction parmi plusieurs. Le mot clé randcase introduit une instruction case pour laquelle une de ses branches est choisie au hasard. Les expressions de terme de cas sont des valeurs entières positives représentant les poids associés à chaque terme. La probabilité de sélectionner un élément est calculée en divisant le poids de l'élément par la somme de tous les poids.
exemple
Si un poids de branche est nul, alors cette branche ne sera jamais choisie :
Si tous les poids sont nuls, un avertissement est émis lors de l'exécution de la simulation :
Chaque appel à randcase récupère un nombre aléatoire de 0 à la somme des poids, puis choisit les poids dans l'ordre de déclaration : le plus petit nombre aléatoire correspond à la première déclaration de poids (en haut).