Veuillez consulter cette page à partir de l'interface principale.
Les registres et la mémoire du YASEP
yasep/doc/reg-mem.en.html version 2012-09-30
Traduction initiale : Laura Bécognée & YG

Le YASEP n'a pas d'instructions de lecture ou d'écriture en mémoire. Contrairement à l'écrasante majorité des processeurs existants (de la catégorie "Load-Store architecture"), il accède à la mémoire au travers des registres, plus ou moins comme le faisait le CDC6600 (sauf que les registres du YASEP ne sont pas dédiés à l'écriture ou la lecture). Cela économise des opcodes, augmente le nombre d'accès mémoire par intruction, et garde la structure des instructions très orthogonale.

L'architecture du YASEP est conçue autour de 16 registres répartis en 4 types. Voici la liste des noms, avec leur numéro :

(code cassé ?)

C'est un peu plus compliqué qu'une architecture RISC classique mais cela réduit le nombre d'opcodes en leur permettant de remplir de nombreuses fonctions différentes. Il existe aussi deux bits de condition (Carry/retenue et Zero) mais ils sont accessibles seulement au travers des instructions conditionnelles. Le mécanisme de sauvegarde et la restauration de l'état en cas d'exception n'est pas encore déterminé.

 

Les registres "normaux"

R1, R2, R3, R4 et R5 sont des registres normaux. Comme dans les autres architectures, ils sont lus et écrits sans déclencher d'action implicite.

; Exemple 1
 MOV 1234h R2   ; écrit la valeur 1234h dans le registre R2
; Exemple 2
 ADD 4 R1       ; R1 <- R1 + 4
; Exemple 3
 ADD 32 R1 R3   ; R3 <- R1 + 32

On les utilise pour contenir les résultats intermédiaires des calculs, les compteurs de boucles, les paramètres d'appel aux fonctions... Les formes d'instructions étendues permettent aussi d'incrémenter ou de décrémenter ces registres. (en développement)

 

Le registre "PC"

Ce registre est plus spécial : PC est le pointeur vers l'instruction en cours. Les lettres "PC" signifient "Program Counter". Il est automatiquement incrémenté à chaque nouvelle instruction, et peut être lu et sur-écrit par toutes les instructions.

On peut remarquer que les instructions de YASEP sont encodées sur 2 ou 4 octets et que toutes les adresses ont une granularité d'un octet. De plus, les instructions sont toujours à des adresses paires, donc le bit de poids faible des adresses d'instructions est toujours implicitement à 0.

Cependant, il ne faut pas se fier à cette particularité car cela pourra changer dans le futur. En attendant, au mieux, l'écriture d'un 1 dans le bit de poids faible peut être ignorée, au pire cela peut bloquer le processeur, le réinitialiser ou déclencher une interruption, selon l'implémentation. Donc gardez ce bit à 0 !

 

Les registres d'"Adresse"

Les registres A1, A2, A3, A4 et A5 contiennent l'adresse où une donnée sera lue ou écrite en mémoire. Ils peuvent être mis à jour (incrémentés ou décrémentés) par les instructions de forme étendue, afin d'accéder facilement à des données contigües.

 

Les registres de "Donnée"

D1, D2, D3, D4 et D5 sont des registres de données, associés aux registres d'adresse : A1 est lié à D1, A2 à D2 etc. Le YASEP maintient toujours la cohérence entre les registres d'adresse et de donnée d'une paire, afin de préserver la relation Dx=memoire[Ax] :

Si on écrit et lit le même registre de donnée (qui est alors utilisé comme opérande et destination), alors cela met à jour la mémoire. C'est une manière de faire des opérations "RMW" (read-modify-write) avec un cœur RISC orthogonal.

; Exemple 1
 MOV 1234h A1    ; pointe A1 vers l'addresse 1234h ==> D1 contient la valeur à cette adresse.
 ADD D1 R3       ; ajoute le contenu de la mémoire à l'adresse 1234 au registre R3.
; Exemple 2
 MOV 1234h A3    ; pointe A3 vers l'adresse 1234h ==> D3 contient la valeur à cette adresse.
 ADD D3 R2       ; ajoute le contenu de la mémoire à l'adresse 1234 au registre R2.
; Exemple 3
 ADD 1234h R1 A4 ; additionne R1+1234h et met le résultat dans A4 ==> D4 contient la valeur à cette adresse.
 ADD R2 D4       ; lit le mot situé à l'adresse [R1+1234h],
                  ; ajoute la valeur de R2 et réécrit le résultat à la même adresse.

En conjonction avec les fonctions d'incrémentation et décrémentation, ces registres peuvent réaliser des piles. Par convention, A5 est le pointeur de pile et D5 est le sommet de la pile, mais rien n'empêche de créer 2 ou 3 piles, ou bien de réaliser la pile standard avec d'autres registres

 

Alias de pointeurs et de la mémoire

Lorsque deux registres d'Adresses (ou plus) pointent vers le même mot en mémoire, il ne faut pas s'attendre à ce que les valeurs des registres de Données restent cohérents après une écriture. Par exemple, dans le cas où A1=A2, alors une écriture dans D1 ne va pas automatiquement mettre à jour la valeur de D2.

Pour les premières réalisations, il n'est pas raisonnable de comparer 5 registres d'Adresses entre eux et d'écrire conditionnellement 5 registres de données. La longueur du pipeline ainsi que le nombre de portes logiques augmenteraient beaucoup trop ! Dans les cas les plus simples, on peut considérer que le banc de registre sert de tampon à la mémoire, ce qui permet de conserver l'état même lors de changements de contextes.

Cependant il est possible et même probable que des réalisations plus sophistiquée résolvent le problème, au moyen de structures différentes.

Dans tous les cas, lorsqu'un alias ou chevauchement de pointeurs est attendu ou même simplement possible, il ne faut pas hésiter à utiliser une section critique (voir l'opcode CRIT) et le plus sûr est de n'utiliser qu'une seule paire de registres Adresse/Donnée pour accéder à ces mots en mémoire. Comme l'a montré l'exemple précédent, les opérations de "read-modify-write" fonctionnement mieux et sont plus courtes lorsqu'une seule paire est utilisée.

 

