• Aucun résultat trouvé

Laurent Arditi et Hélène Collavizza

3. Le processus de vérification

3.1. Une approche coopérative

Comme expliqué en 1.1, la vérification du jeu d’instruction d’un processeur par rapport à son architecture est réalisée par des équivalences successives entre des ni-veaux de spécification adjacents. Une étape de vérification consiste à montrer que le diagramme suivant commute :

trans1i+1 . . .transki+1 . . .transni+1 transi abs abs i i+1 état état état état i,final i+1,final q P où état

est un niveau abstrait (par exemple, le langage assembleur), état✌✑✏✓✒

est un ni-veau plus concret (par exemple, le langage des micro-instructions),✡✁✄✂

Les principaux résultats déjà obtenus [Hun 89, JOY 90, WIN 90, STA 93] concer-nent le cas où✄✂☎✁✆✂☎✝ . Nous proposons ici une approche coopérative entre plusieurs outils de preuve afin de résoudre également les autres cas.

Pour vérifier la commutation du diagramme ci-dessus, les mécanismes mis en jeu sont :

— l’abstraction : pour lier les deux visions du processeur,

— l’exécution symbolique : pour calculer les états symboliques définis par les

transitions,

— la simplification et la composition : pour réduire une séquence de

micro-opérations en une macro-opération et mettre les expressions sous forme canonique,

— l’induction : pour prouver des macro-opérations réalisées par des boucles de

micro-opérations.

Cas où✄✂☎✁✆✂☎✝ .

Ce cas décrit la plus grande partie du jeu d’instructions. Cela correspond à une instruction dont l’opération principale (par exemple une addition) est fournie par l’ar-chitecture. La différence entre les deux niveaux est simplement que le niveau concret est plus détaillé et nécessite plus de transferts intermédiaires. Dans ce cas, l’exécution symbolique et la simplification sont suffisants.

Par exemple, une instruction d’addition en mode d’adressage direct peut être spé-cifiée au niveau assembleur par :

✞✠✟✠✟☛✡✌☞✎✍✑✏✆✒✟✠✓✕✔✗✖✙✘✛✚✜✍✑✚✢✒✣✑✤✦✥★✧✩✥✫✪ ☞✎✍✑✏✆✒✟✠✓✕✔✗✖✙✘✛✚✜✍✑✚✢✒✣✑✤✦✥★✧✩✥✭✬✮✚✯✍✑✚✢✒✰✣✑✤✫✬✌✱✙✥

✣✑✤ ✣✑✤✲✬✴✳

✵✷✶

est le compteur de programme,✸✺✹✻✸ la mémoire,✸✺✹✷✸✽✼ ✵✷✶✆✾

désigne le code de l’instruction courante,

✂❁❀

extrait le numéro du registre destination et❂❃✹✻❄ est le banc des registres utilisateurs.

Au niveau des micro-instructions, cette instruction est implémentée par la sé-quence suivante :

❅❇❆✎✡❉❈❊☞ ✚✜✍✑✚✢✒✰✣✑✤✦✥

; lecture de l’instruction

✣✑✤ ✣✑✤✮✬❉✱

❅●❋✑✡✯❍❃■❑❏ ✚✜✍✑✚✢✒✰✣✑✤✦✥

; utilisation d’un registre tampon

✣✑✤ ✣✑✤✮✬❉✱

❅●▲✑✡✯☞❑✍✑✏✆✒✰✟✠✓✕✔❁✖▼✘★❈❊☞❑✧✩✥◆✪ ☞✎✍✑✏✆✒✟✠✓❖✔❁✖✙✘★❈❊☞✎✧✩✥❊✬✲❍❃■❑❏

La preuve consiste à composer des transitions et à réaliser des simplifications sym-boliques.

Cas où✁✂☎✝ et✁✂✂ .

Il s’agit d’instructions qui répètent fois une opération, étant la valeur d’un registre. Par exemple, l’instructionrep stosbdu processeur Intel 8086, recopie fois ( est la valeur du registrecx) le contenu du registrealen mémoire à partir de l’adresse contenue dansdi. La spécification au niveau assembleur est :

✄✕✓✆☎✎✔❁✖✞✝ ✔✠✟ tant que✡☞☛✍✌ ✎✑✏ faire ✚✯✍✑✚✢✒✰✟✓✒✥ ✞✓✔ ✟✓✒ ✟✓✒❊✬ ✡✕☛ ✡✕☛✗✖

Elle est implémentée par :

❆✎✡ ✞✠✟ ✟✓✒

; connecté au bus d’adresses

✟✠✞ ✞✓✔

