Les instructions du YASEP ont un format fixe sur 16 bits avec une extension de 16 bits par une valeur immédiate ou d'autres registres (forme étendue). Ce format est identique pour YASEP16 et YASEP32 : YASEP32 peut utiliser des instructions sur 16 bits et YASEP16 décode les instructions sur 32 bits. En fait le décodeur d'instructions varie peu entre les 2 versions, et il doit accepter les deux longueurs.
Chaque instruction peut contenir :
Ces champs et flags ne sont pas tous utilisés en même temps. Certaines combinaisons sont impossibles par construction, d'autres n'ont aucun sens, le reste peut être transformé par l'assembleur. Cependant, une grande variété d'instructions (un opcode suivi de noms de regitres, de données immédiates et d'autres flags) peut être écrite par un programmeur. Cette page explique quelles "formes d'instructions" sont disponibles, comment elles sont structurées et quand on les utilise.
Une "forme d'instruction" est nommée selon son écriture en langage assembleur. C'est une séquence de lettres, désignant chacune un champ :
La structure du YASEP ne permet qu'un nombre limité de formes. Quel que soit l'opcode, les deux bits de poids faible d'une instruction du YASEP déterminent comment interpréter les champs suivants, ce qui peut donner 4 combinaisons:
Registre source Source et destination Registre destination Valeur mmédiate Incrémentation Code de condition |
|||||||||||||||||||||||||||||||||
|
adresse SND (opérande Négatif) ↓ |
||||||||||||||||||||||||||||||||
S | SND | OPCODE | 0 | 0 | RR: | ADD R1 R2 | |||||||||||||||||||||||||||
3:0 | SND | OPCODE | 1 | 0 | iR: | ADD 3 R1 | |||||||||||||||||||||||||||
DST | IMM16 15:0 | SN | OPCODE | 0 | 1 | IRR: | ADD 1234h R1 R2 | ||||||||||||||||||||||||||
DST | IMM20 15:0 | 19:16 | OPCODE | 0 | 1 | IR: | MOV 12345h R1 | ||||||||||||||||||||||||||
DST | CND | code | 1 | up | 5:0 | SN | OPCODE | 1 | 1 | iRR: | ADD -25 R2 R3 ZERO R4- | ||||||||||||||||||||||
DST | CND | code | 0 | upd | S | SN | OPCODE | 1 | 1 | RRR: | ADD D1+ R2 D3+ LSB0 R4 | ||||||||||||||||||||||
↑ Exemples |
|||||||||||||||||||||||||||||||||
3 1 |
3 0 |
2 9 |
2 8 |
2 7 |
2 6 |
2 5 |
2 4 |
2 3 |
2 2 |
2 1 |
2 0 |
1 9 |
1 8 |
1 7 |
1 6 |
1 5 |
1 4 |
1 3 |
1 2 |
1 1 |
1 0 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
||
↑ Aux |
↑ Imm/Reg |
↑ Court/Long |
La forme étendue dispose aussi du bit auxiliaire Aux qui indique comment utiliser le champ si4 : soit comme addresse de registre (RRR), soit comme valeur immédiate signée sur 6 bits (iRR).
En plus, certains opcodes comme MOV, ayant un seul opérande, peuvent utiliser les 4 bits inutilisés d'une adresse de registre (snd) pour constituer une valeur immédiate signée sur 20 bits.
Cela porte à 6 le nombre de formes physiquement possibles, mais l'assembleur permet d'écrire encore d'autres formes, par exemple en exploitant certaines combinaisons de champs inutilisés.
Comme le champ Imm16 de la forme longue, le champ Imm4 est considéré comme un nombre signé.
Cependant, si l'instruction fait partie du groupe SHL (rotations et décalages) alors le champ est considéré comme un nombre positif, permettant des décalages de 0 à 15 positions, pour YASEP16 et YASEP32. Pour des décalages supérieurs à 15 bits avec YASEP32, utilisez une forme longue avec Imm16 ou une forme étendue avec Imm6.
Le YASEP écrit le résultat des opérations (si il y a lieu) dans un registre dont l'adresse est donnée par les champs snd ou dst, en fonction de la forme de l'instruction :
Ce système pourrait complexifier les développement dans un futur lointain mais il est indispensable pour conserver la compacité et l'orthogonalité des instructions. De plus, dans le chemin de données, ce qui compte vraiment (pour la performance) c'est le temps d'accès aux opérandes. Le "calcul" de l'adresse de destination peut être réalisé rapidement, en même temps que l'ALU (une bonne quinzaine de niveaux logiques). Et puisque le YASEP n'utilise pas de "bypass de pipeline" (le microYASEP attend l'écriture d'un résultat avant de le réutiliser), on peut avoir plus de liberté avec les champs et obtenir le maximum de chaque combinaison de bits. C'est l'assembleur qui va se charger de mettre les bonnes valeurs dans les bons bits.
Commençons par les formes d'instructions les plus simples. Les instructions courtes ont leur bit de poids faible égal à zéro, ce qui indique une longueur de 16 bits. Il reste juste assez de place pour un autre flag, un opcode de 6 bits et 2 champs de 4 bits.
1 5 | 1 4 |
1 3 | 1 2 |
1 1 | 1 0 |
9 | 8 |
7 | 6 |
5 | 4 |
3 | 2 |
1 | 0 |
S | SND | OPCODE | I/R | S/L | |||||||||||
R2 | R1 | ADD | 0 | 0 |
1 5 | 1 4 |
1 3 | 1 2 |
1 1 | 1 0 |
9 | 8 |
7 | 6 |
5 | 4 |
3 | 2 |
1 | 0 |
S | SND | OPCODE | I/R | S/L | |||||||||||
-3 | R1 | ADD | 1 | 0 |
En pratique, ces deux combinaisons peuvent être utilisées de 5 façons en langage assembleur, puisque chacun de ces champs peut être plus ou moins utile en fonction du type d'opération désirée :
ADD R1 R2 ; R2 <- R1+R2C'est l'une des formes les plus courantes. si4 et snd donnent les adresses des deux registres contenant les opérandes. snd est aussi la destination du résultat.
ADD 2 R3 ; R3 <- 2+R3Une autre forme courante. si4 donne une valeur immédiate signée sur 4 bits, donc snd sert d'adresse de registre pour l'autre opérande source ainsi que la destination.
Certaines instructions comme OUT et PUT utilisent si4 pour indiquer l'adresse de destination, mais comme c'est une valeur immédiate, elle est écrite avant le registre source.
PUT 3 R1 ; Envoie le contenu de R1 dans le registre spécial n°3
NEG R1 ; en interne : R1 = -R1C'est aussi une façon courte d'écrire que les deux champs sont le même registre. L'assembleur va correctement remplir les champs si4 et snd si cette forme est acceptable pour l'opcode désiré.
CRIT 3 ; désactive les interruptions pour les 3 instructions suivantesRarement utilisé mais parfois nécessaire. si4 contient la valeur immédiate et snd est ignoré.
NOP ; pas d'opération, tout les champs sont ignorés.C'est un cas extrême où aucun opérande n'est nécessaire. si4 et snd sont ignorés.
Le YASEP interprète les instructions comme ayant une longueur de 32 bits quand leur bit de poids faible est à 1. Les 16 bits de poids faible sont très similaires aux formes courtes. Le second bit de poids faible indique comment interpréter le demi-mot de poids fort :
DST | IMM16 15:0 | SN | OPCODE | E/I | S/L | ||||||||||||||||||||||||||
R1 | 1234h | R2 | ADD | 0 | 1 | ||||||||||||||||||||||||||
3 1 | 3 0 | 2 9 | 2 8 |
2 7 | 2 6 | 2 5 | 2 4 |
2 3 | 2 2 | 2 1 | 2 0 |
1 9 | 1 8 | 1 7 | 1 6 |
1 5 | 1 4 | 1 3 | 1 2 |
1 1 | 1 0 |
9 | 8 |
7 | 6 |
5 | 4 |
3 | 2 |
1 | 0 |
ADD 1234h R2 R1 ; R1 <- 1234h + R2Cette forme est assez courante. En plus de l'opérande immédiat sur 16 bits, snd fournit l'adresse du registre qui sert de deuxième opérande. dst donne la destination du résultat.
GET 1234h R1 ; R1 <- SR[1234h]Cette forme est utilisée pour les instructions GET ou MOV. Le champ snd est ignoré parce que seul DST a un sens pour l'opération. L'assembleur détecte aussi si la valeur immédiate tient dans 4 bits pour utiliser une forme courte (FORM_iR) ou longue, et optimiser la taille du code binaire.
C'est aussi utilisé par les instructions booléennes (ROP2) et l'ASU (ADD et SUB) pour simuler FORM_iR mais avec une valeur immédiate sur 16 bits. Lorsqu'il trouve le flag ALIAS_IRR, l'assembleur va utiliser FORM_IRR et mettre la même adresse de registre dans les deux champs dst et snd.
; Ces deux instructions font la même chose et sont encodées de la même manière : OR 1234h R1 R1 OR 1234h R1
.profile YASEP32 ; Les 4 bits de poids fort sont ignorés par YASEP16 CALL 12345h R1 MOV 12345h R1MOV 12345h R1 ; R1 <= 12345h
DST | IMM20 15:0 | 3:0 | OPCODE | E/I | S/L | ||||||||||||||||||||||||||
R1 | 1234h | 5h | MOV | 0 | 1 | ||||||||||||||||||||||||||
3 1 | 3 0 | 2 9 | 2 8 |
2 7 | 2 6 | 2 5 | 2 4 |
2 3 | 2 2 | 2 1 | 2 0 |
1 9 | 1 8 | 1 7 | 1 6 |
1 5 | 1 4 | 1 3 | 1 2 |
1 1 | 1 0 |
9 | 8 |
7 | 6 |
5 | 4 |
3 | 2 |
1 | 0 |
Quelques instructions, comme OUT and PUT, utilisent Imm20 comme une adresse de destination. Comme c'est une valeur immédiate, elle est écrite avant le registre source.
PUT 12345h R1 ; Envoie le contenu de R1 dans le registre spécial n°12345h
Les formes dites "étendues" réutilisent la structure des formes courtes et rajoutent 16 bits pour fournir d'autres fonctionnalités :
DST | CND | code | aux | upd | S | SN | OPCODE | E/I | S/L | ||||||||||||||||||||||
R3 | 0 | 0 | 0 | 0 | R2 | R1 | ADD | 1 | 1 | ||||||||||||||||||||||
3 1 | 3 0 | 2 9 | 2 8 |
2 7 | 2 6 | 2 5 | 2 4 |
2 3 | 2 2 | 2 1 | 2 0 |
1 9 | 1 8 | 1 7 | 1 6 |
1 5 | 1 4 | 1 3 | 1 2 |
1 1 | 1 0 |
9 | 8 |
7 | 6 |
5 | 4 |
3 | 2 |
1 | 0 |
ADD R1 R2 R3 ; R3 <- R1+R2si4 (R1) et snd (R2) sont tous les deux des registres sources, le résultat est écrit dans dst (R3).
DST | CND | code | aux | upd | Imm6 | SN | OPCODE | E/I | S/L | ||||||||||||||||||||||
R3 | 0 | 0 | 1 | 0 | 25 | R1 | ADD | 1 | 1 | ||||||||||||||||||||||
3 1 | 3 0 | 2 9 | 2 8 |
2 7 | 2 6 | 2 5 | 2 4 |
2 3 | 2 2 | 2 1 | 2 0 |
1 9 | 1 8 | 1 7 | 1 6 |
1 5 | 1 4 | 1 3 | 1 2 |
1 1 | 1 0 |
9 | 8 |
7 | 6 |
5 | 4 |
3 | 2 |
1 | 0 |
ADD 25 R1 R3 ; R3 <- R1+25La valeur immédiate de Imm6 (25) et la valeur du registre indiqué par snd (R1) sont les opérandes de l'addition, dont le résultat est écrit dans dst (R3).
Les instructions étendues du YASEP disposent de 7 bits pour définir la condition (ou le prédicat) qui valide (ou non) l'écriture du résultat de l'instruction courante. Ils permettent d'encoder trois types de conditions :
Fonction | Neg=0 | Neg=1 |
00 | NZ (Registre[cnd]!=0) | ZERO (Registre[cnd]=0) |
01 | BIT1 (Shadow[cnd]!=0) | BIT0 (Shadow[cnd]=0) |
10 | LSB1 / ODD (Registre[cnd] impair) | LSB0 / EVEN (Registre[cnd] pair) |
11 | MSB1 / NEGATIVE (Registre[cnd] négatif) | MSB0 / POSITIVE (Registre[cnd] positif) |
cnd=0 (PC)
Fonction | Neg=0 | Neg=1 |
00 | Always (défaut) | Never (réservé) |
01 | BIT1 (Shadow[0]!=0) | BIT0 (Shadow[0]=0) |
10 | CARRY / NO_BORROW (Retenue à 1) | NO_CARRY / BORROW (No Carry) |
11 | EQ (flag Égal à 1) | NEQ (flag Égal à 0) |
L'encodage est tel que la condition "toujours" (ALWAYS) est représentée avec tous les bits à 0.
Toutes les conditions peuvent être inversées par le bit Neg. Donc on peut coder le prédicat "jamais" en inversant la condition "toujours" (qui est celle par défaut).
Le registre "shadow" est encore indéterminé. Pour l'instant, on considère qu'il s'agit d'une copie locale du registre R1 mais on pourra le changer avec un SR pour indiquer s'il s'agit d'un port d'entrées-sorties ou d'autres choses encore.
ADD R1,R2,R3 LSB0 R4 ;Si R4 est pair alors R3 <= R1 + R2DST | CND | code | aux | upd | S | SN | OPCODE | E/I | S/L | ||||||||||||||||||||||
R3 | R4 | 101 | 0 | 0 | R2 | R1 | ADD | 1 | 1 | ||||||||||||||||||||||
3 1 | 3 0 | 2 9 | 2 8 |
2 7 | 2 6 | 2 5 | 2 4 |
2 3 | 2 2 | 2 1 | 2 0 |
1 9 | 1 8 | 1 7 | 1 6 |
1 5 | 1 4 | 1 3 | 1 2 |
1 1 | 1 0 |
9 | 8 |
7 | 6 |
5 | 4 |
3 | 2 |
1 | 0 |
L'utilisation des bits restants pour l'auto-update a été figée le 2014-01-31. Certains codes peuvent encore évoluer mais le format ne bougera plus. Voir les justifications à Definition of the auto-update fields.
00 | 01 | 10 | 11 | |
00 | NOP | SND+,SI4+,DST+ | SI4- | SI4+ |
01 | SND-,SI4- | SND+,SI4+ | SND- | SND+ |
10 | DST-,SI4- | DST+,SI4+ | DST- | DST+ |
11 | DST-,SND- | DST+,SND+ | CND- | CND+ |
00 | NOP (aucune modification) |
01 | SND+ (incrémente la source) |
10 | DST+ (incrémente la destination) |
11 | CND- (décrémente la condition) |
Les implémentations du YASEP qui ne prennent pas en charge cette fonctionnalité doivent déclencher une exception si elles décodent autre chose que NOP. Ce n'est pas un fonctionnalité critique pour le microYASEP par exemple.
Ces bits sont interprétés en fonction du bit Aux, car il serait inutile de modifier si4 si celui-ci contient une valeur immédiate. Il reste seulement 4 ou 2 bits disponibles mais les combinaisons retenues sont les plus utiles en pratique et devraient augmenter la densité du code.
La pré-incrémentation et la pré-décrémentation ne sont pas proposées
à cause de leur complexité architecturale. Si une adresse est pré-calculée
avant d'exécuter l'opcode, à l'intérieur d'une même instruction :
- cela augmente la latence de l'instruction car la mémoire doit être lue
et peut retarder le reste de l'exécution durant de nombreux cycles
- l'adresse générée pourrait déclencher une faute d'accès à la mémoire et il n'y a pas de
moyen de stopper l'exécution d'une instruction en plein milieu ni de reprendre l'opération partielle ensuite.
Les règles implicites de modification sont les suivantes :
Certaines combinaisons de modifications produisent des cas particuliers :
ADD R1 R2 R3+ ; R3 ne sera pas incrémenté et contiendra la somme de R1 et R2
ADD R1+ R1+ R2 ; n'incrémentera R1 qu'une seule fois ADD D1+ D2+ D1+ ; A1 sera incrémenté une seule fois
Il en résulte que le champ DST ne peut modifier que les registres A, et seulement de manière implicite en adressant son registre D.
MOV 50, R1 ; initialise le compteur à 50 MOV 0, R2 ; initialise l'accumulateur ADD R1 R2 ; ajoute le compteur à l'accumulateur ADD -2 PC NZ R1- ; Si R1 n'est pas à zéro alors ; - décremente R1 ; - saute de 2 octets en arrière
Une boucle, ou un bloc à sauter, peut donc contenir jusqu'à 8 instructions longues ou 16 instructions courtes, sans nécessiter d'opération ou de registre supplémentaire.
Les registres pointeurs peuvent aussi être incrémentés automatiquement lorsque leur registre de donnée est accédé. Il est ainsi facile de recopier ou de modifier un bloc de données :
MOV 50, R1 ; initialise le compteur à 50 MOV 2345h, A1 ; initialise la source MOV 6789h, A2 ; destination MOV D1+ D2+ ; recopie un mot ADD -4 PC NZ R1- ; Si R1 n'est pas à zéro alors ; - décremente R1 ; - saute de 4 octets en arrière
L'accès à la pile est aussi facilité, les instructions PUSH et POP sont émulées simplement. Puisqu'il n'y a pas de pile dédiée, il est même possible d'implémenter des langages à piles multiples tels que FORTH.