Architecture IA-64

L'architecture IA-64 (Intel Architecture 64 bits) est l'architecture des processeurs 64 bits Itanium et Itanium 2 développés conjointement par Intel et Hewlett-Packard. Elle utilise une technique appelée EPIC (Explicitly Parallel Instruction Computing).

L'architecture de ce micro-processeur se distingue des autres architectures car elle introduit plusieurs concepts novateurs. Bien que l'Itanium soit un (semi-)échec commercial, il est intéressant d'en étudier les caractéristiques essentielles.

Le principe de la technique EPIC est d'avoir une parallélisation explicite des instructions. Dans un programme d'un micro-processeur classique, les instructions sont disposées de manière séquentielle. Le micro-processeur peut essayer d'en exécuter certaines en parallèle mais il ne dispose d'aucune indication pour l'aider. Il doit donc à la volée gérer les dépendances des instructions et leur affecter une unité d'exécution. Les instructions du processeur Itanium sont au contraire disposées en paquets (appelés bundles), prêtes à être parallélisées. Chaque paquet dispose en plus d'indications sur les unités nécessaires à l'exécution de ses instructions. La parallélisation des instructions est donc préparée au moment de la compilation et de l'assemblage.

Voici quelques caractéristiques essentielles de l'architecture IA-64.

Organisation

L'organisation de l'architecture IA-64 est basée sur un nombre important de registres et sur de multiples unités d'exécution. Ces deux caractéristiques permettent un degré élevé de parallélisme. Dans un micro-processeur classique, le nombre de registres réellement disponibles peut être supérieur au nombre de registres utilisés par l'assembleur. Le micro-processeur effectue à la volée un renommage des registres afin d'utiliser au mieux tous les registres. Dans l'architecture IA-64, tous les registres sont visibles de l'assembleur.

Registres

Les registres dont dispose l'architecture IA-64 sont les suivants.

Unités d'exécution

Le nombre d'unités d'exécution peut varier et dépend du nombre de transistors disponibles pour une implémentation donnée. Le micro-processeur s'arrange pour utiliser au mieux les unités dont il dispose. L'architecture distingue les quatre types suivants d'unités d'exécution.

Unité I
unité arithmétique et logique sur les entiers (additions, soustractions, opérations logiques, décalage)
Unité M
unité de chargement et de rangement entre les registres et la mémoire, plus quelques opérations sur les entiers
Unité B
unité de branchement
Unité F
unité d'opérations sur les nombres flottants

Type d'instructions

L'architecture distingue aussi des types d'instructions qui correspondent plus ou moins aux unités qui peuvent les exécuter.

Type d'instruction Description Unité d'exécution
A UAL Unité I ou unité M
I Entier non UAL Unité I
M Accès mémoire Unité M
F Opération sur flottants Unité F
B Branchement Unité B
L + X Étendu Unité I/Unité B

Format des Instructions

Les instructions sont regroupées en paquets (bundles) de 128 bits contenant chacun trois instructions et des indications de parallélisations appelées champ gabarit. Chaque instruction est codée sur 41 bits. Il reste donc 5 = 128 - 3×41 bits pour le champ gabarit. Le format d'un paquet est le suivant.

Format d'un paquet
Format d'un paquet de 128 bits

Chaque instruction est codée sur 41 bits. Les 4 premiers bits donnent le code-op principal et les 6 derniers bits donnent le numéro du registre prédicatif qui conditionne l'exécution de l'instruction. L'utilisation des 31 = 41 - 4 - 6 bits restant dépend du code-op principal. Une utilisation typique est cependant de coder un code-op supplémentaire sur les 10 premiers bits puis de coder 3 registres sur 7 bits chacun.

Format d'une instruction
Format d'une instruction sur 41 bits

L'interprétation du code-op principal dépend du champ gabarit car celui-ci indique déjà l'unité d'exécution nécessaire à l'instruction. Pour que cette information ne soit pas redondante avec le code-op, c'est le code-op combiné avec le champ gabarit qui détermine l'instruction précise. Le gabarit indique d'abord une catégorie d'instructions, le code-op donne ensuite l'instruction dans la catégorie et le code-op supplémentaire peut encore préciser l'instruction. Il s'agit en fait d'une organisation hiérarchique des codes-op.

Champ gabarit

Le champ gabarit code deux informations distinctes. D'une part, il donne l'unité d'exécution nécessaire à chacune des trois instructions du paquet. D'autre part, il précise les positions d'éventuelles limites. Ces limites séparent des blocs d'instructions qui peuvent chevaucher plusieurs paquets. Le micro-processeur peut en effet charger plusieurs paquets simultanément pour essayer d'exécuter un maximum d'instructions en parallèle. Les blocs sont des suites d'instructions consécutives dans le flot d'instructions. Une limite indique qu'une instruction après elle peut avoir une dépendance avec une instruction avant elle. Autrement dit, toutes les instructions entre deux limites consécutives ne présentent pas de dépendance. Elles peuvent donc être exécutées en parallèle.