; connecté au bus de données tant que✡☞☛✍✌ ✎✑✏ faire ✚✯✍✑✚ ✒✞✠✟❖✥ ✟✠✞ ✞✠✟ ✞✠✟ ✬❉✱ ✡☞☛ ✡✕☛✗✖ fi n ▲✑✡ ✟✓✒ ✞✠✟

Une preuve inductive est indispensable puisque la condition de sortie de boucle teste la valeur d’un composant (✘✠✙ ) qui reste symbolique

. Dans un grand nombre de cas cependant, ce type de preuve ne demande pas d’intervention humaine, puisque chaque passage dans la boucle est du même ordre de complexité pour chacun des deux niveaux (l’opération élémentaire effectuée est fournie aux deux niveaux).

Cas où✄✂☎✝ et✁✂✂ .

C’est le cas le plus critique. Il correspond en général à une instruction qui réalise une opération qui n’est pas fournie par le chemin de données et qui est donc réali-sée par une boucle d’opérations élémentaires (opérations arithmétiques complexes, certains décalages et rotations).

Si la condition de sortie de boucle porte sur une valeur constante, l’exécution sym-bolique permet de calculer la valeur de l’état final. Toutefois, comme tous les passages dans la boucle sont tracés, on peut craindre une explosion combinatoire. On peut alors associer à l’exécution symbolique une représentation efficace des données. Ce cas est illustré par l’algorithme de multiplication par additions/décalages détaillé en 5.2. Pour un opérande de taille

, il faut effectuer

additions et décalages.

Par contre, si la condition de sortie de boucle porte sur une valeur symbolique, une preuve inductive est indispensable. C’est le cas par exemple d’une multiplication réalisée par un algorithme d’additions itérées où l’on additionne fois la valeur de

pour réaliser✚✢✜

. La preuve inductive peut nécessiter dans ce cas l’introduction de lemmes liant l’arithmétique du niveau abstrait et celle du niveau concret.

✣✥✤

2. prendre une abstraction de cet état : ✡✁✄✂✗❀☞☛ ☎✍✌ ✄✎ ✌✏✓✒

s’il y a une boucle au niveau

(cas où ✂☎✝ ) alors 3. générer les fonctions récursives des niveau

et niveau✌✑✏✓✒ : ✂✒✑ ✟✂✄✆✟✡☛ ✡✭❀ -☎✞✄ ☎✞✝✠✟ ✄✆☎✞✝✠✟✂✡☞☛ ✌✑✏✓✒ ✂✓✑ ✟✂✄✆✟✡☛ ✡✭❀ -☎✞✄ ☎✞✝✠✟ ✄✆☎✞✝✠✟✂✡☞☛ ✌✑✏✓✒ 4. montrer l’équivalence logique entre ces deux fonctions :

☎✔✄ ☎✞✝✠✟ - ☛✞✌✞✌✞✔ ✌✖✕ ✡✁✄✂✗❀☞☛ ☎✍✌ ✌✑✏✓✒ ✝✗✝

Figure 1. L’algorithme de vérifi cation (début)

3.2. L’algorithme de preuve

Supposons définis deux objets de la classeInterpreterqui spécifient un pro-cesseur aux niveaux ✄✆☎✞✝✠✟✂✡

(la spécification) et ✄✆☎✞✝✠✟✂✡☞☛ ✌✏✓✒

(l’implémentation), et un objet de la classe Proof, qui lie ces deux interprètes. La preuve entre les deux niveaux est réalisée par l’algorithme de les figures 1 et 2.

Les points 1 et 2 calculent des états initiaux à partir de valeurs symboliques. Si l’instruction est une instruction de boucle, les fonctions récursives sont construi-tes à partir de l’attributselectdes interprètes (point 3). Le point 4 réalise une preuve inductive.

Les points 5 et 6 réalisent les transitions d’état ; execute calcule les transitions tout en simplifiant les expressions (sa sémantique est détaillée en 4.2.1). Le point 7 effectue une vérification syntaxique de l’égalité des deux expressions.

Si l’exécution symbolique échoue nous utilisons différentes techniques. Si l’échec est dû à une explosion combinatoire, nous essayons d’associer à l’exécution symbo-lique une représentation compacte sous forme de *BMDs (voir 4.3). Dans ce cas, le point 8 est équivalent aux points 1, 2, 5, 6 et 7 en utilisant les *BMDs comme repré-sentation symbolique.

Si l’exécution symbolique échoue à cause d’une boucle dont la condition n’est pas évaluable ou si elle est dûe à une explosion combinatoire avec les *BMDs, le point 9 réalise une preuve inductive de l’équivalence des fonctions associées aux transitions. Celle du ✄✆☎✔✝✕✟✁✡☞☛✎✌

est primitive tandis que celle du ✄✆☎✞✝✠✟✂✡ ☛✍✌✑✏✓✒