Alignement de la mémoire

Attention : Le YASEP est une architecture "little Endian" où les pointeurs peuvent adresser des octets mais TOUS les accès à la mémoire sont alignés sur les frontières naturelles des mots.

Les accès non alignés ne provoquent pas d'erreur ou d'exception. Les registres de données contiennent une copie de la mémoire, sans décalage.

On pourrait même schématiser de cette manière :

Les bits "perdus" servent juste à sélectionner un octet dans le (demi-)mot.

; Exemple 1 (lecture en mémoire)
 MOV 1231h A5    ; pointe A5 vers l'addresse 1231h (impaire donc non alignée)
 MOV D5 R2       ; copie le mot à l'adresse 1230 dans R2.
; Exemple 1 bis (YASEP32 seulement)
 MOV 1232h A1    ; pointe A1 vers l'addresse 1232h (non alignée sur une frontière de mot)
 MOV D1 R1       ; copie le mot à l'adresse 1230 dans R1.

; Exemple 2 (écriture en mémoire)
 MOV 1231h A2    ; pointe A2 vers l'addresse 1231h (impaire donc non alignée)
 MOV R2 D2       ; écrit R2 en mémoire à l'adresse 1230
; Exemple 2 bis (YASEP32 seulement)
 MOV 1232h A3    ; pointe A3 vers l'addresse 1232h (non alignée sur une frontière de mot)
 MOV R4 D3       ; écrit R4 en mémoire à l'adresse 1230

Si on traite des octets ou demi-mots, l'alignement peut être effectué automatiquement par certaines instructions :

; Exemple 1' (lecture en mémoire)
 MOV 1231h A5    ; pointe A5 vers l'addresse 1231h (impaire donc non alignée)
 ESB D5 R2       ; Extrait l'octet à l'adresse 1231, duplique le bit de signe et écrit le résultat dans R2.
; Exemple 1' bis (YASEP32 seulement)
 MOV 1232h A1    ; pointe A1 vers l'addresse 1232h (non alignée sur une frontière de mot)
 ESH D1 R1       ; Extrait le demi-mot à l'adresse 1232, duplique le bit de signe et écrit le résultat dans R1.