Ces deux informations sont regroupées dans un modèle dont le numéro est donné par le champ gabarit. Parmi les 32 codes possibles, seuls 24 sont assignés à des modèles. Les 8 codes restants sont réservés pour une utilisation ultérieure. La table suivante donne les modèles prévus. Les limites sont indiquées par une double barre verticale ||.

Modèle Instruction 0 Limite Instruction 1 Limite Instruction 2 Limite
00 Unité M Unité I Unité I
01 Unité M Unité I Unité I ||
02 Unité M Unité I || Unité I
03 Unité M Unité I || Unité I ||
04 Unité M Unité L Unité X
05 Unité M Unité L Unité X ||
08 Unité M Unité M Unité I
09 Unité M Unité M Unité I ||
0A Unité M || Unité M Unité I
0B Unité M || Unité M Unité I ||
0C Unité M Unité M Unité I
0D Unité M Unité F Unité I ||
0E Unité M Unité M Unité F
0F Unité M Unité M Unité F ||
10 Unité M Unité I Unité B
11 Unité M Unité I Unité B ||
12 Unité M Unité B Unité B
13 Unité M Unité B Unité B ||
16 Unité B Unité B Unité B
17 Unité B Unité B Unité B ||
18 Unité M Unité M Unité B
19 Unité M Unité M Unité B ||
1C Unité M Unité F Unité B
1D Unité M Unité F Unité B ||

Il est à la charge du compilateur de regrouper au mieux les instructions en paquets puis en blocs afin d'optimiser l'utilisation des unités d'exécution du micro-processeur.

Exécution prédicative

Les registres prédicatifs sont des registres binaires qui sont positionnés par les instructions de comparaison. Ils s'apparentent donc aux indicateurs binaires comme n, z et p présents dans la plupart des micro-processeurs. Par contre, ils sont tous indifférenciés et chacun d'entre eux peut être positionné par une comparaison. En fait, chaque comparaison positionne deux registres prédicatifs avec des valeurs opposées.

L'exécution de chaque instruction est conditionnée par la valeur d'un des registres prédicatifs déterminé par les 6 derniers bits de l'instruction. Celle-ci est exécutée seulement si ce registre prédicatif vaut 1. Sinon, elle n'est pas exécutée. Le registre prédicatif p0 a toujours la valeur 1. Une instruction indique donc ce registre prédicatif si elle doit toujours être exécutée.

La compilation d'une structure de contrôle if-then-else d'un langage de haut niveau se traduit par des sauts en langage d'assembleur. La compilation du morceau de code ci-dessous

  if (cond) {
    bloc 1
  } else {
    bloc 2
  }

conduit au code d'assembleur LC-3 suivant.

	; Calcul de cond
        ...
        BRz bloc2	; Condition fausse
	; Début du bloc 1
        ...
        BR finif        ; Fin du bloc 1
bloc2:  ; Début du bloc 2
        ...
finif:  ; Suite du programme

Si les blocs d'instructions 1 et 2 sont relativement courts, la présence des sauts pénalise l'exécution du programme.

Le principe d'utilisation des registres prédicatifs est le suivant. La calcul de la condition positionne deux registres prédicatifs p1 et p2 de manière opposée. Si la condition est vraie, p1 est positionné à vrai et p2 à faux et si la condition est fausse, p1 est positionné à faux et p2 à vrai. Ensuite, le programme exécute les instructions du bloc 1 conditionnées par p1 et les instructions du bloc 2 conditionnées par p2. Cela donne le code en assembleur suivant où les sauts ont disparu. La syntaxe (p) instr signifie que l'instruction instr est conditionnée par le registre prédicatif p. Le micro-processeur exécute plus d'instructions mais cela est largement compensé par la disparition des sauts si les blocs 1 et 2 sont assez courts.

	; Calcul de cond
        ...
	; Positionnement de p1 et p2
	; Début du bloc 1
	(p1) instr1
	(p1) instr2
        ...
	; Début du bloc 2
        (p2) instr1 
        (p2) instr2
	...
        ; Suite du programme

Chargement spéculatif

L'idée du chargement spéculatif est d'anticiper le chargement de valeurs à partir de la mémoire afin d'éviter que le calcul reste bloqué en attente d'une valeur. Le chargement d'une valeur est scindé en deux actions. La première est de demander le chargement sans attendre que celui-ci soit terminé. La seconde est de vérifier que le chargement a été effectivement accompli au moment où la valeur doit être utilisée. Le principe est de placer la demande bien avant l'utilisation de la valeur. Le chargement a alors largement le temps de s'effectuer. La vérification est ensuite faite juste avant d'utiliser la valeur. Si la valeur n'est pas encore chargée, le calcul est bloqué mais cet événement survient rarement.