est récursive. Cette preuve induira éventuellement une généralisation des expressions et l’introduction de lemmes ; elle ne sera donc pas automatique comme dans le point 4.

sinon

5. exécuter la transition sélectionnée par l’interprète du niveau pour obtenir l’état final au niveau

: ❀☞☛ ✡☞✄ ❀☞☛ ✡☞✄ ✂✁☎❀ ☎✌ ✂✁✟ ✄✆ ✝✗✝ ❀☞☛ ✡☞✄ 6. exécuter les transitions au niveau✌✏✓✒

jusqu’à ce qu’une condition ne soit pas évaluable ou qu’il y ait une explosion combinatoire ou que les deux interprètes soient synchronisés :

répéter ❀☞☛ ✡☞✄ ❀☞☛ ✡☞✄ ✂✂☎❀ ☎✌ ✂✁✟ ✄✆ ✌✑✏✓✒ ✝✗✝ ✌✑✏✓✒ ❀☞☛ ✡☞✄ ✌✑✏✓✒ jusqu’à non-évaluable ✂✁✟ ✄✆ ✌✑✏✓✒✡✝ si ✂✂✁✠✄ ✘✂✄✎ ✌✑✏✓✒ alors 7. vérifier que ✌✄✂ ✡✁✄✂✗❀☞☛ ✄✆ ✌✑✏✓✒ fin si si explosion✄✆ ✌✏✓✒ alors

8. utiliser une representation compacte (Diagrammes de Moments Binaires) : ✌✑✏✓✒ ☎✞✄✆☎❀✆☎ ✸✞✝ ✂✗❀ ✡✠❀ ✌✑✏✓✒ ✡✁✄✂✗❀☞☛ ☎✌ ✄✆ ✌✑✏✓✒✡✝ ❀☞☛ ✡☞✄ ❀☞☛ ✡☞✄ ✂✁☎❀ ☎✍✌ ✂✂✟ ✄✆ ✌✖✝✗✝ ❀☞☛ ✡☞✄ ✌✎✝ répéter ❀☞☛ ✡☞✄ ❀☞☛ ✡☞✄ ✂✂☎❀ ☎✌ ✂✂✟ ✄✎ ✌✏✓✒ ✌✑✏✓✒ ❀☞☛ ✡☞✄ ✌✑✏✓✒ jusqu’à explosion✄✆ ✌✑✏✓✒ ou ✂✂✁✠✄ ✘✂✄✎ ✌✑✏✓✒ si ✂✂✁✠✄ ✘☎✄✆ ✌✑✏✓✒ alors vérifier que ✌✟✂✡✠☞☛✍✌ ✡✁✄✂❁❀☞☛ ☎✌ ✄✆ ✌✑✏✓✒ sinon aller en 9 fin si fin si si non-évaluable ✂✂✟ ✄✎ ✌✑✏✓✒ ✝✗✝ alors

9. preuve inductive entre la spécification et l’implementation :

☎✏✎ ✂✓✑ ✟✂✄✆✟✡☛ ✡✭❀ - ☎✑✎ ☎❀ ☎✞✝✠✟ ✄✆☎ ✝✠✟ ✡☞☛ ✌✑✏✓✒ ✂✒✑ ✟✂✄✆✟✡☛ ✡✭❀ -☎✞✄ ☎✞✝✠✟ ✄✆☎✞✝✠✟✂✡☞☛ ✌✏✓✒ ☎✞✄ ☎✞✝✠✟ - ✟✁☛✍✝✠✟ ☎✏✎ ✂❁❀☞☛ ☎✌ ✌✑✏✓✒☞✝ fin si fin si

field( )ou [: ] sélection d’un champ de bits sum( ❆✟✁

) somme d’un additionneur

carry( ❆✂✁

) retenue sortante d’un additionneur add( ❆✟✁ )équivalent à addition concat(sum( ❆✠✁ ),carry( ❆✂✁ )) sub( ❆✟✁ ) soustraction

incr(V)équivalent àsum(V,"1") incrémentation and( ❆✟✁

✤✆✤

) opérations logiques bit-à-bit

not( ) négation logique

mux(✡ ❆✟✁

)équivalent à sortie d’un multiplexeur if then

else

BV_nat( )équivalent à☛☞ conversion vers les entiers naturels exts(

) extension à✬ bits

length( ) longueur d’un vecteur de bits lsb( )équivalent à [0:0] bit de poids le plus faible msbs( )équivalent à [1:length( )-1] bits de poids forts BV_null(✬ ) vecteur formé de✬ bits nuls eq( ❆✟✁

)ou ❆✍✌

égalité

Tableau 1. L’algèbre des vecteurs de bits