; Exemple 2' (écriture en mémoire)
 MOV 1231h A2    ; pointe A2 vers l'addresse 1231h (impaire donc non alignée)
 IB R2 D2        ; prend l'octet de poids faible en R2, insère le résultat dans l'octet de poids fort de D2.
; Exemple 2' bis (YASEP32 seulement)
 MOV 1232h A3    ; pointe A3 vers l'addresse 1232h (non alignée sur une frontière de mot)
 IH R4 D3        ; prend le demi-mot de poids faible en R4, insère le résultat dans le demi-mot de poids fort de D3.

Les mots ou demi-mots non alignés doivent être reconstitués au moyen de séquences d'instructions appropriées.

(à écrire)

 

"Garage" des registres ("parking")

(ajouté au blog le mardi 8 Novembre 2011) (updated 2013-08-09 : added to YASim and simplified)

Ainsi donc, l'architecture du YASEP spécifie 5 registres "normaux" et 5 paires de registres adresses/données (A1/D1, A2/D2...) et il est assez difficile de trouver un équilibre entre ces nombres, car chaque application et chaque cas requiert un nombre différent de registres.

Si plus de registres normaux sont nécessaires (imaginons qu'on aie besoin de R6 et R7) alors on pourrait les assigner à D1 et D2 par exemple. Cependant, auparavant, il faut donner des adresses convenables à A1 et A2, sinon le programme pourrait planter à cause de valeurs aléatoires (ou pire) dans A1 et A2, ce qui écrirait D1 et D2 n'importe où...

Une autre situation indésirable pourrait se produire si on se sert de A1 et A2 comme registres de données. Chaque écriture déclencherait une lecture de la mémoire. Si en plus un système de mémoire paginée est utilisé, chaque écriture risque de déclencher une faute de page (ou de protection), ce qui ralentirait énormément le programme...

Il existe une approche assez radicale : un bit par paire de registres définit si la paire est utilisée pour accéder à la mémoire ou comme registres normaux. L'avantage de cette approche est qu'il est possible de libérer deux registres d'un coup, mais c'est compliqué à utiliser avec un compilateur ou au niveau d'un code source de haut niveau. En plus il faut sauver 5 bits supplémentaires lors d'un changement de contexte, ce qui peut devenir un vrai cauchemar...

 

Dans le YASEP, une autre technique, appelée "parking", est utilisée. Elle est moins optimale mais plus pratique et moins coûteuse : une valeur particulière dans un registre A va inhiber les accès à la mémoire pour le registre D associé. On perd un registre mais le système reste disponible : on n'a pas besoin d'une instruction supplémentaire pour accéder de nouveau à la mémoire, il suffit de mettre une nouvelle adresse valide dans le registre A.

Une paire de registres est "parquée" lorsque son registre A a tous ses bits à 1 (soit la valeur -1). Lorsqu'on écrit -1 dans un registre A, le registre D associé garde sa valeur précédente mais n'accède plus à la mémoire et donc on peut y écrire autant qu'on veut.

Cette adresse négative correspond à une zone "tout en haut" de la carte mémoire, qui n'est pas nécessairement implémentée, ou alors réservée à des constantes adressées par des adresses immédiates courtes (de -8 à +7) :

      MOV 6, A3  ; mem[6] contient une constante utile
                 ;   ou une variable récurrente
      MOV D3,... ;   dont l'adresse tient sur 4 bits

Attention car toutes les implémentations du YASEP ne supportent pas le parking.
Ces profils le supportent : (not initialised)
Et ceux-cis non : (not initialised)

Voici un exemple qui parque tous les registres :

    MOV -1 A1
    MOV -1 A2  ; n'oubliez pas de sauver l'adresse
    MOV -1 A3  ; de la pile à un endroit bien connu
    MOV -1 A4  ; sinon vous ne pourrez plus reprendre
    MOV -1 A5  ; la suite du programme...

Vous pouvez toujours accéder le (demi-)mot à l'adresse -1 en utilisant une adresse paire, car tous les accès à la mémoire ignorent le bit de poids faible de l'adresse. Donc le mot est aussi accessible à l'adresse -2 pour YASEP16 et -4 pour YASEP32.

Cela signifie aussi que sur une architecture qui ne supporte pas le parking, en supposant que la RAM y soit accessible, on peut avoir au maximum une paire "parquée" sans risque de corruption dûe au parking d'une autre paire. On peut toujours réaliser un "parking" logiciel en allouant une zone mémoire dédiée, mais avec le coût d'accès intempstifs à la mémoire.

Au niveau de l'architecture, cela ne change quasiment rien. Le registres de données sont habituellement "cachés" au niveau du banc de registre. Ce que le système de garage ajoute, c'est juste l'inhibition du signal d'écriture vers la mémoire, qui serait normalement actif à chaque fois qu'on écrit dans le registre de données.

En ce qui concerne la auvegarde et la restauration des registres d'un thread, différentes approches sont possibles.
* Si le système de parking n'est pas disponible, alors sauver le registre d'adresse suffit car le registre de donnée correspondant sera remis à la bonne valeur en relisant la mémoire lors de la restauration.
* Si le parking est implémenté alors il faut aussi conserver la valeur du registre D, mais ne le restaurer que si le registre A est à -1 (autrement cela risque d'altérer la mémoire qui a pu être modifiée entretemps).

Finalement, le parking n'ajoute que quelques portes logiques pour inhiber les accès à la mémoire si un registre a une certaine valeur. Pour le programmeur, cela veut dire qu'il est possible d'avoir plus de registres de données, au détriment des accès à la mémoire. Il est possible alors d'avoir R6, R7 et R8 mais cela empêchera l'utilisation de A1/D1, A2/D2 et A3/D3. À vous de choisir !

 

Le bit de retenue ("Carry flag")

La retenue est un registre d'un bit qui mémorise la retenue de la dernière instruction ADD ou SUB executée. Le bit de retenue est mis à 1 lorsqu'une addition dépasse la largeur des registres :

.profile YASEP16    
 MOV 5678h R1
 ADD CDEFh R1 R2  ; R2 <- 5678h + CDEFh = 12467h > FFFFh donc "carry" mis à 1

 ADD 1234h R1 R2  ; R2 <- 5678h + 1234h = 68ACh <= FFFFh donc "carry" mis à 0

Le bit "carry" peut ensuite être testé par une instruction conditionnelle :

 ADD 1234h R1    ; R1 <- R1 + 1234h (change la retenue)
 ADD 5 R2 CARRY  ; Si la retenue est égale à 1, alors on additionne R2 et 5

L'instruction SUB est basée sur l'addition et donc utilise le même bit de retenue que ADD. Cependant, pour SUB, la valeur du bit est inversée, ce dernier est à 1 lorsque la soustraction n'a pas généré de retenue :

MOV 4 R1
SUB 3 R1 R2 ; R2 = 3 - 4 = -1 ==> carry=0
SUB 4 R1 R2 ; R2 = 4 - 4 = 0  ==> carry=1
SUB 5 R1 R2 ; R2 = 5 - 4 = 1  ==> carry=1

Seules les instructions marquées par le flag "CHANGE_CARRY" peuvent changer le bit de retenue. Parmi celles-cis on trouve CMPU et CMPS, qui sont comme l'instruction SUB mais qui n'écrivent pas le résultat de la soustraction. Il y a aussi ESH EZH et IH qui indiquent un alignement qui dépasse du mot, en mettant ce bit à 1. Toutes les autres instructions préserveront ce bit.

La retenue peut donc être testée de nombreux cycles après l'exécution de ADD/SUB/CMPU/CMPS, même après des appels ou des retours de fonctions. La meilleure manière de forcer la valeur de la retenue est d'utiliser astucieusement les instructions CMPU ou CMPS, avec des opérandes qui donneront toujours le même résultat :

; mettre la retenue à 0 :
 CMPU R1, R1  ; R1 est égal à R1 donc le flag ne peut pas être à 1.
; mettre la retenue à 1 :
 CMPU 0, PC  ; PC est (quasiment) toujours supérieur à 0 donc la retenue est forcément à 1.
; (devrait devenir une macro/substitution)

 

Le flag "Zéro"

Comme pour le bit de retenue, le bit Zero est mis à jour pour les opcodes marqués par le flag CHANGE_ZERO.

Cela peut sembler redondant avec la condition "zero" qui peut tester presque tous les registres. Cependant, certaines instructions n'écrivent par le résultat du calcul dans un registre : CMPU/CMPS.