Randomisation contrainte et reproductibilité | FPGA Pour Tous
Résumé
Randomisation contrainte et reproductibilité
Générer des transactions variées tout en respectant le protocole, puis reproduire exactement un échec.
Aléatoire, mais valide
La randomisation contrainte génère des combinaisons de valeurs dans un espace défini. L'objectif n'est pas de remplacer le plan de vérification, mais d'explorer davantage d'interactions qu'une petite liste de tests dirigés.
class dma_request; rand bit [31:0] address; rand int unsigned length; rand bit write; constraint legal_length { length inside {[1:256]}; } constraint aligned_address { address[1:0] == 2'b00; } constraint boundary { address[11:0] + length <=
4096
;
}
endclass
Les contraintes décrivent ici ce qui constitue une commande valide : longueur bornée, adresse alignée et transfert qui ne franchit pas une page de 4 Kio.
Une contrainte est déclarative : toutes les expressions actives sont résolues ensemble, sans ordre d'exécution de haut en bas. Une implication permet d'activer une règle conditionnelle :
L'implication ne fonctionne que dans ce sens : lorsque write vaut 0, elle n'impose rien à length. solve write before length pourrait modifier la distribution obtenue, mais ni ajouter ni retirer de solutions légales.
Toujours contrôler le résultat
randomize() renvoie 1 si une solution a été trouvée et 0 si les contraintes sont incompatibles.
dma_request req = new();if (!req.randomize() with { write == 1'b1; length inside {16, 32, 64};}) $fatal(1, "Request constraints have no solution");
Ignorer cette valeur de retour peut envoyer une ancienne valeur ou une valeur par défaut au driver. Un échec de randomisation est une erreur de testbench à diagnostiquer. L'appel n'est pas placé dans l'expression d'une assertion : même si les assertions sont désactivées, la randomisation indispensable doit toujours être exécutée.
Le bloc with ajoute des contraintes aux blocs déjà actifs ; il ne les remplace pas. Une demande inline incompatible doit donc échouer, ce qui est précisément la raison de contrôler le retour.
Distribution et cas intéressants
dist permet de donner plus de poids à certaines valeurs sans les rendre obligatoires.
Ici, l'ensemble [2:63] possède un poids total de 20. Ce n'est pas équivalent à attribuer 20 à chacune de ses 62 valeurs.
randc parcourt les valeurs d'un petit domaine avant de les répéter. Il ne convient pas à de grands vecteurs où le cycle complet serait irréaliste.
Activer et désactiver sans surprise
req.address.rand_mode(0) fige address, mais les contraintes qui mentionnent ce champ restent actives. Si sa valeur figée les contredit, la randomisation échoue. req.legal_length.constraint_mode(0) désactive au contraire le bloc de contraintes nommé.
Séparer les contraintes de validité facilite l'injection volontaire d'erreurs : le test désactive exactement la règle à violer et conserve les autres. Désactiver un grand bloc indistinct élargirait l'espace de stimulus bien au-delà du cas recherché.
Contrôler largeurs et signes
Le solveur respecte les règles de taille et de signe du langage. Une somme stockée dans un type trop étroit peut déborder puis satisfaire une contrainte avec une valeur rebouclée. Dimensionner l'expression pour le résultat attendu, préférer des longueurs et adresses non signées et borner explicitement la taille des tableaux dynamiques.
Des contraintes foreach imbriquées sur tous les couples créent rapidement un problème quadratique. Pour de grands ensembles uniques, un algorithme procédural ou un domaine randc raisonnable peut être plus clair et plus rapide.
Reproduire un bug
Un générateur pseudo-aléatoire est déterministe pour une graine donnée, tant que l'environnement, l'ordre des appels et l'outil restent comparables. Le journal de test doit enregistrer :
la graine globale ou locale ;
le nom et la configuration du test ;
les paramètres du DUT ;
la transaction fautive et son numéro ;
la version du code et du simulateur.
Relancer avec la même graine est la première étape. Réduire ensuite le scénario à quelques transactions accélère le diagnostic.
À retenir
Les contraintes décrivent l'espace valide ou volontairement invalide à explorer.
Le retour de randomize() doit toujours être contrôlé.
Une contrainte inline s'ajoute aux contraintes actives et solve before ne change que la distribution.
:= pondère chaque valeur d'une plage ; :/ répartit un poids total.
rand_mode fige une variable, tandis que constraint_mode active ou désactive un bloc.
Les distributions ciblent les cas importants et les trous de couverture.
randc est adapté aux petits domaines.
Une graine, la configuration et la transaction fautive doivent être enregistrées.